import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { App } from "../src/client/App.js";
import type {
  CollectionSummaryResponse,
  DocumentComparisonResponse,
  ModifierStageResponse,
} from "../src/shared/api.js";

vi.mock("../src/client/DiffView.js", () => ({
  DiffView: ({ beforeLabel, afterLabel }: { beforeLabel: string; afterLabel: string }) => (
    <div data-testid="diff-view">
      {beforeLabel} / {afterLabel}
    </div>
  ),
}));

const modifiers = [
  {
    modifier_position: 0,
    modifier_id: "paper.rule",
    modifier_version: "1.0.0",
    parameters: [],
    applicability: "替换测试单词。",
    change_count: 1,
    stage_available: true,
  },
  {
    modifier_position: 1,
    modifier_id: "paper.zero",
    modifier_version: "1.0.0",
    parameters: [],
    applicability: "不修改当前测试文档。",
    change_count: 0,
    stage_available: true,
  },
];

const documentSummary = {
  document_id: "paper",
  source_label: "paper.md",
  status: "success" as const,
  current_kind: "success_output" as const,
  input_sha256: "1".repeat(64),
  current_sha256: "2".repeat(64),
  modifier_count: 2,
  completed_stage_count: 2,
  change_count: 1,
  error_count: 0,
  residual_proposal_count: 0,
};

const collectionResponse: CollectionSummaryResponse = {
  schema_version: 1,
  collection: { label: "合成评审", status: "success" },
  documents: [documentSummary],
  summary: {
    document_count: 1,
    success_count: 1,
    failed_count: 0,
    unstable_count: 0,
    change_count: 1,
  },
};

const change = {
  modifier_position: 0,
  modifier_id: "paper.rule",
  modifier_version: "1.0.0",
  proposal_index: 0,
  edit_index: 0,
  reason: "替换测试单词",
  location: { line: 1, column: 1 },
  span: { start: 0, end: 3 },
  editor_range: { start: 0, end: 3 },
  before: "old",
  after: "new",
  before_sha256: "1".repeat(64),
  after_sha256: "2".repeat(64),
};

const documentResponse: DocumentComparisonResponse = {
  schema_version: 1,
  document: documentSummary,
  modifiers,
  input_markdown: "old",
  current_markdown: "new",
  changes: [change],
  errors: [],
  residual_proposals: [],
};

const stageResponse: ModifierStageResponse = {
  schema_version: 1,
  document_id: "paper",
  modifier: modifiers[0]!,
  before_sha256: "1".repeat(64),
  after_sha256: "2".repeat(64),
  before_markdown: "old",
  after_markdown: "new",
  changes: [change],
};

function response(payload: unknown, status = 200): Response {
  return new Response(JSON.stringify(payload), {
    status,
    headers: { "Content-Type": "application/json" },
  });
}

describe("App", () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

  it("shows documents, Modifier order and the selected stage", async () => {
    const fetchMock = vi.fn((input: string | URL | Request) => {
      const pathname =
        typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
      if (pathname === "/api/v1/collection") {
        return Promise.resolve(response(collectionResponse));
      }
      if (pathname.endsWith("/modifiers/0")) {
        return Promise.resolve(response(stageResponse));
      }
      return Promise.resolve(response(documentResponse));
    });
    vi.stubGlobal("fetch", fetchMock);

    render(<App />);

    expect(await screen.findByRole("heading", { name: "合成评审" })).toBeInTheDocument();
    expect(await screen.findByTestId("diff-view")).toHaveTextContent("清洗前 / 清洗后");
    expect(screen.getByText("paper.rule")).toBeInTheDocument();
    expect(screen.getByText("paper.zero")).toBeInTheDocument();

    fireEvent.click(screen.getByRole("button", { name: /paper\.rule/ }));
    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining("modifiers/0"), expect.anything());
    });
    expect(await screen.findByTestId("diff-view")).toHaveTextContent(
      "Modifier 1 执行前 / Modifier 1 执行后",
    );
  });

  it("clicks a change through its Modifier stage", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn((input: string | URL | Request) => {
        const pathname =
          typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
        if (pathname === "/api/v1/collection") {
          return Promise.resolve(response(collectionResponse));
        }
        if (pathname.endsWith("/modifiers/0")) {
          return Promise.resolve(response(stageResponse));
        }
        return Promise.resolve(response(documentResponse));
      }),
    );

    render(<App />);

    fireEvent.click(await screen.findByRole("button", { name: /替换测试单词/ }));
    expect(await screen.findByTestId("diff-view")).toHaveTextContent(
      "Modifier 1 执行前 / Modifier 1 执行后",
    );
  });

  it("shows failed diagnostics without a cleaned comparison", async () => {
    const failedSummary = {
      ...documentSummary,
      status: "failed" as const,
      current_kind: "partial_output" as const,
      current_sha256: documentSummary.input_sha256,
      completed_stage_count: 0,
      change_count: 0,
      error_count: 1,
    };
    const failedCollection: CollectionSummaryResponse = {
      ...collectionResponse,
      collection: { ...collectionResponse.collection, status: "failed" },
      documents: [failedSummary],
      summary: {
        ...collectionResponse.summary,
        success_count: 0,
        failed_count: 1,
        change_count: 0,
      },
    };
    const failedDocument: DocumentComparisonResponse = {
      schema_version: 1,
      document: failedSummary,
      modifiers: modifiers.map((modifier) => ({
        ...modifier,
        change_count: 0,
        stage_available: false,
      })),
      input_markdown: "原文",
      current_markdown: null,
      changes: [],
      errors: [
        {
          code: "run.transform_failed",
          stage: "transform",
          modifier_position: 0,
          modifier_id: "paper.rule",
          modifier_version: "1.0.0",
          diagnostic_type: "SyntheticError",
          message: "测试 Modifier 失败。",
        },
      ],
      residual_proposals: [],
    };
    vi.stubGlobal(
      "fetch",
      vi.fn((input: string | URL | Request) => {
        const pathname =
          typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
        return Promise.resolve(
          response(
            pathname === "/api/v1/collection" ? failedCollection : failedDocument,
          ),
        );
      }),
    );

    render(<App />);

    expect(await screen.findByRole("heading", { name: "失败文档只展示审计证据" })).toBeInTheDocument();
    expect(screen.getByText("测试 Modifier 失败。")).toBeInTheDocument();
    expect(screen.queryByTestId("diff-view")).not.toBeInTheDocument();
  });
});
