import type Stripe from 'stripe';
import { orderRepository } from '@/lib/db/repositories/orderRepository';
import { enqueueOrderReceived } from '@/lib/email/queue/enqueue';
import { sessionAmountMatchesOrder } from '@/lib/stripe/amount';

function paymentIntentId(session: Stripe.Checkout.Session): string | null {
  return typeof session.payment_intent === 'string'
    ? session.payment_intent
    : (session.payment_intent?.id ?? null);
}

function paymentIntentIdFromIntent(intent: Stripe.PaymentIntent): string {
  return intent.id;
}

export async function syncOrderPaymentFromCheckoutSession(
  session: Stripe.Checkout.Session
): Promise<boolean> {
  const orderId = session.metadata?.order_id;
  if (!orderId) return false;

  const order = await orderRepository.findWithItems(orderId);
  if (!order) return false;

  // Already paid (e.g. confirmation page won the race) — no side effects.
  if (order.payment_status === 'paid') return true;

  if (session.payment_status !== 'paid') return false;

  if (!sessionAmountMatchesOrder(session, order)) {
    console.error('[stripe] amount mismatch', {
      orderId: order.id,
      orderRef: order.order_ref,
      expectedCents: Math.round(parseFloat(order.total_amount) * 100),
      sessionAmountTotal: session.amount_total,
      sessionId: session.id,
    });
    return false;
  }

  const affected = await orderRepository.markStripePaid(
    order.id,
    session.id,
    paymentIntentId(session)
  );

  // Only the winner of the webhook vs /confirmation race enqueues email.
  // markStripePaid uses WHERE payment_status != 'paid', so a concurrent
  // caller gets affected === 0 and must not trigger side effects.
  if (affected > 0) {
    void enqueueOrderReceived({
      id: order.id,
      order_ref: order.order_ref,
      customer_name: order.customer_name,
      customer_phone: order.customer_phone,
      pickup_time: order.pickup_time,
      notes: order.notes,
      total_amount: order.total_amount,
      items: order.items.map((item) => ({
        item_name: item.item_name,
        quantity: item.quantity,
        line_total: item.line_total,
      })),
    });
  }

  return affected > 0;
}

export async function handleCheckoutSessionExpired(
  session: Stripe.Checkout.Session
): Promise<boolean> {
  const orderId = session.metadata?.order_id;
  if (!orderId) {
    const bySession = await orderRepository.findByStripeCheckoutSessionId(session.id);
    if (!bySession) return false;
    const affected = await orderRepository.markStripePaymentCancelled(bySession.id);
    return affected > 0;
  }

  const affected = await orderRepository.markStripePaymentCancelled(orderId);
  return affected > 0;
}

export async function handlePaymentIntentFailed(intent: Stripe.PaymentIntent): Promise<boolean> {
  const orderId = intent.metadata?.order_id;
  let order = orderId ? await orderRepository.findById(orderId) : null;

  if (!order) {
    order = await orderRepository.findByStripePaymentIntentId(intent.id);
  }

  if (!order) return false;
  if (order.payment_status === 'paid') return false;

  const affected = await orderRepository.markStripePaymentFailed(
    order.id,
    paymentIntentIdFromIntent(intent)
  );

  return affected > 0;
}

export async function handleStripeWebhookEvent(event: Stripe.Event): Promise<void> {
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      await syncOrderPaymentFromCheckoutSession(session);
      break;
    }
    case 'checkout.session.expired': {
      const session = event.data.object as Stripe.Checkout.Session;
      await handleCheckoutSessionExpired(session);
      break;
    }
    case 'payment_intent.payment_failed': {
      const intent = event.data.object as Stripe.PaymentIntent;
      await handlePaymentIntentFailed(intent);
      break;
    }
    default:
      break;
  }
}
