// src/lib/billing/status.ts — org billing status (app-level trial + Stripe sub).
// Single source of truth for "is this org allowed in?" — consumed by layouts,
// server actions and the blocking page.
import "server-only";
import { eq } from "drizzle-orm";
import { unstable_cache } from "next/cache";
import { organization, subscription } from "@/lib/db/auth-schema";
import { db } from "@/lib/db/client";

export type BillingStatus = "active" | "trialing" | "expired";

/** Subscription statuses that grant full access (past_due keeps access during dunning). */
export const ACTIVE_SUB_STATUSES = new Set(["active", "trialing", "past_due"]);

// Pure — unit-testable.
export function computeBillingStatus(
  input: { trialEndsAt: Date | null; subscriptionStatus: string | null },
  now: Date,
): BillingStatus {
  if (
    input.subscriptionStatus !== null &&
    ACTIVE_SUB_STATUSES.has(input.subscriptionStatus)
  ) {
    return "active";
  }
  if (input.trialEndsAt && input.trialEndsAt.getTime() > now.getTime()) {
    return "trialing";
  }
  return "expired";
}

/**
 * Pick the subscription row that best represents the org's standing:
 * prefer any row with an access-granting status, otherwise the most recent
 * one (periodStart desc, nulls last — deterministic on the nullable column).
 */
export function pickRelevantSubscription<
  T extends { status: string | null; periodStart: Date | null },
>(subs: T[]): T | undefined {
  const granting = subs.find(
    (s) => s.status !== null && ACTIVE_SUB_STATUSES.has(s.status),
  );
  if (granting) return granting;
  return [...subs].sort(
    (a, b) => (b.periodStart?.getTime() ?? 0) - (a.periodStart?.getTime() ?? 0),
  )[0];
}

export interface OrgBillingInfo {
  status: BillingStatus;
  /** Plan name of the relevant subscription (null when no subscription). */
  plan: string | null;
}

/** Cache tag for `getOrgBillingInfo(orgId)` — invalidated wherever the org's
 *  trial stamp or subscription status/plan changes (Stripe webhook, admin
 *  God-Mode trial/plan actions). See revalidateBillingCache() below. */
export function orgBillingTag(orgId: string): string {
  return `org-billing-${orgId}`;
}

async function fetchOrgBillingInfo(orgId: string): Promise<OrgBillingInfo> {
  const [org] = await db
    .select({ trialEndsAt: organization.trialEndsAt })
    .from(organization)
    .where(eq(organization.id, orgId))
    .limit(1);
  const subs = await db
    .select({
      status: subscription.status,
      periodStart: subscription.periodStart,
      plan: subscription.plan,
    })
    .from(subscription)
    .where(eq(subscription.referenceId, orgId));
  const sub = pickRelevantSubscription(subs);
  const status = computeBillingStatus(
    {
      trialEndsAt: org?.trialEndsAt ?? null,
      subscriptionStatus: sub?.status ?? null,
    },
    new Date(),
  );
  return { status, plan: sub?.plan ?? null };
}

// Fresh-ish DB read — bypasses Better Auth's 5-min session cookieCache: a user
// who just subscribed must unlock
// immediately, and a trial that just lapsed must lock immediately. Wrapped in
// `unstable_cache` (30s TTL, per-org tag) because this reads TWO tables
// (organization + subscription) on every quota/entitlement chokepoint call
// (QW2-A) — status/plan only change on rare events (checkout, trial
// extension), so a 30s window is an acceptable trade for cutting the request
// count. Also exposes the plan for quota resolution (quotas.ts).
export async function getOrgBillingInfo(
  orgId: string,
): Promise<OrgBillingInfo> {
  return unstable_cache(fetchOrgBillingInfo, ["org-billing-info", orgId], {
    tags: [orgBillingTag(orgId)],
    revalidate: 30,
  })(orgId);
}

export async function getOrgBillingStatus(
  orgId: string,
): Promise<BillingStatus> {
  return (await getOrgBillingInfo(orgId)).status;
}
