// src/lib/billing/reconcile.ts — heal subscription drift from Stripe.
// The local `subscription.plan` is normally synced by the
// `customer.subscription.updated` webhook. When a webhook is missed (account
// switch, transient failure, dev without forwarding), the DB shows a stale plan.
// This re-pulls the live Stripe subscription and writes the truth back — the
// same fields the webhook would. Best-effort and self-gating: a no-op when
// Stripe is disabled or the org has no Stripe-linked subscription.
import "server-only";
import { eq } from "drizzle-orm";
import Stripe from "stripe";
import { env } from "@/env";
import { subscription } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";
import { PLANS } from "./plans";
import { extractSubscriptionPeriod } from "./subscription-sync";

export type ReconcileResult = {
  /** True when a field actually changed (and was written). */
  changed: boolean;
  fromPlan: string;
  toPlan: string;
  status: string | null;
};

/** Map a Stripe price lookup_key → our plan name (matches the plugin's resolver). */
function planFromLookupKey(
  lookupKey: string | null | undefined,
): string | null {
  if (!lookupKey) return null;
  const p = PLANS.find(
    (p) => p.lookupKey === lookupKey || p.annualLookupKey === lookupKey,
  );
  return p?.name ?? null;
}

/**
 * Reconcile one org's subscription with Stripe. Returns null when there's
 * nothing to do (Stripe off, no Stripe-linked sub, or the price maps to no
 * known plan). Only writes when a field changed, so it's cheap to call on a
 * page load.
 */
export async function reconcileOrgSubscription(
  orgId: string,
): Promise<ReconcileResult | null> {
  if (!env.stripeEnabled || !env.STRIPE_SECRET_KEY) return null;

  const rows = await db
    .select({
      id: subscription.id,
      plan: subscription.plan,
      status: subscription.status,
      billingInterval: subscription.billingInterval,
      periodStart: subscription.periodStart,
      periodEnd: subscription.periodEnd,
      cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
      stripeSubscriptionId: subscription.stripeSubscriptionId,
    })
    .from(subscription)
    .where(eq(subscription.referenceId, orgId));

  // Prefer an active/trialing row; else any row carrying a Stripe id.
  const row =
    rows.find(
      (r) =>
        r.stripeSubscriptionId &&
        (r.status === "active" || r.status === "trialing"),
    ) ?? rows.find((r) => r.stripeSubscriptionId);
  if (!row?.stripeSubscriptionId) return null;

  const stripe = new Stripe(env.STRIPE_SECRET_KEY);
  const sub = await stripe.subscriptions.retrieve(row.stripeSubscriptionId);
  const item = sub.items.data[0];
  const plan = planFromLookupKey(item?.price.lookup_key);
  if (!plan) return null;

  const {
    periodStart,
    periodEnd,
    billingInterval: interval,
  } = extractSubscriptionPeriod(sub);

  const changed =
    plan !== row.plan ||
    sub.status !== row.status ||
    interval !== row.billingInterval ||
    sub.cancel_at_period_end !== row.cancelAtPeriodEnd ||
    // The renew date alone advancing (renewal with no plan/status change) must
    // still be written — otherwise "renews on <date>" stays stale.
    periodEnd?.getTime() !== row.periodEnd?.getTime() ||
    periodStart?.getTime() !== row.periodStart?.getTime();

  if (changed) {
    await db
      .update(subscription)
      .set({
        plan,
        status: sub.status,
        billingInterval: interval,
        periodStart,
        periodEnd,
        cancelAtPeriodEnd: sub.cancel_at_period_end,
      })
      .where(eq(subscription.id, row.id));
  }

  return { changed, fromPlan: row.plan, toPlan: plan, status: sub.status };
}
