import { NextRequest, NextResponse } from "next/server";
import { randomUUID } from "crypto";
import type Stripe from "stripe";
import { db, users, tierDefinitions, processedStripeEvents, auditLogs, eq, or } from "@epicure/db";
import { getStripeClient } from "@/lib/stripe";
import { getSiteSetting } from "@/lib/site-settings";

type SubscriptionStatus = "active" | "trialing" | "past_due" | "canceled" | "incomplete";

async function resolveTierFromPriceId(priceId: string | null | undefined): Promise<"pro" | "family" | null> {
  if (!priceId) return null;
  const row = await db.query.tierDefinitions.findFirst({
    where: or(eq(tierDefinitions.stripePriceIdMonthly, priceId), eq(tierDefinitions.stripePriceIdYearly, priceId)),
  });
  return row?.tier === "pro" || row?.tier === "family" ? row.tier : null;
}

function mapStripeStatus(status: Stripe.Subscription.Status): SubscriptionStatus {
  switch (status) {
    case "trialing": return "trialing";
    case "past_due": return "past_due";
    case "canceled":
    case "unpaid": return "canceled";
    case "incomplete":
    case "incomplete_expired": return "incomplete";
    default: return "active";
  }
}

// The billing-period fields moved from the Subscription object onto its
// items in a 2025 Stripe API revision — read the item first, fall back to
// the top-level field for older API versions. Verify against whichever
// API version the configured account is actually pinned to before
// switching on real (non-test) keys, per the plan's Stripe-CLI test note.
function currentPeriodEnd(subscription: Stripe.Subscription): Date | null {
  const itemPeriodEnd = subscription.items.data[0]?.current_period_end;
  const legacyPeriodEnd = (subscription as unknown as { current_period_end?: number }).current_period_end;
  const unix = itemPeriodEnd ?? legacyPeriodEnd;
  return unix ? new Date(unix * 1000) : null;
}

async function logBillingEvent(userId: string, action: string, metadata: unknown): Promise<void> {
  await db.insert(auditLogs).values({
    id: randomUUID(),
    userId,
    action: `billing.${action}`,
    targetType: "user",
    targetId: userId,
    metadata: JSON.stringify(metadata),
    createdAt: new Date(),
  });
}

async function findUserByCustomerId(customerId: string) {
  const [user] = await db.select({ id: users.id, subscriptionStatus: users.subscriptionStatus }).from(users).where(eq(users.stripeCustomerId, customerId));
  return user;
}

export async function POST(req: NextRequest) {
  const stripe = await getStripeClient();
  const webhookSecret = await getSiteSetting("STRIPE_WEBHOOK_SECRET");
  if (!stripe || !webhookSecret) {
    return NextResponse.json({ error: "Stripe not configured" }, { status: 400 });
  }

  const rawBody = await req.text();
  const signature = req.headers.get("stripe-signature");
  if (!signature) {
    return NextResponse.json({ error: "Missing signature" }, { status: 400 });
  }

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
  } catch {
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  // Dedup: Stripe may redeliver the same event within its retry window.
  const [inserted] = await db
    .insert(processedStripeEvents)
    .values({ id: event.id, type: event.type })
    .onConflictDoNothing()
    .returning({ id: processedStripeEvents.id });
  if (!inserted) {
    return NextResponse.json({ received: true, duplicate: true });
  }

  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.Checkout.Session;
      const userId = session.client_reference_id ?? (session.metadata?.["userId"] ?? null);
      const customerId = typeof session.customer === "string" ? session.customer : session.customer?.id;
      const subscriptionId = typeof session.subscription === "string" ? session.subscription : session.subscription?.id;
      if (!userId || !customerId || !subscriptionId) break;

      const subscription = await stripe.subscriptions.retrieve(subscriptionId);
      const tier = await resolveTierFromPriceId(subscription.items.data[0]?.price.id);
      if (!tier) break;

      await db.update(users).set({
        tier,
        stripeCustomerId: customerId,
        stripeSubscriptionId: subscriptionId,
        subscriptionStatus: mapStripeStatus(subscription.status),
        currentPeriodEnd: currentPeriodEnd(subscription),
        updatedAt: new Date(),
      }).where(eq(users.id, userId));
      await logBillingEvent(userId, "checkout_completed", { tier, subscriptionId });
      break;
    }

    case "customer.subscription.updated": {
      const subscription = event.data.object as Stripe.Subscription;
      const customerId = typeof subscription.customer === "string" ? subscription.customer : subscription.customer.id;
      const user = await findUserByCustomerId(customerId);
      if (!user) break;

      const tier = await resolveTierFromPriceId(subscription.items.data[0]?.price.id);
      await db.update(users).set({
        ...(tier ? { tier } : {}),
        subscriptionStatus: mapStripeStatus(subscription.status),
        currentPeriodEnd: currentPeriodEnd(subscription),
        updatedAt: new Date(),
      }).where(eq(users.id, user.id));
      await logBillingEvent(user.id, "subscription_updated", { tier, status: subscription.status });
      break;
    }

    case "customer.subscription.deleted": {
      const subscription = event.data.object as Stripe.Subscription;
      const customerId = typeof subscription.customer === "string" ? subscription.customer : subscription.customer.id;
      const user = await findUserByCustomerId(customerId);
      if (!user) break;

      await db.update(users).set({ tier: "free", subscriptionStatus: "canceled", updatedAt: new Date() }).where(eq(users.id, user.id));
      await logBillingEvent(user.id, "subscription_deleted", {});
      break;
    }

    case "invoice.payment_failed": {
      const invoice = event.data.object as Stripe.Invoice;
      const customerId = typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id;
      if (!customerId) break;
      const user = await findUserByCustomerId(customerId);
      if (!user) break;

      // Deliberately does NOT downgrade tier — Stripe retries the card
      // automatically, recovering via invoice.paid or eventually giving up
      // via customer.subscription.deleted.
      await db.update(users).set({ subscriptionStatus: "past_due", updatedAt: new Date() }).where(eq(users.id, user.id));
      await logBillingEvent(user.id, "payment_failed", {});
      break;
    }

    case "invoice.paid": {
      const invoice = event.data.object as Stripe.Invoice;
      const customerId = typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id;
      if (!customerId) break;
      const user = await findUserByCustomerId(customerId);
      if (user?.subscriptionStatus === "past_due") {
        await db.update(users).set({ subscriptionStatus: "active", updatedAt: new Date() }).where(eq(users.id, user.id));
        await logBillingEvent(user.id, "payment_recovered", {});
      }
      break;
    }

    default:
      break;
  }

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