import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { Objective5Chat } from "~/components/labs/Objective5Chat";
import {
  LAB2_CHAT_STORAGE_KEY,
  LAB2_CUSTOM_MODEL_VALUE,
} from "~/lib/lab2-chat";

describe("Objective5Chat", () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  function mockFetch() {
    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const url = String(input);

      if (url === "/courseware-runtime.json") {
        return new Response(
          JSON.stringify({
            lab2OllamaModels: [
              {
                label: "Gemma 4 E2B Q4",
                value: "batiai/gemma4-e2b:q4",
              },
              {
                label: "Gemma 4 E2B Q6",
                value: "batiai/gemma4-e2b:q6",
              },
            ],
            lab2OllamaUrl: "http://127.0.0.1:11434",
          }),
          { status: 200 },
        );
      }

      if (url === "/api/lab2/models") {
        return {
          json: async () => ({
            models: [
              {
                label: "Gemma 4 E2B Q4",
                value: "batiai/gemma4-e2b:q4",
              },
              {
                label: "Gemma 4 E2B Q6",
                value: "batiai/gemma4-e2b:q6",
              },
              { label: "Custom model", value: LAB2_CUSTOM_MODEL_VALUE },
            ],
          }),
          ok: true,
        };
      }

      return {
        json: async () => ({
          content: "Q6 stayed the most coherent in this run.",
          metrics: {
            completionTokens: 451,
            tokensPerSecond: 14.4,
          },
          renderMode: "text",
          role: "assistant",
        }),
        ok: true,
      };
    });

    vi.stubGlobal("fetch", fetchMock);
    return fetchMock;
  }

  it("loads persisted settings from localStorage", async () => {
    mockFetch();

    window.localStorage.setItem(
      LAB2_CHAT_STORAGE_KEY,
      JSON.stringify({
        apiKey: "sk-student",
        customModel: "custom/quantized-model",
        endpoint: "https://example.com/api",
        selectedModel: LAB2_CUSTOM_MODEL_VALUE,
      }),
    );

    render(<Objective5Chat />);

    expect(await screen.findByLabelText("Endpoint")).toHaveValue(
      "https://example.com/api",
    );
    expect(screen.getByLabelText("API key")).toHaveValue("sk-student");
    expect(screen.getByLabelText("Model")).toHaveValue(
      LAB2_CUSTOM_MODEL_VALUE,
    );
    expect(screen.getByLabelText("Custom model id")).toHaveValue(
      "custom/quantized-model",
    );
  });

  it("persists settings updates back to localStorage", async () => {
    mockFetch();

    render(<Objective5Chat />);

    await screen.findByLabelText("Endpoint");

    fireEvent.change(screen.getByLabelText("Endpoint"), {
      target: { value: "https://saved.example/api" },
    });

    await waitFor(() => {
      const saved = window.localStorage.getItem(LAB2_CHAT_STORAGE_KEY);
      expect(saved).toContain("https://saved.example/api");
    });
  });

  it("refreshes models from the endpoint into the dropdown", async () => {
    const fetchMock = mockFetch();

    render(<Objective5Chat />);

    fireEvent.change(screen.getByLabelText("Endpoint"), {
      target: { value: "http://127.0.0.1:1234" },
    });

    expect(
      await screen.findByRole("option", { name: "Gemma 4 E2B Q4" }),
    ).toBeInTheDocument();
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/lab2/models",
      expect.objectContaining({
        method: "POST",
      }),
    );
  });

  it("renders a text response from the lab chat route", async () => {
    mockFetch();

    render(<Objective5Chat />);

    fireEvent.change(screen.getByLabelText("Prompt"), {
      target: { value: "Compare these quantized models." },
    });
    fireEvent.submit(screen.getByRole("button", { name: "Send Prompt" }).closest("form")!);

    expect(
      await screen.findByText("Q6 stayed the most coherent in this run."),
    ).toBeInTheDocument();
    expect(screen.getByText("Tokens/sec 14.4")).toBeInTheDocument();
  });

  it("renders sanitized svg responses as an image preview", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn(async (input: RequestInfo | URL) => {
        const url = String(input);

        if (url === "/courseware-runtime.json") {
          return new Response(
            JSON.stringify({
              lab2OllamaModels: [
                {
                  label: "Gemma 4 E2B Q4",
                  value: "batiai/gemma4-e2b:q4",
                },
              ],
              lab2OllamaUrl: "http://127.0.0.1:11434",
            }),
            { status: 200 },
          );
        }

        if (url === "/api/lab2/models") {
          return {
            json: async () => ({
              models: [
                { label: "Gemma 4 E2B Q4", value: "batiai/gemma4-e2b:q4" },
                { label: "Custom model", value: LAB2_CUSTOM_MODEL_VALUE },
              ],
            }),
            ok: true,
          };
        }

        return {
          json: async () => ({
            content:
              "<svg viewBox=\"0 0 10 10\"><circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"#0f4f76\" /></svg>",
            metrics: {
              completionTokens: 451,
              tokensPerSecond: 14.4,
            },
            renderMode: "svg",
            role: "assistant",
            svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"#0f4f76\" /></svg>",
          }),
          ok: true,
        };
      }),
    );

    render(<Objective5Chat />);

    fireEvent.change(screen.getByLabelText("Prompt"), {
      target: { value: "Draw a pelican riding a bicycle." },
    });
    fireEvent.submit(screen.getByRole("button", { name: "Send Prompt" }).closest("form")!);

    expect(
      await screen.findByAltText(/svg sketch generated by/i),
    ).toBeInTheDocument();
    expect(screen.getByText("View SVG source")).toBeInTheDocument();
    expect(screen.getByText("Tokens/sec 14.4")).toBeInTheDocument();
  });

  it("defaults to the managed Ollama runtime endpoint and models", async () => {
    mockFetch();

    render(<Objective5Chat />);

    expect(await screen.findByLabelText("Endpoint")).toHaveValue(
      "http://localhost:11434/",
    );
    expect(screen.getByLabelText("Model")).toHaveValue("batiai/gemma4-e2b:q4");
  });
});
