import "../../../tests/web/test-utils";
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";

import { App } from "../../../src/web/app";
import { THEME_MEDIA_QUERY, THEME_PREFERENCE_STORAGE_KEY } from "../../../src/web/hooks/use-theme-preference";

function createDashboardResult(overrides: Record<string, unknown> = {}) {
  return {
    data: {
      summary: {
        down: 0,
        incidents: 0,
        lastCheckTime: "2025-01-15T10:00:00.000Z",
        total: 0,
        up: 0,
        window: {
          from: "2025-01-14T10:00:00.000Z",
          label: "24h",
          to: "2025-01-15T10:00:00.000Z",
        },
      },
      targets: [],
    },
    dataUpdatedAt: Date.now(),
    error: null,
    isFetching: false,
    isLoading: false,
    refetch: vi.fn(),
    ...overrides,
  };
}

const useDashboardMock = vi.fn(() => createDashboardResult());
const useMetaMock = vi.fn(() => ({
  data: { checkerTypes: ["http", "cmd"] as string[], version: "0.1.0" },
}));
const useTargetDetailMock = vi.fn(() => ({
  activeTab: "overview",
  closeDrawer: vi.fn(),
  handlePageChange: vi.fn(),
  handleTabChange: vi.fn(),
  handleTimeChange: vi.fn(),
  historyData: {
    items: [],
    page: 1,
    pageSize: 20,
    total: 0,
  },
  historyLoading: false,
  metricsData: null,
  metricsLoading: false,
  openDrawer: vi.fn(),
  selectedTarget: null,
  timeFrom: "",
  timeTo: "",
}));

void vi.mock("../../../src/web/hooks/use-queries", () => ({
  useDashboard: useDashboardMock,
  useMeta: useMetaMock,
}));

void vi.mock("../../../src/web/hooks/use-target-detail", () => ({
  useTargetDetail: useTargetDetailMock,
}));

function installMatchMedia(initialMatches: boolean) {
  const originalMatchMedia = window.matchMedia;
  let matches = initialMatches;
  const listeners = new Set<(event: MediaQueryListEvent) => void>();
  const mediaQueryList = {
    addEventListener: (_type: string, listener: (event: MediaQueryListEvent) => void) => listeners.add(listener),
    addListener: (listener: (event: MediaQueryListEvent) => void) => listeners.add(listener),
    dispatchEvent: () => true,
    get matches() {
      return matches;
    },
    media: THEME_MEDIA_QUERY,
    onchange: null,
    removeEventListener: (_type: string, listener: (event: MediaQueryListEvent) => void) => listeners.delete(listener),
    removeListener: (listener: (event: MediaQueryListEvent) => void) => listeners.delete(listener),
  } as MediaQueryList;

  window.matchMedia = () => mediaQueryList;

  return {
    restore: () => {
      window.matchMedia = originalMatchMedia;
    },
    setMatches: (nextMatches: boolean) => {
      matches = nextMatches;
      listeners.forEach((listener) => listener({ matches, media: THEME_MEDIA_QUERY } as MediaQueryListEvent));
    },
  };
}

describe("App", () => {
  let matchMediaController: ReturnType<typeof installMatchMedia>;

  beforeEach(() => {
    useDashboardMock.mockReturnValue(createDashboardResult());
    window.localStorage.clear();
    document.documentElement.removeAttribute("theme-mode");
    matchMediaController = installMatchMedia(false);
  });

  afterEach(() => {
    matchMediaController?.restore();
  });

  test("渲染不崩溃", () => {
    const { container } = render(<App />);
    expect(container.firstChild).not.toBeNull();
  });

  test("loading 状态不崩溃", () => {
    useDashboardMock.mockReturnValue(
      createDashboardResult({
        data: null,
        dataUpdatedAt: 0,
        error: null,
        isFetching: true,
        isLoading: true,
        refetch: vi.fn(),
      }),
    );

    const { container } = render(<App />);
    expect(container.firstChild).not.toBeNull();
  });

  test("错误状态不崩溃", () => {
    useDashboardMock.mockReturnValue(
      createDashboardResult({
        data: null,
        dataUpdatedAt: 0,
        error: new Error("Network error"),
        isFetching: false,
        isLoading: false,
        refetch: vi.fn(),
      }),
    );

    const { container } = render(<App />);
    expect(container.firstChild).not.toBeNull();
  });

  test("有数据状态不崩溃", () => {
    useDashboardMock.mockReturnValue(
      createDashboardResult({
        data: {
          summary: {
            down: 1,
            incidents: 0,
            lastCheckTime: "2025-01-15T10:00:00.000Z",
            total: 2,
            up: 1,
            window: {
              from: "2025-01-14T10:00:00.000Z",
              label: "24h",
              to: "2025-01-15T10:00:00.000Z",
            },
          },
          targets: [],
        },
        dataUpdatedAt: Date.now(),
        error: null,
        isFetching: false,
        isLoading: false,
        refetch: vi.fn(),
      }),
    );

    const { container } = render(<App />);
    expect(container.firstChild).not.toBeNull();
  });

  test("默认渲染主题模式选项并按系统亮色应用主题", async () => {
    render(<App />);
    expect(screen.getByText("系统")).not.toBeNull();
    expect(screen.getByText("明亮")).not.toBeNull();
    expect(screen.getByText("黑暗")).not.toBeNull();
    await waitFor(() => expect(document.documentElement.getAttribute("theme-mode")).toBe("light"));
  });

  test("切换黑暗模式后写入本地存储并应用主题", async () => {
    render(<App />);
    fireEvent.click(screen.getByText("黑暗"));
    expect(window.localStorage.getItem(THEME_PREFERENCE_STORAGE_KEY)).toBe("dark");
    await waitFor(() => expect(document.documentElement.getAttribute("theme-mode")).toBe("dark"));
  });

  test("刷新后恢复已保存的主题偏好", async () => {
    window.localStorage.setItem(THEME_PREFERENCE_STORAGE_KEY, "dark");
    render(<App />);
    await waitFor(() => expect(document.documentElement.getAttribute("theme-mode")).toBe("dark"));
  });

  test("系统模式响应 matchMedia 变化", async () => {
    render(<App />);
    await waitFor(() => expect(document.documentElement.getAttribute("theme-mode")).toBe("light"));
    act(() => matchMediaController.setMatches(true));
    await waitFor(() => expect(document.documentElement.getAttribute("theme-mode")).toBe("dark"));
  });

  test("Header 展示版本号", () => {
    render(<App />);
    expect(screen.getByText("v0.1.0")).not.toBeNull();
  });

  test("缺失版本时不展示版本占位", () => {
    useMetaMock.mockReturnValue({
      data: { checkerTypes: ["http", "cmd"], version: undefined as unknown as string },
    });

    render(<App />);
    expect(screen.queryByText(/v\d+\.\d+\.\d+/)).toBeNull();
  });

  test("复用 useMeta 查询结果", () => {
    render(<App />);
    expect(useMetaMock).toHaveBeenCalled();
  });
});
