import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import { describe, expect, mock, test } from "bun:test";
import { createElement, useRef } from "react";

import {
  useCreateModel,
  useDeleteModel,
  useTestModelConnection,
  useUpdateModel,
} from "../../../src/web/shared/hooks/use-models";
import {
  useArchiveProject,
  useCreateProject,
  useDeleteProject,
  useRestoreProject,
  useUpdateProject,
} from "../../../src/web/shared/hooks/use-projects";
import {
  useCreateProvider,
  useDeleteProvider,
  useTestProviderConfig,
  useUpdateProvider,
} from "../../../src/web/shared/hooks/use-providers";
import { installFetchMock, jsonResponse } from "../test-utils";

const MODEL = {
  autoAdapt: true,
  capabilities: ["text"] as string[],
  createdAt: "2024-01-01T00:00:00.000Z",
  customApiKey: null,
  customBaseUrl: null,
  description: "测试模型",
  id: "m1",
  modelId: "gpt-4",
  name: "测试模型",
  providerId: "prov-1",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const PROJECT = {
  archivedAt: null,
  createdAt: "2024-01-01T00:00:00.000Z",
  description: "测试",
  id: "p1",
  name: "测试项目",
  status: "active" as const,
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const PROVIDER = {
  createdAt: "2024-01-01T00:00:00.000Z",
  id: "prov-1",
  name: "测试供应商",
  type: "openai" as const,
  updatedAt: "2024-01-01T00:00:00.000Z",
};

function getLogMessages(spy: ReturnType<typeof mock>) {
  return spy.mock.calls.map((c) => c[0] as string).filter((s) => s.includes("[Alfred:INFO]"));
}

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
}

function setupModelFetches(result: unknown) {
  installFetchMock((call) => {
    if (call.method === "DELETE") return new Response(null, { status: 204 });
    if (call.url.includes("test")) return jsonResponse({ modelTestResponse: { message: "ok", ok: true } });
    return jsonResponse({ model: result }, { status: 201 });
  });
}

function setupProjectFetches(result: unknown) {
  installFetchMock((call) => {
    if (call.method === "DELETE") return new Response(null, { status: 204 });
    if (call.url.includes("archive")) return jsonResponse({ project: result });
    if (call.url.includes("restore")) return jsonResponse({ project: result });
    return jsonResponse({ project: result }, { status: 201 });
  });
}

function setupProviderFetches(result: unknown) {
  installFetchMock((call) => {
    if (call.method === "DELETE") return new Response(null, { status: 204 });
    if (call.url.includes("test")) return jsonResponse({ providerTestResponse: { message: "ok", ok: true } });
    return jsonResponse({ provider: result }, { status: 201 });
  });
}

function spyConsoleLog() {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const spy = mock((..._args: any[]) => {});
  const orig = console.log;
  console.log = spy;
  return { orig, restore: () => (console.log = orig), spy };
}

describe("useProjects onSuccess 日志", () => {
  const qc = makeQueryClient();

  test("create onSuccess", async () => {
    setupProjectFetches(PROJECT);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useCreateProject();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ name: "x" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/项目创建成功/);
    restore();
  });

  test("update onSuccess", async () => {
    setupProjectFetches(PROJECT);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useUpdateProject();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ data: { name: "y" }, id: "p1" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/项目更新成功/);
    restore();
  });

  test("delete onSuccess", async () => {
    setupProjectFetches(PROJECT);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useDeleteProject();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate("p1"));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/项目删除成功/);
    restore();
  });

  test("archive onSuccess", async () => {
    setupProjectFetches(PROJECT);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useArchiveProject();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate("p1"));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/项目归档成功/);
    restore();
  });

  test("restore onSuccess", async () => {
    setupProjectFetches(PROJECT);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useRestoreProject();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate("p1"));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/项目恢复成功/);
    restore();
  });
});

describe("useModels onSuccess 日志", () => {
  const qc = makeQueryClient();

  test("create onSuccess", async () => {
    setupModelFetches(MODEL);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useCreateModel();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ capabilities: ["text"], modelId: "gpt-4", name: "x", providerId: "p1" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/模型创建成功/);
    restore();
  });

  test("update onSuccess", async () => {
    setupModelFetches(MODEL);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useUpdateModel();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ data: { name: "y" }, id: "m1" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/模型更新成功/);
    restore();
  });

  test("delete onSuccess", async () => {
    setupModelFetches(MODEL);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useDeleteModel();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate("m1"));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/模型删除成功/);
    restore();
  });

  test("test onSuccess", async () => {
    setupModelFetches(MODEL);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useTestModelConnection();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ modelId: "gpt-4", providerId: "p1" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    restore();
    // useTestModelConnection has no onSuccess logger
    const infoCalls = spy.mock.calls.filter((c) => typeof c[0] === "string" && c[0].includes("[Alfred:INFO]"));
    expect(infoCalls.length).toBe(0);
  });
});

describe("useProviders onSuccess 日志", () => {
  const qc = makeQueryClient();

  test("create onSuccess", async () => {
    setupProviderFetches(PROVIDER);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useCreateProvider();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/供应商创建成功/);
    restore();
  });

  test("update onSuccess", async () => {
    setupProviderFetches(PROVIDER);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useUpdateProvider();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ data: { name: "y" }, id: "prov-1" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/供应商更新成功/);
    restore();
  });

  test("delete onSuccess", async () => {
    setupProviderFetches(PROVIDER);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useDeleteProvider();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate("prov-1"));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    const msgs = getLogMessages(spy);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatch(/供应商删除成功/);
    restore();
  });

  test("test onSuccess", async () => {
    setupProviderFetches(PROVIDER);
    const { restore, spy } = spyConsoleLog();

    function T({ onResult }: { onResult: (fn: () => void) => void }) {
      const { mutate } = useTestProviderConfig();
      const c = useRef(false);
      if (!c.current) {
        c.current = true;
        onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
      }
      return null;
    }
    render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
    await new Promise((r) => setTimeout(r, 200));

    restore();
    // useTestProviderConfig has no onSuccess logger
    const infoMsgs = spy.mock.calls.filter((c) => typeof c[0] === "string" && String(c[0]).includes("[Alfred:INFO]"));
    expect(infoMsgs.length).toBe(0);
  });
});
