import { NextResponse } from 'next/server';
import { getStripeClientFromDb } from '@server/lib/stripe';
import { db, subscriptions, plans } from '@server/db/drizzle';
import { eq, or, sql } from 'drizzle-orm';
import { initDatabase } from '@server/db/init';
import type Stripe from 'stripe';

export const runtime = 'nodejs';

function getSubPeriodEnd(sub: Stripe.Subscription): number | null {
  const item = sub.items?.data?.[0];
  if (item && 'current_period_end' in item) {
    return (item as Stripe.SubscriptionItem & { current_period_end: number }).current_period_end;
  }
  return null;
}

type PlanStatusValue = 'active' | 'inactive' | 'trial' | 'cancelled' | 'past_due';

function normalizeStripeStatus(status: string): PlanStatusValue {
  switch (status) {
    case 'active':       return 'active';
    case 'trialing':     return 'trial';
    case 'past_due':     return 'past_due';
    case 'canceled':
    case 'cancelled':    return 'cancelled';
    case 'unpaid':
    case 'incomplete':   return 'past_due';
    default:             return 'active';
  }
}

async function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session) {
  const restaurantId = session.metadata?.restaurant_id;
  const planId = session.metadata?.plan_id;
  if (!restaurantId || !planId || !session.subscription) return;

  const stripe = await getStripeClientFromDb();
  const subscriptionId = typeof session.subscription === 'string' ? session.subscription : session.subscription.id;
  const stripeSub = await stripe.subscriptions.retrieve(subscriptionId);
  const periodEnd = getSubPeriodEnd(stripeSub);

  const priceId = stripeSub.items.data[0]?.price.id ?? null;
  const customerId = session.customer as string;
  if (periodEnd != null) {
    await db.execute(sql`
      INSERT INTO subscriptions (restaurant_id, plan_id, stripe_customer_id, stripe_subscription_id, stripe_price_id, status, current_period_end)
      VALUES (${restaurantId}, ${planId}, ${customerId}, ${stripeSub.id}, ${priceId}, 'active', to_timestamp(${periodEnd}))
      ON CONFLICT (restaurant_id) DO UPDATE SET
        status = 'active', plan_id = ${planId}, stripe_customer_id = ${customerId},
        stripe_subscription_id = ${stripeSub.id}, stripe_price_id = ${priceId},
        current_period_end = to_timestamp(${periodEnd})
    `);
  } else {
    await db.execute(sql`
      INSERT INTO subscriptions (restaurant_id, plan_id, stripe_customer_id, stripe_subscription_id, stripe_price_id, status)
      VALUES (${restaurantId}, ${planId}, ${customerId}, ${stripeSub.id}, ${priceId}, 'active')
      ON CONFLICT (restaurant_id) DO UPDATE SET
        status = 'active', plan_id = ${planId}, stripe_customer_id = ${customerId},
        stripe_subscription_id = ${stripeSub.id}, stripe_price_id = ${priceId}
    `);
  }
}

function resolveSubscriptionId(invoice: Stripe.Invoice): string | null {
  const fromParent = invoice.parent?.subscription_details?.subscription;
  if (fromParent) {
    return typeof fromParent === 'string' ? fromParent : (fromParent as Stripe.Subscription).id;
  }
  const direct = (invoice as unknown as Record<string, unknown>).subscription;
  if (direct) {
    return typeof direct === 'string' ? direct : (direct as Stripe.Subscription).id;
  }
  return null;
}

async function handleInvoicePaymentSucceeded(invoice: Stripe.Invoice) {
  const customerId = typeof invoice.customer === 'string' ? invoice.customer : (invoice.customer as Stripe.Customer)?.id;
  if (!customerId) return;

  const rows = await db.select({ restaurantId: subscriptions.restaurantId })
    .from(subscriptions)
    .where(eq(subscriptions.stripeCustomerId, customerId))
    .limit(1);
  const dbSub = rows[0];
  if (!dbSub) return;

  const subId = resolveSubscriptionId(invoice);
  if (!subId) {
    await db.update(subscriptions)
      .set({ status: 'active' })
      .where(eq(subscriptions.restaurantId, dbSub.restaurantId));
    return;
  }

  const stripe = await getStripeClientFromDb();
  const stripeSub = await stripe.subscriptions.retrieve(subId);
  const periodEnd = getSubPeriodEnd(stripeSub);

  if (periodEnd != null) {
    await db.execute(sql`
      UPDATE subscriptions SET status = 'active', current_period_end = to_timestamp(${periodEnd}) WHERE restaurant_id = ${dbSub.restaurantId}
    `);
  } else {
    await db.update(subscriptions)
      .set({ status: 'active' })
      .where(eq(subscriptions.restaurantId, dbSub.restaurantId));
  }
}

async function handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
  const customerId = typeof invoice.customer === 'string' ? invoice.customer : (invoice.customer as Stripe.Customer)?.id;
  if (!customerId) return;

  await db.update(subscriptions)
    .set({ status: 'past_due' })
    .where(eq(subscriptions.stripeCustomerId, customerId));
}

async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
  const periodEnd = getSubPeriodEnd(subscription);
  const priceId = subscription.items.data[0]?.price.id ?? null;

  let planRow: { id: string } | undefined;
  if (priceId) {
    const rows = await db.select({ id: plans.id })
      .from(plans)
      .where(or(eq(plans.stripePriceIdMonthly, priceId), eq(plans.stripePriceIdAnnual, priceId)))
      .limit(1);
    planRow = rows[0];
  }

  const normalizedStatus = normalizeStripeStatus(subscription.status);
  if (periodEnd != null && planRow) {
    await db.execute(sql`
      UPDATE subscriptions SET status = ${normalizedStatus}, stripe_price_id = ${priceId}, plan_id = ${planRow.id},
        current_period_end = to_timestamp(${periodEnd})
      WHERE stripe_subscription_id = ${subscription.id}
    `);
  } else if (periodEnd != null) {
    await db.execute(sql`
      UPDATE subscriptions SET status = ${normalizedStatus}, stripe_price_id = ${priceId},
        current_period_end = to_timestamp(${periodEnd})
      WHERE stripe_subscription_id = ${subscription.id}
    `);
  } else {
    await db.update(subscriptions)
      .set({ status: normalizedStatus, stripePriceId: priceId })
      .where(eq(subscriptions.stripeSubscriptionId, subscription.id));
  }
}

async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
  await db.update(subscriptions)
    .set({ status: 'cancelled', stripeSubscriptionId: null })
    .where(eq(subscriptions.stripeSubscriptionId, subscription.id));
}

export async function POST(req: Request) {
  await initDatabase();

  const body = await req.text();
  const sig = req.headers.get('stripe-signature');
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  if (!webhookSecret) {
    console.error('[Stripe Webhook] STRIPE_WEBHOOK_SECRET is not configured — rejecting request');
    return NextResponse.json({ error: 'Webhook secret not configured' }, { status: 500 });
  }

  let event: Stripe.Event;
  try {
    const stripe = await getStripeClientFromDb();
    event = stripe.webhooks.constructEvent(body, sig ?? '', webhookSecret);
  } catch (err) {
    console.error('[Stripe Webhook] Signature verification failed:', err);
    return NextResponse.json({ error: 'Webhook signature verification failed' }, { status: 400 });
  }

  try {
    switch (event.type) {
      case 'checkout.session.completed':
        await handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session);
        break;
      case 'invoice.payment_succeeded':
        await handleInvoicePaymentSucceeded(event.data.object as Stripe.Invoice);
        break;
      case 'invoice.payment_failed':
        await handleInvoicePaymentFailed(event.data.object as Stripe.Invoice);
        break;
      case 'customer.subscription.updated':
        await handleSubscriptionUpdated(event.data.object as Stripe.Subscription);
        break;
      case 'customer.subscription.deleted':
        await handleSubscriptionDeleted(event.data.object as Stripe.Subscription);
        break;
      default:
        break;
    }
  } catch (err) {
    console.error('[Stripe Webhook] Handler error for event', event.type, ':', err);
    return NextResponse.json({ error: 'Webhook handler failed — Stripe should retry' }, { status: 500 });
  }

  return NextResponse.json({ received: true });
}
