import { NextRequest, NextResponse } from "next/server";
import type Stripe from "stripe";
import {
  activateProPlan,
  applyMonthlyCreditRenewal,
  downgradeToFreePlan,
  grantExtraCredits,
} from "@/lib/billing";
import { monthlyCreditsForPlan } from "@/lib/credits";
import { prisma } from "@/lib/db";
import { getNextMonthlyQuotaReset } from "@/lib/plans";
import { getStripe } from "@/lib/stripe";

export const runtime = "nodejs";

/** Stripe API 2025+: subscription lives on parent.subscription_details, not invoice.subscription. */
function subscriptionIdFromInvoice(invoice: Stripe.Invoice): string | null {
  const legacy = (invoice as Stripe.Invoice & {
    subscription?: string | Stripe.Subscription | null;
  }).subscription;
  if (typeof legacy === "string" && legacy) return legacy;
  if (legacy && typeof legacy === "object" && "id" in legacy && legacy.id) {
    return String(legacy.id);
  }

  const parent = (
    invoice as Stripe.Invoice & {
      parent?: {
        type?: string | null;
        subscription_details?: { subscription?: string | Stripe.Subscription | null } | null;
      } | null;
    }
  ).parent;

  const fromParent = parent?.subscription_details?.subscription;
  if (typeof fromParent === "string" && fromParent) return fromParent;
  if (fromParent && typeof fromParent === "object" && "id" in fromParent && fromParent.id) {
    return String(fromParent.id);
  }
  return null;
}

async function fulfillFreeTopUp(session: Stripe.Checkout.Session) {
  const purchaseId = session.metadata?.purchaseId;
  const userId = session.metadata?.userId;
  if (!purchaseId || !userId) {
    console.error("[stripe] free_top_up missing metadata", session.id);
    return;
  }

  const purchase = await prisma.creditPurchase.findUnique({ where: { id: purchaseId } });
  if (!purchase || purchase.userId !== userId) {
    throw new Error(`Purchase not found: ${purchaseId}`);
  }
  if (purchase.status === "COMPLETED") return;

  await grantExtraCredits(userId, purchase.credits);

  await prisma.creditPurchase.update({
    where: { id: purchaseId },
    data: {
      status: "COMPLETED",
      completedAt: new Date(),
      stripeSessionId: session.id,
      stripePaymentIntentId:
        typeof session.payment_intent === "string"
          ? session.payment_intent
          : session.payment_intent?.id ?? null,
    },
  });
}

async function fulfillProCheckout(session: Stripe.Checkout.Session) {
  const userId = session.metadata?.userId;
  if (!userId) {
    console.error("[stripe] pro_subscription missing userId", session.id);
    return;
  }

  const subscriptionId =
    typeof session.subscription === "string"
      ? session.subscription
      : session.subscription?.id ?? null;

  const customerId =
    typeof session.customer === "string" ? session.customer : session.customer?.id ?? null;

  await activateProPlan(userId, {
    stripeCustomerId: customerId,
    stripeSubscriptionId: subscriptionId,
  });
}

async function handleInvoicePaymentSucceeded(invoice: Stripe.Invoice) {
  const subscriptionId = subscriptionIdFromInvoice(invoice);
  if (!subscriptionId) {
    console.warn("[stripe] invoice.payment_succeeded without subscription id", invoice.id);
    return;
  }

  // Skip the first invoice if checkout already activated Pro (billing_reason subscription_create)
  const user = await prisma.user.findFirst({
    where: { stripeSubscriptionId: subscriptionId },
  });
  if (!user) {
    // Fallback: metadata on subscription via Stripe retrieve is optional; try customer
    const customerId =
      typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id;
    if (!customerId) return;
    const byCustomer = await prisma.user.findFirst({
      where: { stripeCustomerId: customerId },
    });
    if (!byCustomer) return;
    await applyMonthlyCreditRenewal(byCustomer.id, {
      plan: "PREMIUM",
      newMonthlyCredits: monthlyCreditsForPlan("PREMIUM"),
      quotaResetAt: getNextMonthlyQuotaReset(),
    });
    await prisma.user.update({
      where: { id: byCustomer.id },
      data: {
        stripeSubscriptionId: subscriptionId,
        subscribedAt: byCustomer.subscribedAt ?? new Date(),
      },
    });
    return;
  }

  // Renewal: rollover unused + new monthly, trim to MAX_CREDIT_CAP (30)
  await applyMonthlyCreditRenewal(user.id, {
    plan: "PREMIUM",
    newMonthlyCredits: monthlyCreditsForPlan("PREMIUM"),
    quotaResetAt: getNextMonthlyQuotaReset(),
  });
}

async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
  const user = await prisma.user.findFirst({
    where: { stripeSubscriptionId: subscription.id },
  });
  if (!user) return;
  await downgradeToFreePlan(user.id);
}

/** Stripe retries failed invoices; when status is unpaid/canceled, revoke Pro access. */
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
  const terminal = new Set(["canceled", "unpaid", "incomplete_expired"]);
  if (!terminal.has(subscription.status)) return;

  const user = await prisma.user.findFirst({
    where: { stripeSubscriptionId: subscription.id },
  });
  if (!user || user.plan !== "PREMIUM") return;
  await downgradeToFreePlan(user.id);
}

async function handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
  const subscriptionId = subscriptionIdFromInvoice(invoice);
  if (!subscriptionId) return;

  const user = await prisma.user.findFirst({
    where: {
      OR: [
        { stripeSubscriptionId: subscriptionId },
        ...(typeof invoice.customer === "string"
          ? [{ stripeCustomerId: invoice.customer }]
          : invoice.customer?.id
            ? [{ stripeCustomerId: invoice.customer.id }]
            : []),
      ],
    },
  });
  if (!user) return;

  // Soft signal only  Stripe Smart Retries continue. Do not downgrade on first failure.
  console.warn(
    "[stripe] invoice.payment_failed",
    invoice.id,
    "user",
    user.id,
    "attempt",
    invoice.attempt_count,
  );
}

export async function POST(req: NextRequest) {
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!webhookSecret) {
    return NextResponse.json({ error: "Webhook not configured" }, { status: 503 });
  }

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

  const rawBody = await req.text();
  let event: Stripe.Event;

  try {
    const stripe = getStripe();
    event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
  } catch (err) {
    const message = err instanceof Error ? err.message : "Invalid signature";
    return NextResponse.json({ error: message }, { status: 400 });
  }

  try {
    if (event.type === "checkout.session.completed") {
      const session = event.data.object as Stripe.Checkout.Session;
      if (session.payment_status === "paid" || session.status === "complete") {
        const type = session.metadata?.type;
        if (type === "pro_subscription" || session.mode === "subscription") {
          await fulfillProCheckout(session);
        } else if (type === "free_top_up") {
          await fulfillFreeTopUp(session);
        } else if (session.metadata?.purchaseId) {
          // Legacy PAYG purchases: grant metadata credits as extras
          const userId = session.metadata.userId;
          const credits = Number(session.metadata.credits);
          const purchaseId = session.metadata.purchaseId;
          if (userId && purchaseId && Number.isFinite(credits) && credits > 0) {
            const purchase = await prisma.creditPurchase.findUnique({
              where: { id: purchaseId },
            });
            if (purchase && purchase.status !== "COMPLETED") {
              await grantExtraCredits(userId, credits);
              await prisma.creditPurchase.update({
                where: { id: purchaseId },
                data: {
                  status: "COMPLETED",
                  completedAt: new Date(),
                  stripeSessionId: session.id,
                },
              });
            }
          }
        }
      }
    } else if (event.type === "invoice.payment_succeeded") {
      await handleInvoicePaymentSucceeded(event.data.object as Stripe.Invoice);
    } else if (event.type === "invoice.payment_failed") {
      await handleInvoicePaymentFailed(event.data.object as Stripe.Invoice);
    } else if (event.type === "customer.subscription.updated") {
      await handleSubscriptionUpdated(event.data.object as Stripe.Subscription);
    } else if (event.type === "customer.subscription.deleted") {
      await handleSubscriptionDeleted(event.data.object as Stripe.Subscription);
    }
  } catch (err) {
    console.error("[stripe] webhook handler error", event.type, event.id, err);
    return NextResponse.json({ error: "Handler failed" }, { status: 500 });
  }

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