// src/lib/billing/plans.test.ts — pure invariants on the plan grid (no DB).
import { describe, expect, test } from "bun:test";
import {
  getPlan,
  PLANS,
  SEAT_ANNUAL_USD,
  SEAT_MONTHLY_USD,
  TRIAL_DAYS,
} from "./plans";

describe("billing plans", () => {
  test("exactly 4 plans, in tier order", () => {
    expect(PLANS.map((p) => p.name)).toEqual([
      "solo",
      "starter",
      "pro",
      "agency",
    ]);
  });

  test("lookup keys are unique across plans (8 plan keys + 2 seat keys)", () => {
    const planKeys = PLANS.flatMap((p) => [p.lookupKey, p.annualLookupKey]);
    expect(planKeys).toHaveLength(8);
    expect(new Set(planKeys).size).toBe(8);

    const seatKeys = new Set(
      PLANS.flatMap((p) =>
        p.seatLookupKey && p.seatAnnualLookupKey
          ? [p.seatLookupKey, p.seatAnnualLookupKey]
          : [],
      ),
    );
    expect([...seatKeys].sort()).toEqual(["seat_annual", "seat_monthly"]);
    // Seat keys never collide with plan keys.
    for (const k of seatKeys) expect(planKeys).not.toContain(k);
  });

  test("annual price gives at least the advertised 20% discount", () => {
    // The grid rounds annual prices DOWN to marketing prices (e.g. pro $129 →
    // $99/mo billed yearly = -23%), so annual ≤ monthly×12×0.8 (+1¢ slack) —
    // a strict equality at exactly -20% does not hold for this grid.
    for (const p of PLANS) {
      expect(p.annualUsd).toBeLessThanOrEqual(
        Math.round(p.monthlyUsd * 12 * 0.8) + 1,
      );
      expect(p.annualUsd).toBeGreaterThan(0);
    }
    // Seat pricing IS exactly -20%.
    expect(SEAT_ANNUAL_USD).toBe(Math.round(SEAT_MONTHLY_USD * 12 * 0.8));
  });

  test("prices match the pricing grid exactly (cents)", () => {
    expect(getPlan("solo").monthlyUsd).toBe(999);
    expect(getPlan("solo").annualUsd).toBe(9588);
    expect(getPlan("starter").monthlyUsd).toBe(4900);
    expect(getPlan("starter").annualUsd).toBe(46800);
    expect(getPlan("pro").monthlyUsd).toBe(12900);
    expect(getPlan("pro").annualUsd).toBe(118800);
    expect(getPlan("agency").monthlyUsd).toBe(29900);
    expect(getPlan("agency").annualUsd).toBe(286800);
  });

  test("limits match the grid exactly", () => {
    expect(getPlan("solo").limits).toEqual({
      keywords: 100,
      projects: 1,
      seats: 1,
      integrations: 1,
      backlinkRows: 10_000,
      mcpMonthly: 1_000,
    });
    expect(getPlan("starter").limits).toEqual({
      keywords: 500,
      projects: 5,
      seats: 1,
      integrations: 2,
      backlinkRows: 100_000,
      mcpMonthly: 10_000,
    });
    expect(getPlan("pro").limits).toEqual({
      keywords: 1_500,
      projects: 15,
      seats: 3,
      integrations: 5,
      backlinkRows: 500_000,
      mcpMonthly: 100_000,
    });
    expect(getPlan("agency").limits).toEqual({
      keywords: 5_000,
      projects: 40,
      seats: 10,
      integrations: -1,
      backlinkRows: 2_000_000,
      mcpMonthly: 500_000,
    });
  });

  test("only pro and agency have seat keys", () => {
    for (const p of PLANS) {
      const hasSeats = p.name === "pro" || p.name === "agency";
      expect(Boolean(p.seatLookupKey)).toBe(hasSeats);
      expect(Boolean(p.seatAnnualLookupKey)).toBe(hasSeats);
    }
  });

  test("constants", () => {
    expect(TRIAL_DAYS).toBe(14);
    expect(SEAT_MONTHLY_USD).toBe(1900);
    expect(SEAT_ANNUAL_USD).toBe(18240);
  });

  test("getPlan throws on unknown plan", () => {
    expect(() => getPlan("free" as Parameters<typeof getPlan>[0])).toThrow(
      "Unknown plan: free",
    );
  });
});
