// src/lib/mcp/keyword-tools.test.ts — get_keyword / get_keyword_brief MCP tool logic.
//
// Two layers, NO global module mocks (avoids the bun mock.module global-leak):
//   1. Pure resolver/projection — `resolveKeywordRow` + `toKeywordReference`
//      drive found-by-id / found-by-term / not-found without any DB.
//   2. Org-scoping (postgres) — the join keywords→sites must never surface
//      another org's keyword. Integration, run under the `test` script which
//      sets DB_TESTS=1 + the test DB. Skipped otherwise.
import { beforeEach, describe, expect, test } from "bun:test";
import { inArray } from "drizzle-orm";
import type { KeywordLookupRow, KeywordReference } from "./tools";
import { resolveKeywordRow, toKeywordReference } from "./tools";

const ROWS: KeywordLookupRow[] = [
  {
    id: "kw-1",
    siteId: "site-a",
    term: "diamant de laboratoire",
    lang: "fr",
    country: "FR",
    intent: ["commercial"],
    primarySource: "seranking",
    metrics: {
      seranking: {
        volume: 1200,
        kd: 34,
        cpc: 1.8,
        growthPct: 5,
        updatedAt: "2026-01-01",
      },
    },
    briefs: {
      FR: {
        source: "ytg",
        query: "diamant de laboratoire",
        language: "French (France)",
        lang: "fr",
        country: "FR",
        importedAt: "2026-01-01",
        primaryIntent: "commercial",
        objectives: ["acheter"],
        keySubjects: [],
        questions: ["comment choisir ?"],
        tone: [],
        style: [],
        valueProps: [],
        topTerms: ["diamant", "carat"],
        twoGrams: ["diamant synthétique"],
        threeGrams: [],
        entities: ["GIA"],
        rawBrief: "",
        rawGuide: "",
      },
    },
    serp: {
      FR: {
        source: "se-ranking",
        importedAt: "2026-01-01",
        features: [],
        competitors: [
          {
            position: 1,
            domain: "concurrent.fr",
            url: "https://concurrent.fr/a",
            backlinks: 10,
            refDomains: 5,
            pageAuthority: 40,
            searchTraffic: 100,
            urlKeywords: 20,
            title: null,
            metaDescription: null,
            lang: null,
            images: null,
            imagesWithoutAlt: null,
            imageAlts: null,
            missingAltSrcs: null,
            hasJsonLd: null,
            jsonLdTypes: null,
            crawledAt: null,
          },
        ],
      },
    },
  },
  {
    id: "kw-2",
    siteId: "site-a",
    term: "bague or 18 carats",
    lang: "fr",
    country: "FR",
    intent: ["transactional"],
    primarySource: "mangools",
    metrics: {}, // no metrics for the primary source
    briefs: {}, // no brief
    serp: {}, // no serp
  },
];

describe("resolveKeywordRow (pure)", () => {
  test("found by id", () => {
    expect(resolveKeywordRow(ROWS, { id: "kw-2" })?.id).toBe("kw-2");
  });

  test("found by term", () => {
    expect(
      resolveKeywordRow(ROWS, { term: "diamant de laboratoire" })?.id,
    ).toBe("kw-1");
  });

  test("id wins over term when both given", () => {
    expect(
      resolveKeywordRow(ROWS, { id: "kw-1", term: "bague or 18 carats" })?.id,
    ).toBe("kw-1");
  });

  test("not found → null (unknown id)", () => {
    expect(resolveKeywordRow(ROWS, { id: "nope" })).toBeNull();
  });

  test("not found → null (unknown term)", () => {
    expect(resolveKeywordRow(ROWS, { term: "inexistant" })).toBeNull();
  });

  test("empty lookup → null", () => {
    expect(resolveKeywordRow(ROWS, {})).toBeNull();
  });
});

describe("toKeywordReference (pure)", () => {
  test("projects primary-source metrics + country SERP top-10", () => {
    const ref: KeywordReference = toKeywordReference(ROWS[0]);
    expect(ref.term).toBe("diamant de laboratoire");
    expect(ref.country).toBe("FR");
    expect(ref.intent).toEqual(["commercial"]);
    expect(ref.primarySource).toBe("seranking");
    expect(ref.metrics?.volume).toBe(1200);
    expect(ref.metrics?.kd).toBe(34);
    expect(ref.serp).toHaveLength(1);
    expect(ref.serp[0].domain).toBe("concurrent.fr");
  });

  test("metrics null when the primary source has none; serp [] when none imported", () => {
    const ref = toKeywordReference(ROWS[1]);
    expect(ref.metrics).toBeNull();
    expect(ref.serp).toEqual([]);
  });
});

// ── Org-scoping (postgres) ───────────────────────────────────────────────────
const RUN_DB = process.env.DB_TESTS === "1";
const ORG = "kw-org";
const OTHER = "kw-other-org";

describe.skipIf(!RUN_DB)("get_keyword/get_keyword_brief org-scoping", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let organization: typeof import("@/lib/db/auth-schema").organization;
  let getKeywordReference: typeof import("./tools").getKeywordReference;
  let getKeywordBrief: typeof import("./tools").getKeywordBrief;

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    ({ organization } = await import("@/lib/db/auth-schema"));
    ({ getKeywordReference, getKeywordBrief } = await import("./tools"));
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    await assertTestDb();
    await db.delete(schema.keywords);
    await db.delete(schema.sites);
    await db.delete(organization).where(inArray(organization.id, [ORG, OTHER]));
    await db.insert(organization).values([
      { id: ORG, name: "Org", slug: ORG, createdAt: new Date() },
      { id: OTHER, name: "Other", slug: OTHER, createdAt: new Date() },
    ]);
    await db.insert(schema.sites).values([
      {
        id: "kw-site-mine",
        orgId: ORG,
        ownerUserId: "seed-owner",
        name: "Mine",
        url: "https://mine.test",
        primaryLanguage: "fr",
        languages: [],
        targetCountries: ["FR"],
      },
      {
        id: "kw-site-foreign",
        orgId: OTHER,
        ownerUserId: "seed-owner",
        name: "Foreign",
        url: "https://foreign.test",
        primaryLanguage: "fr",
        languages: [],
        targetCountries: ["FR"],
      },
    ]);
    await db.insert(schema.keywords).values([
      {
        id: "kw-mine",
        siteId: "kw-site-mine",
        term: "mon mot-clé",
        lang: "fr",
        country: "FR",
        status: "active",
        intent: ["commercial"],
        stages: ["planning"],
        primarySource: "seranking",
        sources: ["seranking"],
        metrics: {
          seranking: {
            volume: 900,
            kd: 20,
            cpc: 1.1,
            growthPct: null,
            updatedAt: "2026-01-01",
          },
        },
        briefs: ROWS[0].briefs,
        serp: ROWS[0].serp,
      },
      {
        // Same term as the in-org keyword, but in a FOREIGN org's site.
        id: "kw-foreign",
        siteId: "kw-site-foreign",
        term: "mon mot-clé",
        lang: "fr",
        country: "FR",
        status: "active",
        intent: ["commercial"],
        stages: ["planning"],
        primarySource: "seranking",
        sources: ["seranking"],
        metrics: {},
        briefs: {},
        serp: {},
      },
    ]);
  });

  test("getKeywordReference resolves an in-org keyword by id", async () => {
    const ref = await getKeywordReference(ORG, { id: "kw-mine" });
    expect(ref?.term).toBe("mon mot-clé");
    expect(ref?.metrics?.volume).toBe(900);
    expect(ref?.serp[0].domain).toBe("concurrent.fr");
  });

  test("getKeywordReference resolves an in-org keyword by term", async () => {
    const ref = await getKeywordReference(ORG, { term: "mon mot-clé" });
    expect(ref?.id).toBe("kw-mine");
  });

  test("getKeywordReference cannot reach another org's keyword by id", async () => {
    expect(await getKeywordReference(ORG, { id: "kw-foreign" })).toBeNull();
  });

  test("getKeywordReference scoped to org: by-term never returns the foreign row", async () => {
    // OTHER org asks by term → only its own (foreign) row; ORG asks by term →
    // only kw-mine. Neither crosses.
    const mine = await getKeywordReference(ORG, { term: "mon mot-clé" });
    const other = await getKeywordReference(OTHER, { term: "mon mot-clé" });
    expect(mine?.id).toBe("kw-mine");
    expect(other?.id).toBe("kw-foreign");
  });

  test("getKeywordReference returns null for an unknown id", async () => {
    expect(await getKeywordReference(ORG, { id: "does-not-exist" })).toBeNull();
  });

  test("getKeywordBrief returns the country brief for an in-org keyword", async () => {
    const res = await getKeywordBrief(ORG, { id: "kw-mine" });
    expect(res.found).toBe(true);
    if (res.found) expect(res.brief.topTerms).toContain("diamant");
  });

  test("getKeywordBrief reports no-brief when the keyword has none", async () => {
    const res = await getKeywordBrief(OTHER, { id: "kw-foreign" });
    expect(res).toEqual({
      found: false,
      reason: "no-brief",
      term: "mon mot-clé",
    });
  });

  test("getKeywordBrief cannot reach another org's keyword (no-keyword)", async () => {
    const res = await getKeywordBrief(ORG, { id: "kw-foreign" });
    expect(res).toEqual({ found: false, reason: "no-keyword" });
  });
});
