import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ChatPanel } from "../ChatPanel";
import type { ChatAskRequest, ChatAskResponse } from "@/lib/site-finder-api";

// Mock the API client so the mutation resolves with a controlled response and we
// can assert the request payload (intent / cad_num).
const postChatAsk = vi.fn<(req: ChatAskRequest) => Promise<ChatAskResponse>>();

vi.mock("@/lib/site-finder-api", () => ({
  postChatAsk: (req: ChatAskRequest) => postChatAsk(req),
}));

function renderPanel(cadNum = "66:41:0000000:1") {
  const client = new QueryClient({
    defaultOptions: { mutations: { retry: false } },
  });
  return render(
    <QueryClientProvider client={client}>
      <ChatPanel cadNum={cadNum} />
    </QueryClientProvider>,
  );
}

const READY_RESPONSE: ChatAskResponse = {
  answer: "Краткая сводка по участку.",
  grounded_in: { run_id: 42, schema_version: "1.0", sections: ["6.1", "6.4"] },
  llm_used: true,
  fallback_reason: null,
  advisory: true,
  report_status: "ready",
};

const PENDING_RESPONSE: ChatAskResponse = {
  answer: "Сначала запустите анализ участка.",
  grounded_in: null,
  llm_used: false,
  fallback_reason: "report_pending",
  advisory: false,
  report_status: "pending",
};

beforeEach(() => {
  postChatAsk.mockReset();
});

afterEach(() => {
  vi.clearAllMocks();
});

describe("ChatPanel", () => {
  it("renders the heading and the five preset-intent buttons", () => {
    renderPanel();
    expect(
      screen.getByRole("heading", { name: "Чат по участку" }),
    ).toBeInTheDocument();
    for (const label of [
      "Сводка",
      "Что строить",
      "Почему такой прогноз",
      "Риски",
      "Сценарии",
    ]) {
      expect(screen.getByRole("button", { name: label })).toBeInTheDocument();
    }
  });

  it("sends with the matching intent + cad_num when a preset is clicked", async () => {
    postChatAsk.mockResolvedValue(READY_RESPONSE);
    renderPanel("66:41:0000000:1");

    await userEvent.click(screen.getByRole("button", { name: "Риски" }));

    await waitFor(() => expect(postChatAsk).toHaveBeenCalledTimes(1));
    const req = postChatAsk.mock.calls[0][0];
    expect(req.intent).toBe("risks");
    expect(req.cad_num).toBe("66:41:0000000:1");
    expect(req.message.length).toBeGreaterThan(0);
  });

  it("renders the assistant answer, advisory badge and provenance line", async () => {
    postChatAsk.mockResolvedValue(READY_RESPONSE);
    renderPanel();

    await userEvent.click(screen.getByRole("button", { name: "Сводка" }));

    expect(
      await screen.findByText("Краткая сводка по участку."),
    ).toBeInTheDocument();
    expect(screen.getByText("Advisory")).toBeInTheDocument();
    // grounded_in → "источник: разделы 6.1, 6.4 · run #42"
    expect(screen.getByText(/источник:/)).toHaveTextContent("разделы 6.1, 6.4");
    expect(screen.getByText(/источник:/)).toHaveTextContent("run #42");
  });

  it("renders the pending state as a neutral message (not an error)", async () => {
    postChatAsk.mockResolvedValue(PENDING_RESPONSE);
    renderPanel();

    await userEvent.click(screen.getByRole("button", { name: "Сводка" }));

    expect(
      await screen.findByText("Сначала запустите анализ участка."),
    ).toBeInTheDocument();
    expect(screen.getByText("Прогноз не рассчитан")).toBeInTheDocument();
    expect(screen.getByText("Детерминированный ответ")).toBeInTheDocument();
    // pending is NOT an error → no alert / retry button
    expect(screen.queryByRole("alert")).not.toBeInTheDocument();
    expect(
      screen.queryByRole("button", { name: "Повторить" }),
    ).not.toBeInTheDocument();
  });

  it("shows a retry affordance on network error", async () => {
    postChatAsk.mockRejectedValueOnce(new Error("network down"));
    renderPanel();

    await userEvent.click(screen.getByRole("button", { name: "Сводка" }));

    expect(await screen.findByRole("alert")).toBeInTheDocument();
    const retry = screen.getByRole("button", { name: "Повторить" });
    expect(retry).toBeInTheDocument();

    // Retry re-fires the same request.
    postChatAsk.mockResolvedValueOnce(READY_RESPONSE);
    await userEvent.click(retry);
    await waitFor(() => expect(postChatAsk).toHaveBeenCalledTimes(2));
  });

  it("submits the free-text input and clears it", async () => {
    postChatAsk.mockResolvedValue(READY_RESPONSE);
    renderPanel();

    const input = screen.getByPlaceholderText("Спросите про участок…");
    await userEvent.type(input, "Какова плотность застройки?");
    await userEvent.click(
      screen.getByRole("button", { name: "Отправить сообщение" }),
    );

    await waitFor(() => expect(postChatAsk).toHaveBeenCalledTimes(1));
    expect(postChatAsk.mock.calls[0][0].message).toBe(
      "Какова плотность застройки?",
    );
    expect(input).toHaveValue("");
  });
});
