import { describe, it, expect } from "vitest";
// Subject lives in src/shared/entitlements.ts; test co-located here per the
// issue's file plan (services/).
import {
  isEntitled,
  requiredTierFor,
  ENTITLEMENTS,
  type FeatureKey,
} from "../shared/entitlements";
import type { Edition } from "./licenseService";
import fr from "../i18n/locales/fr.json";
import en from "../i18n/locales/en.json";

const EDITIONS: Edition[] = ["free", "base", "premium"];
const FEATURES = Object.keys(ENTITLEMENTS) as FeatureKey[];

describe("isEntitled — matrix", () => {
  it("denies every feature in Free (fail-closed)", () => {
    FEATURES.forEach((f) => {
      expect(isEntitled(f, "free", [])).toBe(false);
    });
  });

  it("Base unlocks budget/adjustments/reports-advanced/multi-profile, not balance", () => {
    expect(isEntitled("budget", "base", [])).toBe(true);
    expect(isEntitled("adjustments", "base", [])).toBe(true);
    expect(isEntitled("reports-advanced", "base", [])).toBe(true);
    expect(isEntitled("multi-profile", "base", [])).toBe(true);
    expect(isEntitled("balance", "base", [])).toBe(false);
  });

  it("Premium unlocks everything including balance", () => {
    FEATURES.forEach((f) => {
      expect(isEntitled(f, "premium", [])).toBe(true);
    });
  });

  it("matches the ENTITLEMENTS table for every feature × edition", () => {
    FEATURES.forEach((f) => {
      EDITIONS.forEach((ed) => {
        const expected = ed !== "free" && ENTITLEMENTS[f].includes(ed);
        expect(isEntitled(f, ed, [])).toBe(expected);
      });
    });
  });
});

describe("isEntitled — features[] override", () => {
  it("grants a higher-tier feature when the signed override lists it", () => {
    // balance is Premium-only; a Base license carrying balance in features[] gets it.
    expect(isEntitled("balance", "base", ["balance"])).toBe(true);
  });

  it("IGNORES the override when edition is free (CWE-863)", () => {
    // A copied key downgrades to free (machine-binding) but still carries its
    // signed features[]. Those must not be re-granted.
    expect(isEntitled("balance", "free", ["balance"])).toBe(false);
    expect(isEntitled("budget", "free", ["budget"])).toBe(false);
  });

  it("does not grant unrelated features via the override", () => {
    expect(isEntitled("balance", "base", ["budget"])).toBe(false);
  });
});

describe("isEntitled — unknown feature", () => {
  it("denies an unknown feature key (deny-all)", () => {
    expect(isEntitled("web-sync" as FeatureKey, "premium", [])).toBe(false);
    expect(isEntitled("web-sync" as FeatureKey, "base", [])).toBe(false);
  });

  it("still honours a signed override for an unknown key when non-free", () => {
    expect(isEntitled("web-sync" as FeatureKey, "premium", ["web-sync"])).toBe(true);
  });

  it("keeps an unknown feature denied in Free even with an override", () => {
    expect(isEntitled("web-sync" as FeatureKey, "free", ["web-sync"])).toBe(false);
  });
});

describe("requiredTierFor — upsell tier derivation", () => {
  it("maps every Base-tier feature to base, and balance to premium", () => {
    expect(requiredTierFor("budget")).toBe("base");
    expect(requiredTierFor("adjustments")).toBe("base");
    expect(requiredTierFor("reports-advanced")).toBe("base");
    expect(requiredTierFor("multi-profile")).toBe("base");
    expect(requiredTierFor("balance")).toBe("premium");
  });

  it("returns the MINIMUM tier satisfying isEntitled, never free", () => {
    FEATURES.forEach((f) => {
      const tier = requiredTierFor(f);
      expect(tier).not.toBe("free");
      // The returned tier does unlock the feature…
      expect(isEntitled(f, tier, [])).toBe(true);
      // …and is minimal: premium only when base does not unlock it.
      if (tier === "premium") {
        expect(isEntitled(f, "base", [])).toBe(false);
      }
    });
  });
});

describe("upsell i18n coverage (fr + en)", () => {
  // UpsellGate renders t(`upsell.features.${feature}`) — every FeatureKey must
  // resolve in BOTH locales or a locked screen shows a raw key to the user.
  // Structural sub-type only: full typeof-identity between the two JSON files
  // is not this test's concern.
  interface UpsellMessages {
    nav: { locked: string };
    license: { editions: { base: string; premium: string } };
    upsell: {
      title: string;
      features: Record<string, string>;
      ctaGet: string;
      ctaGetSoon: string;
      ctaHaveKey: string;
    };
  }
  const locales: Record<string, UpsellMessages> = { fr, en };

  it("has a non-empty upsell description for every FeatureKey in both locales", () => {
    Object.entries(locales).forEach(([lng, messages]) => {
      FEATURES.forEach((f) => {
        expect(
          messages.upsell.features[f],
          `missing upsell.features.${f} in ${lng}`,
        ).toBeTruthy();
      });
    });
  });

  it("has no stale upsell.features key outside the ENTITLEMENTS matrix", () => {
    Object.entries(locales).forEach(([lng, messages]) => {
      Object.keys(messages.upsell.features).forEach((k) => {
        expect(FEATURES, `stale upsell.features.${k} in ${lng}`).toContain(k);
      });
    });
  });

  it("exposes the title, CTA keys and nav.locked in both locales", () => {
    Object.values(locales).forEach((messages) => {
      expect(messages.upsell.title).toBeTruthy();
      expect(messages.upsell.ctaGet).toBeTruthy();
      expect(messages.upsell.ctaGetSoon).toBeTruthy();
      expect(messages.upsell.ctaHaveKey).toBeTruthy();
      expect(messages.nav.locked).toBeTruthy();
    });
  });

  it("keeps the license.editions.<tier> label keys required by the CTA", () => {
    Object.values(locales).forEach((messages) => {
      expect(messages.license.editions.base).toBeTruthy();
      expect(messages.license.editions.premium).toBeTruthy();
    });
  });
});
