// @vitest-environment jsdom
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { useKimiOauth } from "./useKimiOauth";

// 后端客户端（POST start/disconnect + GET job/status，经真实 useJobPoll）与 Toast 是副作用边界。
// kimiOauth.ts/job.ts 纯逻辑保留真实。
const post = vi.fn();
const get = vi.fn();
const toast = vi.fn();
vi.mock("@/lib/api/client", () => ({
  api: {
    POST: (...a: unknown[]) => post(...a),
    GET: (...a: unknown[]) => get(...a),
  },
}));
vi.mock("@/components/Toast", () => ({ useToast: () => toast }));

const START = "/settings/providers/kimi-code/oauth/start";
const DISCONNECT = "/settings/providers/kimi-code/oauth/disconnect";
const STATUS = "/settings/providers/kimi-code/oauth/status";
const JOB = "/jobs/{job_id}";

const startOk = {
  data: {
    job_id: "job-1",
    user_code: "ABCD-1234",
    verification_uri: "https://kimi.example/verify",
    verification_uri_complete: "https://kimi.example/verify?code=ABCD-1234",
    expires_in: 600,
    interval: 5,
  },
  error: null,
};

// 把 GET 按 path 路由：job 轮询 vs 状态查询。
function routeGet(jobResult: unknown, statusResult: unknown): void {
  get.mockImplementation((path: string) => {
    if (path === JOB) return Promise.resolve(jobResult);
    return Promise.resolve(statusResult);
  });
}

describe("useKimiOauth", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    post.mockReset();
    get.mockReset();
    toast.mockReset();
    vi.spyOn(window, "open").mockReturnValue(null);
  });
  afterEach(() => {
    vi.useRealTimers();
    vi.clearAllMocks();
  });

  it("初始未连接：phase=idle、无 device、不忙、无错误", () => {
    const { result } = renderHook(() =>
      useKimiOauth({ initialConnected: false, initialExpiresAt: null }),
    );
    expect(result.current.phase).toBe("idle");
    expect(result.current.device).toBeNull();
    expect(result.current.busy).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it("初始已连接：phase=connected 并带过期时刻", () => {
    const { result } = renderHook(() =>
      useKimiOauth({
        initialConnected: true,
        initialExpiresAt: "2026-12-31T00:00:00Z",
      }),
    );
    expect(result.current.phase).toBe("connected");
    expect(result.current.expiresAt).toBe("2026-12-31T00:00:00Z");
  });

  it("connect 成功（pending→authorized）：展示 device、轮询 done 后置已连接并弹成功 toast", async () => {
    // Arrange：start 拿到 device + job_id；job 首拍即 done 且 connected；状态查询确认已连接。
    post.mockImplementation((path: string) =>
      path === START ? Promise.resolve(startOk) : Promise.resolve({ data: {}, error: null }),
    );
    routeGet(
      { data: { id: "job-1", status: "done", progress: 100, result: { connected: true } }, error: null },
      { data: { connected: true, expires_at: "2027-01-01T00:00:00Z" }, error: null },
    );

    const { result } = renderHook(() =>
      useKimiOauth({ initialConnected: false, initialExpiresAt: null }),
    );

    // Act：发起连接，刷掉 job 轮询 + refreshStatus 的微任务。
    await act(async () => {
      await result.current.connect();
      await vi.advanceTimersByTimeAsync(0);
    });
    await act(async () => {
      await vi.advanceTimersByTimeAsync(0);
    });

    // Assert：已展示 device、phase=connected、刷新到新过期时刻、弹成功 toast。
    expect(result.current.device?.userCode).toBe("ABCD-1234");
    expect(window.open).toHaveBeenCalledWith(
      "https://kimi.example/verify?code=ABCD-1234",
      "_blank",
      "noopener,noreferrer",
    );
    expect(result.current.phase).toBe("connected");
    expect(result.current.expiresAt).toBe("2027-01-01T00:00:00Z");
    expect(toast).toHaveBeenCalledWith("已连接 Kimi Code。", "success");
  });

  it("connect 后授权进行中：job 仍 running → phase=awaiting 且 busy", async () => {
    post.mockImplementation((path: string) =>
      path === START ? Promise.resolve(startOk) : Promise.resolve({ data: {}, error: null }),
    );
    routeGet(
      { data: { id: "job-1", status: "running", progress: 20 }, error: null },
      { data: { connected: false }, error: null },
    );

    const { result } = renderHook(() =>
      useKimiOauth({ initialConnected: false, initialExpiresAt: null }),
    );
    await act(async () => {
      await result.current.connect();
      await vi.advanceTimersByTimeAsync(0);
    });

    expect(result.current.phase).toBe("awaiting");
    expect(result.current.busy).toBe(true);
  });

  it("connect 轮询失败（job failed）：phase=error 并弹失败 toast", async () => {
    post.mockImplementation((path: string) =>
      path === START ? Promise.resolve(startOk) : Promise.resolve({ data: {}, error: null }),
    );
    routeGet(
      { data: { id: "job-1", status: "failed", progress: 0, error: "授权已过期" }, error: null },
      { data: { connected: false }, error: null },
    );

    const { result } = renderHook(() =>
      useKimiOauth({ initialConnected: false, initialExpiresAt: null }),
    );
    await act(async () => {
      await result.current.connect();
      await vi.advanceTimersByTimeAsync(0);
    });
    await act(async () => {
      await vi.advanceTimersByTimeAsync(0);
    });

    expect(result.current.phase).toBe("error");
    expect(result.current.error).toBe("授权已过期");
    expect(toast).toHaveBeenCalledWith(
      expect.stringContaining("连接失败"),
      "error",
    );
  });

  it("connect 发起失败（start 返回 error）：保持 idle 并弹错误 toast", async () => {
    post.mockResolvedValue({ data: null, error: { detail: "rate limited" } });

    const { result } = renderHook(() =>
      useKimiOauth({ initialConnected: false, initialExpiresAt: null }),
    );
    await act(async () => {
      await result.current.connect();
    });

    expect(result.current.phase).toBe("idle");
    expect(result.current.device).toBeNull();
    expect(toast).toHaveBeenCalledWith(
      "发起 Kimi Code 连接失败，请稍后重试。",
      "error",
    );
  });

  it("disconnect 成功：复位为未连接并弹成功 toast", async () => {
    post.mockResolvedValue({ data: { ok: true }, error: null });

    const { result } = renderHook(() =>
      useKimiOauth({
        initialConnected: true,
        initialExpiresAt: "2026-12-31T00:00:00Z",
      }),
    );
    await act(async () => {
      await result.current.disconnect();
    });

    expect(result.current.phase).toBe("idle");
    expect(result.current.device).toBeNull();
    expect(result.current.expiresAt).toBeNull();
    expect(toast).toHaveBeenCalledWith("已断开 Kimi Code。", "success");
  });

  it("disconnect 失败（返回 error）：弹错误 toast、不复位", async () => {
    post.mockResolvedValue({ data: null, error: { detail: "boom" } });

    const { result } = renderHook(() =>
      useKimiOauth({
        initialConnected: true,
        initialExpiresAt: "2026-12-31T00:00:00Z",
      }),
    );
    await act(async () => {
      await result.current.disconnect();
    });

    expect(result.current.phase).toBe("connected");
    expect(toast).toHaveBeenCalledWith(
      "断开 Kimi Code 失败，请稍后重试。",
      "error",
    );
  });
});
