// src/lib/keywords/prompt/compute-readiness.test.ts
import { describe, expect, test } from "bun:test";
import type { StoredSemanticProfile } from "@/lib/semantic/profile-types";
import { computeReadiness } from "./compute-readiness";
import type { KeywordPromptBundle, ReadyState } from "./types";

/** Minimal-but-valid fixture — same shape used by
 *  `src/lib/semantic/actions.test.ts`. Two terms, one country, ready state. */
const semanticProfile: StoredSemanticProfile = {
  intent: { curve: "transactionnelle", mode: "inferred", mixedIntent: false },
  coverage: { analyzed: 8, total: 10, excludedByUser: [], missing: [], flags: [] },
  terms: [
    {
      term: "bague",
      display: "bague",
      pagesWithTerm: 6,
      score: 0.8,
      medianWeighted: 1.2,
      isOutlier: false,
      zones: {
        title: 1,
        h1: 1,
        h2h3: 0,
        slug: 0,
        alt: 0,
        anchor: 0,
        meta: 0,
        body: 4,
      },
      top3: { min: 1, median: 2, max: 3 },
    },
    {
      term: "diamant",
      display: "diamant",
      pagesWithTerm: 5,
      score: 0.6,
      medianWeighted: 0.9,
      isOutlier: false,
      zones: {
        title: 1,
        h1: 0,
        h2h3: 1,
        slug: 0,
        alt: 0,
        anchor: 0,
        meta: 0,
        body: 3,
      },
      top3: { min: 0, median: 1, max: 2 },
    },
  ],
  ngrams: { bigrams: [], trigrams: [] },
  questions: [],
  structure: {
    wordCount: { p25: 800, median: 1200, p75: 1600 },
    h2Count: 5,
    lists: 2,
    tables: 0,
    schemaTypes: {},
    archetypes: {},
    top3VsTop10: {
      wordCountTop3: 1400,
      wordCountTop10: 1200,
      h2CountTop3: 6,
      h2CountTop10: 5,
    },
  },
  guide: {
    terms: [],
    ngrams: [],
    questions: [],
    objectives: { wordCount: { p25: 800, median: 1200, p75: 1600 }, h2Count: 5, lists: 2, tables: 0 },
  },
  gap: null,
  analyzedAt: "2026-07-21T00:00:00.000Z",
  jobId: "job-1",
  exports: {
    pagesKey: "serp-semantics-results/site-a/k1/FR/job-1/pages.jsonl.gz",
    termsKey: "serp-semantics-results/site-a/k1/FR/job-1/terms.jsonl.gz",
  },
};

function bundle(over: Partial<KeywordPromptBundle> = {}): KeywordPromptBundle {
  return {
    id: "k1",
    term: "bague diamant",
    country: "FR",
    lang: "fr",
    intent: ["commercial"],
    primarySource: "seranking",
    sources: ["seranking"],
    vol: 4400,
    kd: 38,
    cpc: 1.25,
    competitors: [],
    serpFeatures: [],
    brief: null,
    hasGsc: true,
    gscConnected: true,
    // No serp-semantics analysis by default — most keywords don't have one yet.
    semanticProfile: null,
    readiness: { serp: "ready", onpage: "ready", brief: "ready" },
    ...over,
  };
}

describe("computeReadiness", () => {
  test("emits 6 items in the documented order", () => {
    const { items } = computeReadiness(bundle());
    expect(items.map((i) => i.key)).toEqual([
      "metrics",
      "gsc",
      "serp",
      "onpage",
      "brief",
      "semantic",
    ]);
  });

  test("all ready (incl. semantic profile) → pct 100 with detailed metrics line", () => {
    const r = computeReadiness(bundle({ semanticProfile }));
    expect(r.pct).toBe(100);
    expect(r.items[0].state).toBe("ready");
    expect(r.items[0].detail).toBe("Vol 4400 · KD 38");
    expect(r.items[5].state).toBe("ready");
  });

  test("metrics missing → 'Aucune métrique' and 67 pct (4/6 ready)", () => {
    const r = computeReadiness(bundle({ vol: null, kd: null, cpc: null }));
    expect(r.items[0].state).toBe("missing");
    expect(r.items[0].detail).toBe("Aucune métrique");
    expect(r.pct).toBe(67); // gsc + serp + onpage + brief ready, metrics + semantic missing
  });

  test("org not connected → 'Absent' (missing) and 67 pct (4/6 ready)", () => {
    const r = computeReadiness(bundle({ hasGsc: false, gscConnected: false }));
    expect(r.items[1].state).toBe("missing");
    expect(r.items[1].detail).toBe("Absent");
    expect(r.pct).toBe(67);
  });

  test("org connected but no positions for this keyword → progress, not missing", () => {
    const r = computeReadiness(bundle({ hasGsc: false, gscConnected: true }));
    expect(r.items[1].state).toBe("progress");
    expect(r.items[1].detail).toBe("Aucune position remontée pour ce mot-clé");
    expect(r.pct).toBe(67);
  });

  test("progress states count as not-ready and carry partial detail", () => {
    const readiness: KeywordPromptBundle["readiness"] = {
      serp: "progress",
      onpage: "progress",
      brief: "progress",
    };
    const r = computeReadiness(bundle({ readiness }));
    // metrics + gsc ready only, semantic missing (no profile) → 2/6 = 33
    expect(r.pct).toBe(33);
    expect(r.items[2].detail).toBe("Récupération partielle");
    expect(r.items[3].detail).toBe("Crawl partiel");
    expect(r.items[4].detail).toBe("Brief sur une autre langue");
  });

  test("everything missing (incl. no semantic profile) → 0 pct", () => {
    const missing: ReadyState = "missing";
    const r = computeReadiness(
      bundle({
        vol: null,
        hasGsc: false,
        gscConnected: false,
        readiness: { serp: missing, onpage: missing, brief: missing },
      }),
    );
    expect(r.pct).toBe(0);
    expect(r.items.every((i) => i.state === "missing")).toBe(true);
    expect(r.items[5].detail).toBe("Aucune analyse");
  });

  test("rounding: 3/6 ready → 50", () => {
    const r = computeReadiness(
      bundle({
        readiness: { serp: "ready", onpage: "missing", brief: "missing" },
      }),
    );
    // metrics + gsc + serp ready, semantic missing (no profile) = 3/6
    expect(r.pct).toBe(50);
  });

  test("semantic profile present → ready with term count + intent curve", () => {
    const r = computeReadiness(bundle({ semanticProfile }));
    expect(r.items[5]).toMatchObject({
      key: "semantic",
      label: "Profil sémantique",
      state: "ready",
    });
    expect(r.items[5].detail).toContain("2 termes");
    expect(r.items[5].detail).toContain("transactionnelle");
  });

  test("semantic profile null → missing with 'Aucune analyse'", () => {
    const r = computeReadiness(bundle({ semanticProfile: null }));
    expect(r.items[5]).toMatchObject({
      key: "semantic",
      label: "Profil sémantique",
      state: "missing",
      detail: "Aucune analyse",
    });
  });
});
