// src/lib/billing/mcp-addon.ts — MCP add-on ("checklist_mcp") entitlement sync
// driven by the org's PLAN, not a standalone purchase. Product decision
// (2026-07): the MCP server is INCLUDED in every plan whose `mcpMonthly` quota
// is > 0 (all four plans today) — the per-request quota gate (assertMcpQuota,
// src/lib/mcp/quota.ts) already caps usage, so the add-on entitlement merely
// mirrors "is this org on a plan that grants MCP?". Presence of an
// `org_addons (orgId, "checklist_mcp")` row IS the entitlement, read by
// isAddonEnabled (src/lib/mcp/entitlements.ts).
//
// Wired from subscription-sync.ts on `customer.subscription.*` (upsert on a
// paying MCP plan, delete on cancellation / a non-MCP plan) and from
// backfillMcpAddons() for orgs already subscribed before this shipped. Mirrors
// seranking-addon.ts: same webhook Route Handler context, so `revalidateTag`
// with `{ expire: 0 }` (NOT `updateTag`, which throws outside a Server Action).
import "server-only";
import { and, eq } from "drizzle-orm";
import { revalidateTag } from "next/cache";
import type Stripe from "stripe";
import { subscription } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";
import { orgAddons } from "@/lib/db/domain-schema";
import { MCP_ADDON } from "@/lib/mcp/auth";
import { orgAddonsTag } from "@/lib/mcp/entitlements";
import { PLANS, type PlanName } from "./plans";

const SUBSCRIPTION_EVENTS = new Set([
  "customer.subscription.created",
  "customer.subscription.updated",
  "customer.subscription.deleted",
]);

// Same fail-safe allow-list as seranking-addon.ts: only "actively paying or
// grace period" statuses grant the entitlement; every other status — including
// unknown future ones Stripe may add — falls through to "no MCP" (delete).
const ACTIVE_STATUSES = new Set(["active", "trialing", "past_due"]);

/** Pure: does a plan (by name) grant the MCP add-on? True iff the plan exists
 *  AND its `mcpMonthly` quota is > 0. `null`/unknown → false (fail closed). */
export function mcpAddonEnabledForPlan(
  plan: PlanName | string | null | undefined,
): boolean {
  if (!plan) return false;
  const def = PLANS.find((p) => p.name === plan);
  return (def?.limits.mcpMonthly ?? 0) > 0;
}

/** Pure: the plan a Stripe subscription grants for MCP purposes, or `null` when
 *  the org must NOT have MCP (subscription deleted, or a non-paying status).
 *  Maps the subscription's item lookup_key — monthly OR annual — to a PlanName.
 *  Scans every item (not just index 0) in case of a future combined checkout. */
export function resolveEffectivePlanForMcp(
  sub: Stripe.Subscription,
  eventType: string,
): PlanName | null {
  if (eventType === "customer.subscription.deleted") return null;
  if (!ACTIVE_STATUSES.has(sub.status)) return null;
  const keys = new Set(
    (sub.items?.data ?? [])
      .map((i) => i.price?.lookup_key)
      .filter((k): k is string => Boolean(k)),
  );
  const def = PLANS.find(
    (p) => keys.has(p.lookupKey) || keys.has(p.annualLookupKey),
  );
  return def?.name ?? null;
}

/**
 * Reconcile the org's MCP add-on entitlement against a plan. Idempotent: the
 * upsert is `onConflictDoNothing` and deleting an absent row is a no-op, so
 * webhook retries and backfill re-runs are safe.
 *   - plan grants MCP → upsert `org_addons (orgId, "checklist_mcp")`
 *   - otherwise (null / non-MCP plan) → delete it
 * Then invalidate the per-org add-on cache tag so the MCP chokepoint picks the
 * change up on its very next request instead of waiting out the 30s TTL.
 */
export async function syncMcpAddonForPlan(
  orgId: string,
  plan: PlanName | string | null,
): Promise<void> {
  if (mcpAddonEnabledForPlan(plan)) {
    await db
      .insert(orgAddons)
      .values({ orgId, addon: MCP_ADDON })
      .onConflictDoNothing({ target: [orgAddons.orgId, orgAddons.addon] });
  } else {
    await db
      .delete(orgAddons)
      .where(and(eq(orgAddons.orgId, orgId), eq(orgAddons.addon, MCP_ADDON)));
  }
  revalidateTag(orgAddonsTag(orgId), { expire: 0 });
}

/**
 * Webhook entry point: reconcile the MCP add-on from a subscription lifecycle
 * event, using the org already resolved by subscription-sync (no second
 * Stripe-id → org query). Filters non-subscription events out. Delegates the
 * enable/disable decision to resolveEffectivePlanForMcp + syncMcpAddonForPlan.
 */
export async function syncMcpAddonFromEvent(
  event: Stripe.Event,
  orgId: string,
): Promise<void> {
  if (!SUBSCRIPTION_EVENTS.has(event.type)) return;
  const sub = event.data.object as Stripe.Subscription;
  await syncMcpAddonForPlan(orgId, resolveEffectivePlanForMcp(sub, event.type));
}

/**
 * One-shot reconciliation for orgs that subscribed to an MCP plan BEFORE this
 * plan-based activation shipped (the entitlement was previously only set by the
 * god-mode super-admin action, so pre-existing subscribers have none).
 *
 * ADDITIVE ONLY: it upserts the entitlement for every org currently on an
 * active plan that grants MCP, and NEVER deletes — so it cannot stomp a manual
 * god-mode grant on a non-plan org. Idempotent (`onConflictDoNothing`).
 * Deliberately skips `revalidateTag` (a plain script has no Next context; the
 * 30s isAddonEnabled TTL reconciles the cache on its own). Returns counters.
 * Run once from a super-admin action or a `bun` script.
 */
export async function backfillMcpAddons(): Promise<{
  scanned: number;
  granted: number;
}> {
  const rows = await db
    .select({
      orgId: subscription.referenceId,
      plan: subscription.plan,
      status: subscription.status,
    })
    .from(subscription);

  const orgsToGrant = new Set<string>();
  for (const r of rows) {
    if (!r.orgId) continue;
    if (
      r.status &&
      ACTIVE_STATUSES.has(r.status) &&
      mcpAddonEnabledForPlan(r.plan)
    ) {
      orgsToGrant.add(r.orgId);
    }
  }

  for (const orgId of orgsToGrant) {
    await db
      .insert(orgAddons)
      .values({ orgId, addon: MCP_ADDON })
      .onConflictDoNothing({ target: [orgAddons.orgId, orgAddons.addon] });
  }

  return { scanned: rows.length, granted: orgsToGrant.size };
}
