import { expect } from "chai";
import { runAuditLowConfidenceJob } from "../../src/jobs/audit-low-confidence.js";

/**
 * Stubs `globalThis.fetch` directly (this worker's own `api-client.ts` is
 * a thin wrapper around it) — same convention `apps/api`'s
 * `the-meal-db.ts` test uses for the same reason: no real network call,
 * no mocking library needed for a single-function dependency.
 */
function stubFetch(responses: Record<string, unknown>): { url: string; body: unknown }[] {
  const calls: { url: string; body: unknown }[] = [];
  globalThis.fetch = (async (url: string | URL, init?: RequestInit) => {
    const href = String(url);
    const body = typeof init?.body === "string" ? JSON.parse(init.body) : undefined;
    calls.push({ url: href, body });
    for (const [pathFragment, response] of Object.entries(responses)) {
      if (href.includes(pathFragment)) {
        return new Response(JSON.stringify(response), { status: 200 });
      }
    }
    throw new Error(`stubFetch: no response configured for ${href}`);
  }) as typeof fetch;
  return calls;
}

describe("runAuditLowConfidenceJob", () => {
  const originalFetch = globalThis.fetch;

  afterEach(() => {
    globalThis.fetch = originalFetch;
  });

  it("proposes a suggestion only when the LLM disagrees with the NLP anchor", async () => {
    const calls = stubFetch({
      "audit-batch": [
        {
          stepId: 1,
          recipeId: 1,
          clauseText: "jusqu'à ce que ce soit doré",
          anchorKey: "fry",
          intentKey: null,
          score: 0.5,
          locale: "fr",
        },
        {
          stepId: 2,
          recipeId: 1,
          clauseText: "laisser reposer un instant",
          anchorKey: "rest",
          intentKey: "rest",
          score: 0.6,
          locale: "fr",
        },
      ],
      "training-suggestions": { created: 1 },
    });
    const llm = {
      judgeClause: async (text: string) => (text.includes("doré") ? "roast" : "rest"),
    };

    const count = await runAuditLowConfidenceJob(llm, { locale: "fr", limit: 10 });

    expect(count).to.equal(1);
    const postCall = calls.find((call) => call.url.includes("training-suggestions"));
    if (!postCall) throw new Error("expected a POST to training-suggestions");
    expect(postCall.body).to.deep.equal({
      suggestions: [
        {
          techStepKey: "roast",
          locale: "fr",
          suggestedSynonyms: [],
          suggestedUtterances: ["jusqu'à ce que ce soit doré"],
          sourceType: "llm_audit",
        },
      ],
    });
  });

  it("posts nothing when the LLM agrees with the anchor or has no opinion", async () => {
    const calls = stubFetch({
      "audit-batch": [
        {
          stepId: 1,
          recipeId: 1,
          clauseText: "agrees",
          anchorKey: "cook",
          intentKey: "cook",
          score: 0.5,
          locale: "fr",
        },
        {
          stepId: 2,
          recipeId: 1,
          clauseText: "no opinion",
          anchorKey: "boil",
          intentKey: null,
          score: 0.4,
          locale: "fr",
        },
      ],
    });
    const llm = {
      judgeClause: async (text: string) => (text === "agrees" ? "cook" : null),
    };

    const count = await runAuditLowConfidenceJob(llm, { locale: "fr", limit: 10 });

    expect(count).to.equal(0);
    expect(calls.some((call) => call.url.includes("training-suggestions"))).to.equal(false);
  });

  it("passes locale/limit through to GET /internal/tech-steps/audit-batch", async () => {
    const calls = stubFetch({ "audit-batch": [] });
    const llm = { judgeClause: async () => null };

    await runAuditLowConfidenceJob(llm, { locale: "en", limit: 7 });

    const getCall = calls.find((call) => call.url.includes("audit-batch"));
    if (!getCall) throw new Error("expected a GET to audit-batch");
    expect(getCall.url).to.include("locale=en");
    expect(getCall.url).to.include("limit=7");
  });
});
