/**
 * Webhook Verification Suite
 *
 * Pure decision logic for the Stripe webhook policy: event-type allowlist,
 * payment-status gating, the amount cross-check against the REAL shared price
 * catalog, plan validation and expiry derivation. Signature verification
 * itself requires live Stripe secrets (HMAC of the raw body), so it is only
 * ever exercised end-to-end by the running app — everything that decides
 * whether a license may be granted is covered here, no database needed.
 */

import { describe, test, expect } from "./runner";
import {
  ALLOWED_EVENT_TYPES,
  isAllowedEventType,
  isPaymentConfirmed,
  sessionModeMatchesPlan,
  subscriptionExpiresAt,
  verifyPaidAmount,
} from "../../src/lib/billing/webhookPolicy";
import { getPlanConfig, isPlanId, resolvePlan } from "../../src/lib/billing/pricing";

describe("WebhookVerification — event type allowlist", () => {
  test("all handled event types are allowed (including the fixed invoice.payment_succeeded)", () => {
    expect(isAllowedEventType("checkout.session.completed")).toBe(true);
    expect(isAllowedEventType("checkout.session.expired")).toBe(true);
    expect(isAllowedEventType("invoice.payment_failed")).toBe(true);
    expect(isAllowedEventType("invoice.payment_succeeded")).toBe(true);
    expect(isAllowedEventType("customer.subscription.updated")).toBe(true);
    expect(isAllowedEventType("customer.subscription.deleted")).toBe(true);
  });

  test("unhandled and unknown event types are rejected", () => {
    expect(isAllowedEventType("charge.succeeded")).toBe(false);
    expect(isAllowedEventType("payment_intent.succeeded")).toBe(false);
    expect(isAllowedEventType("customer.created")).toBe(false);
    expect(isAllowedEventType("invoice.created")).toBe(false);
    expect(isAllowedEventType("")).toBe(false);
  });

  test("the allowlist is not accidentally empty", () => {
    expect(ALLOWED_EVENT_TYPES.size).toBeGreaterThan(0);
    // Every allowlisted type is a real Stripe event name pattern, never a wildcard.
    expect(ALLOWED_EVENT_TYPES.has("*")).toBe(false);
  });
});

describe("WebhookVerification — payment confirmation gating", () => {
  test("payment-mode session is confirmed only when payment_status is paid", () => {
    expect(isPaymentConfirmed({ mode: "payment", payment_status: "paid" }, null)).toBe(true);
    expect(isPaymentConfirmed({ mode: "payment", payment_status: "unpaid" }, null)).toBe(false);
    expect(isPaymentConfirmed({ mode: "payment", payment_status: "no_payment_required" }, null)).toBe(
      false
    );
    expect(isPaymentConfirmed({ mode: "payment", payment_status: null }, null)).toBe(false);
  });

  test("subscription is confirmed while active or trialing", () => {
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "active" })).toBe(true);
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "trialing" })).toBe(true);
  });

  test("subscription is rejected when canceled, unpaid, past_due or missing", () => {
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "canceled" })).toBe(false);
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "unpaid" })).toBe(false);
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "past_due" })).toBe(false);
    expect(isPaymentConfirmed({ mode: "subscription" }, { status: "incomplete" })).toBe(false);
    expect(isPaymentConfirmed({ mode: "subscription" }, null)).toBe(false);
    expect(isPaymentConfirmed({ mode: "subscription" }, undefined)).toBe(false);
  });

  test("unknown or missing session modes are never confirmed", () => {
    expect(isPaymentConfirmed({ mode: null, payment_status: "paid" }, null)).toBe(false);
    expect(isPaymentConfirmed({ mode: "setup", payment_status: "paid" }, null)).toBe(false);
    expect(isPaymentConfirmed({}, null)).toBe(false);
  });
});

describe("WebhookVerification — amount cross-check against the real catalog", () => {
  test("lifetime amount matching the catalog exactly is accepted", () => {
    const session = {
      mode: "payment",
      payment_status: "paid",
      amount_total: getPlanConfig("lifetime").unitAmountMinor,
    };
    expect(verifyPaidAmount(session, "lifetime")).toBe(true);
    expect(verifyPaidAmount(session, getPlanConfig("lifetime").id)).toBe(true);
    // The catalog really is the source: 5999 minor units for lifetime.
    expect(getPlanConfig("lifetime").unitAmountMinor).toBe(5999);
  });

  test("one cent less than the catalog amount is rejected", () => {
    expect(
      verifyPaidAmount(
        { mode: "payment", payment_status: "paid", amount_total: getPlanConfig("lifetime").unitAmountMinor - 1 },
        "lifetime"
      )
    ).toBe(false);
  });

  test("a missing amount_total is rejected for one-time payments", () => {
    expect(
      verifyPaidAmount({ mode: "payment", payment_status: "paid", amount_total: null }, "lifetime")
    ).toBe(false);
  });

  test("subscription sessions are not amount-checked (trial amount may be 0)", () => {
    expect(verifyPaidAmount({ mode: "subscription", amount_total: 0 }, "weekly")).toBe(true);
    expect(verifyPaidAmount({ mode: "subscription", amount_total: 0 }, "annual")).toBe(true);
    expect(verifyPaidAmount({ mode: "subscription", amount_total: 1 }, "weekly")).toBe(true);
  });
});

describe("WebhookVerification — plan validation", () => {
  test("known plan ids validate", () => {
    expect(isPlanId("weekly")).toBe(true);
    expect(isPlanId("annual")).toBe(true);
    expect(isPlanId("lifetime")).toBe(true);
  });

  test("unknown and missing plans are rejected by isPlanId", () => {
    expect(isPlanId("enterprise")).toBe(false);
    expect(isPlanId("free")).toBe(false);
    expect(isPlanId("")).toBe(false);
    expect(isPlanId(null)).toBe(false);
    expect(isPlanId(undefined)).toBe(false);
  });

  test("resolvePlan falls back to annual for unknown or absent values", () => {
    expect(resolvePlan("lifetime")).toBe("lifetime");
    expect(resolvePlan("weekly")).toBe("weekly");
    expect(resolvePlan("annual")).toBe("annual");
    expect(resolvePlan("bogus")).toBe("annual");
    expect(resolvePlan(null)).toBe("annual");
    expect(resolvePlan(undefined)).toBe("annual");
    expect(resolvePlan("")).toBe("annual");
  });

  test("the session mode must match the plan's billing mode", () => {
    expect(sessionModeMatchesPlan("payment", "lifetime")).toBe(true);
    expect(sessionModeMatchesPlan("subscription", "weekly")).toBe(true);
    expect(sessionModeMatchesPlan("subscription", "annual")).toBe(true);
    expect(sessionModeMatchesPlan("subscription", "lifetime")).toBe(false);
    expect(sessionModeMatchesPlan("payment", "annual")).toBe(false);
  });
});

describe("WebhookVerification — expiry derivation from the subscription", () => {
  test("expiresAt comes from current_period_end, never Date.now()", () => {
    const periodEnd = 1_700_000_000; // fixed point in the past — must not shift
    const expires = subscriptionExpiresAt({ status: "trialing", current_period_end: periodEnd });
    expect(expires?.getTime()).toBe(periodEnd * 1000);
    // A fixed period end must yield a fixed expiry regardless of when the test runs.
    expect(Date.now()).toBeGreaterThan(periodEnd * 1000);
  });

  test("missing period end or missing subscription yields no expiry", () => {
    expect(subscriptionExpiresAt({ status: "active", current_period_end: null })).toBeNull();
    expect(subscriptionExpiresAt({ status: "active" })).toBeNull();
    expect(subscriptionExpiresAt(null)).toBeNull();
    expect(subscriptionExpiresAt(undefined)).toBeNull();
  });
});

describe("WebhookVerification — catalog contract used by the webhook", () => {
  test("lifetime is a one-time payment; weekly and annual are subscriptions", () => {
    expect(getPlanConfig("lifetime").mode).toBe("payment");
    expect(getPlanConfig("weekly").mode).toBe("subscription");
    expect(getPlanConfig("annual").mode).toBe("subscription");
  });

  test("catalog amounts match the pricing guide (EUR minor units)", () => {
    expect(getPlanConfig("lifetime").unitAmountMinor).toBe(5999);
    expect(getPlanConfig("weekly").unitAmountMinor).toBe(499);
    expect(getPlanConfig("annual").unitAmountMinor).toBe(3999);
  });
});
