import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, mock, test } from "bun:test";
import { createElement } from "react";

import type { Model, Provider } from "../../../src/shared/api";

import { App } from "../../../src/web/app";
import { ModelFormModal } from "../../../src/web/features/models/components/ModelFormModal";
import { installFetchMock, jsonResponse, mockMetaResponse, renderWithProviders } from "../test-utils";

const ENABLED_PROVIDER: Provider = {
  apiKey: "sk-test",
  baseUrl: "https://api.openai.com/v1",
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "pv1",
  name: "OpenAI",
  type: "openai",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const DISABLED_PROVIDER: Provider = {
  apiKey: "sk-off",
  baseUrl: "https://api.deepseek.com/v1",
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "pv2",
  name: "DeepSeek",
  type: "openai-compatible",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const ENABLED_MODEL: Model = {
  capabilities: ["text", "reasoning"],
  contextLength: 128000,
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "m1",
  maxOutputTokens: 4096,
  modelId: "gpt-4o",
  name: "GPT-4o",
  providerId: "pv1",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

function clickLatestConfirmButton() {
  const buttons = screen.getAllByRole("button", { name: /OK|确\s*定/ });
  fireEvent.click(buttons[buttons.length - 1]!);
}

describe("ModelFormModal", () => {
  test("编辑模型表单只提交变更字段", async () => {
    const updateCalls: unknown[] = [];

    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: ENABLED_MODEL,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: (args: unknown) => {
          updateCalls.push(args);
          return Promise.resolve();
        },
        open: true,
        providers: [ENABLED_PROVIDER, DISABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull());
    fireEvent.change(screen.getByPlaceholderText("请输入模型名称"), { target: { value: "GPT-4o Mini" } });
    clickLatestConfirmButton();

    await waitFor(() => expect(updateCalls.length).toBe(1));
    expect(updateCalls[0]).toEqual({ data: { name: "GPT-4o Mini" }, id: "m1" });
  });

  test("模型表单校验失败不会提交", async () => {
    const onCreate = mock(() => Promise.resolve());

    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: null,
        onCancel: () => undefined,
        onCreate,
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [ENABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull());
    clickLatestConfirmButton();
    expect(onCreate).not.toHaveBeenCalled();
  });

  test("新建模型默认选中文本和推理能力", async () => {
    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: null,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [ENABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByLabelText("文本")).not.toBeNull());
    const textCheckbox = screen.getByLabelText("文本");
    const reasoningCheckbox = screen.getByLabelText("推理");
    expect((textCheckbox as { checked?: boolean }).checked).toBe(true);
    expect((reasoningCheckbox as { checked?: boolean }).checked).toBe(true);
  });

  test("新建模型展示供应商 options 列表", async () => {
    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: null,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [ENABLED_PROVIDER, DISABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull());
    fireEvent.mouseDown(screen.getByRole("combobox"));

    expect(await screen.findByText("OpenAI")).not.toBeNull();
    expect(await screen.findByText("DeepSeek")).not.toBeNull();
  });

  test("供应商下拉展示加载错误提示", async () => {
    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: null,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [],
        providersError: new Error("options failed"),
        providersLoading: false,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull());
    fireEvent.mouseDown(screen.getByRole("combobox"));

    expect(await screen.findByText("供应商加载失败：options failed")).not.toBeNull();
  });

  test("编辑模型时可测试模型连接", async () => {
    const testModelConnection = mock(() => Promise.resolve({ message: "模型连接成功", ok: true }));

    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: ENABLED_MODEL,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [ENABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
        testModelConnection,
      }),
    );

    await waitFor(() => expect(screen.getByRole("button", { name: "测试连接" })).not.toBeNull());
    fireEvent.click(screen.getByRole("button", { name: "测试连接" }));

    await waitFor(() =>
      expect(testModelConnection).toHaveBeenCalledWith({
        modelId: "gpt-4o",
        providerId: "pv1",
      }),
    );
  });

  test("新建模型也显示测试连接按钮", async () => {
    renderWithProviders(
      createElement(ModelFormModal, {
        editingModel: null,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate: () => Promise.resolve(),
        open: true,
        providers: [ENABLED_PROVIDER],
        providersError: null,
        providersLoading: false,
        submitting: false,
        testModelConnection: () => Promise.resolve({ message: "ok", ok: true }),
      }),
    );

    await waitFor(() => expect(screen.getByRole("button", { name: "测试连接" })).not.toBeNull());
  });
});

const TEST_PROVIDER: Provider = {
  apiKey: "sk-test",
  baseUrl: "https://api.openai.com/v1",
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "pv1",
  name: "OpenAI",
  type: "openai",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const TEST_MODEL: Model = {
  capabilities: ["text"],
  contextLength: 128000,
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "m1",
  maxOutputTokens: 4096,
  modelId: "gpt-4o",
  name: "GPT-4o",
  providerId: "pv1",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

function createModelFetchMock() {
  let models = [TEST_MODEL];

  return installFetchMock((call) => {
    if (call.url.includes("/api/meta")) return mockMetaResponse();

    const url = new URL(call.url, "http://localhost");

    if (url.pathname === "/api/providers/options" && call.method === "GET") {
      return jsonResponse({ items: [TEST_PROVIDER] });
    }

    if (url.pathname === "/api/models" && call.method === "POST") {
      const data = JSON.parse(typeof call.body === "string" ? call.body : "{}") as Record<string, unknown>;
      const created: Model = {
        ...TEST_MODEL,
        ...data,
        createdAt: "2024-01-02T00:00:00.000Z",
        id: "m-new",
        updatedAt: "2024-01-02T00:00:00.000Z",
      };
      models = [created, ...models];
      return jsonResponse({ model: created }, { status: 201 });
    }

    if (/^\/api\/models\/[^/]+$/.exec(url.pathname) && call.method === "PATCH") {
      const id = url.pathname.split("/").pop()!;
      const data = JSON.parse(typeof call.body === "string" ? call.body : "{}") as Record<string, unknown>;
      const existing = models.find((m) => m.id === id) ?? TEST_MODEL;
      const updated = { ...existing, ...(data as Partial<Model>) };
      models = models.map((m) => (m.id === id ? updated : m));
      return jsonResponse({ model: updated });
    }

    if (/^\/api\/models\/[^/]+$/.exec(url.pathname) && call.method === "DELETE") {
      const id = url.pathname.split("/").pop()!;
      models = models.filter((m) => m.id !== id);
      return new Response(null, { status: 204 });
    }

    if (url.pathname === "/api/models" && call.method === "GET") {
      const keyword = url.searchParams.get("keyword") ?? "";
      const items = keyword ? models.filter((m) => `${m.name}${m.modelId}`.includes(keyword)) : models;
      return jsonResponse({ items, page: 1, pageSize: 20, total: items.length });
    }

    return jsonResponse({ error: "Not Found" }, { status: 404 });
  });
}

describe("ModelListPage", () => {
  test("渲染模型列表页并请求模型数据", async () => {
    const calls = createModelFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/models" });

    await waitFor(() => {
      expect(screen.getByText("GPT-4o")).not.toBeNull();
    });

    expect(screen.getByPlaceholderText("搜索模型名称或 ID")).not.toBeNull();
    expect(screen.getByRole("button", { name: /新建模型/ })).not.toBeNull();
    expect(calls.some((call) => call.url.includes("/api/models"))).toBe(true);
  }, 15000);

  test("搜索模型更新请求参数", async () => {
    const calls = createModelFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/models" });
    await waitFor(() => expect(screen.getByText("GPT-4o")).not.toBeNull());

    fireEvent.change(screen.getByPlaceholderText("搜索模型名称或 ID"), { target: { value: "gpt" } });
    fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
    await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=gpt"))).toBe(true));
  }, 15000);

  test("新建模型弹窗可以打开", async () => {
    createModelFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/models" });
    await waitFor(() => expect(screen.getByRole("button", { name: /新建模型/ })).not.toBeNull());

    fireEvent.click(screen.getByRole("button", { name: /新建模型/ }));
    await waitFor(() => expect(screen.getByPlaceholderText("请输入模型名称")).not.toBeNull());
  }, 15000);
});
