import { expect, it, vi } from "vitest";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import AiTextDetector from "./AiTextDetector";
import { compareAiDetectors } from "../api";
import type { AiDetectItem } from "../api";

// The detector tab talks to exactly one endpoint: compare-all-detectors.
vi.mock("../api", () => ({ compareAiDetectors: vi.fn() }));

// The verbatim uncertainty string the backend attaches to every response.
// The tab must render THIS string, not a hardcoded copy of it.
const WARNING =
  "AI detectors are probabilistic and can be wrong, especially on short, edited, " +
  "non-native, highly formal, or mixed-authorship text. Do not use this as proof of authorship.";

const item = (over: Partial<AiDetectItem> = {}): AiDetectItem => ({
  model_id: "desklib-ai-detector",
  name: "desklib/ai-text-detector-v1.01",
  domain: "general AI-written text detection",
  label: "ai",
  scores: { human: 0.1, ai: 0.9 },
  confidence: 0.9,
  latency_ms: 120.4,
  note: "Default detector.",
  ...over,
});

it("shows the probabilistic-signal disclaimer before any detection runs", () => {
  render(<AiTextDetector />);

  expect(screen.getByText(/not proof of authorship/i)).toBeInTheDocument();
  expect(compareAiDetectors).not.toHaveBeenCalled();
});

it("detects across all detectors by default and renders the API's own warning", async () => {
  vi.mocked(compareAiDetectors).mockResolvedValue({
    results: [item()],
    disagreement: false,
    warning: WARNING,
  });

  const user = userEvent.setup();
  render(<AiTextDetector />);
  await user.clear(screen.getByRole("textbox"));
  await user.type(screen.getByRole("textbox"), "some text to check");
  await user.click(screen.getByRole("button", { name: /detect ai text/i }));

  // Default = compare across ALL detectors: no model_ids argument.
  expect(compareAiDetectors).toHaveBeenCalledWith("some text to check");
  // The prominent callout renders the string the backend actually returned.
  expect(await screen.findByText(WARNING)).toBeInTheDocument();
});

it("renders a readout per detector with its verdict, scores, latency, and note", async () => {
  vi.mocked(compareAiDetectors).mockResolvedValue({
    results: [
      item({
        model_id: "desklib-ai-detector",
        name: "desklib/ai-text-detector-v1.01",
        label: "ai",
        scores: { human: 0.02, ai: 0.98 },
        confidence: 0.98,
        latency_ms: 130,
        note: "Default detector.",
      }),
      item({
        model_id: "fakespot-ai-detector",
        name: "fakespot-ai/roberta-base-ai-text-detection-v1",
        label: "human",
        scores: { human: 0.7, ai: 0.3 },
        confidence: 0.7,
        latency_ms: 45,
        note: "RoBERTa-based detector.",
      }),
    ],
    disagreement: true,
    warning: WARNING,
  });

  const user = userEvent.setup();
  render(<AiTextDetector />);
  await user.click(screen.getByRole("button", { name: /detect ai text/i }));

  const desklib = await screen.findByRole("group", { name: "desklib-ai-detector" });
  const fakespot = screen.getByRole("group", { name: "fakespot-ai-detector" });

  expect(within(desklib).getByText("desklib/ai-text-detector-v1.01")).toBeInTheDocument();
  expect(within(desklib).getByText("general AI-written text detection")).toBeInTheDocument();
  expect(within(desklib).getByText("2.0%")).toBeInTheDocument(); // P(human)
  expect(within(desklib).getAllByText("98.0%").length).toBeGreaterThan(0); // P(ai) + confidence
  expect(within(desklib).getByText("130 ms")).toBeInTheDocument();
  expect(within(desklib).getByText("Default detector.")).toBeInTheDocument();

  expect(within(fakespot).getByText("45 ms")).toBeInTheDocument();
  expect(within(fakespot).getByText(/RoBERTa-based/)).toBeInTheDocument();
});

it("shows a disagreement indicator when detectors reach different verdicts", async () => {
  vi.mocked(compareAiDetectors).mockResolvedValue({
    results: [
      item({ model_id: "a", label: "ai" }),
      item({ model_id: "b", label: "human", scores: { human: 0.8, ai: 0.2 } }),
    ],
    disagreement: true,
    warning: WARNING,
  });

  const user = userEvent.setup();
  render(<AiTextDetector />);
  await user.click(screen.getByRole("button", { name: /detect ai text/i }));

  expect(await screen.findByText(/detectors disagree/i)).toBeInTheDocument();
});

it("omits the disagreement indicator when the detectors agree", async () => {
  vi.mocked(compareAiDetectors).mockResolvedValue({
    results: [item({ model_id: "a", label: "ai" }), item({ model_id: "b", label: "ai" })],
    disagreement: false,
    warning: WARNING,
  });

  const user = userEvent.setup();
  render(<AiTextDetector />);
  await user.click(screen.getByRole("button", { name: /detect ai text/i }));

  await screen.findByRole("group", { name: "a" });
  expect(screen.queryByText(/detectors disagree/i)).not.toBeInTheDocument();
});

it("surfaces the API error detail when a detector is disabled on the public deploy", async () => {
  vi.mocked(compareAiDetectors).mockRejectedValue(
    new Error("Model 'fakespot-ai-detector' is disabled on the public deployment."),
  );

  const user = userEvent.setup();
  render(<AiTextDetector />);
  await user.click(screen.getByRole("button", { name: /detect ai text/i }));

  expect(await screen.findByText(/disabled on the public deployment/i)).toBeInTheDocument();
});
