/**
 * Tests for TanStack Query data fetching layer.
 *
 * Tests query hooks for lore data, bridge status, and mutations
 * for sync/reconcile operations.
 */

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, waitFor, act } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { type ReactNode } from "react";
import {
  useLoreStatus,
  useBridgeStatus,
  useSyncNow,
  useReconcile,
  createQueryClient,
} from "@/lib/queries";
import {
  invoke,
  resetMocks,
  setMockResponse,
  simulateEvent,
} from "../mocks/tauri-api";
import type { LoreStatus, BridgeStatus, SyncResult } from "@/lib/types";

// --- Test Fixtures ---

const mockLoreStatus: LoreStatus = {
  last_sync: "2026-02-26T10:00:00Z",
  is_healthy: true,
  message: "Synced successfully",
  summary: {
    open_issues: 5,
    authored_mrs: 3,
    reviewing_mrs: 2,
  },
};

const mockBridgeStatus: BridgeStatus = {
  mapping_count: 42,
  pending_count: 3,
  suspect_count: 1,
  last_sync: "2026-02-26T10:00:00Z",
  last_reconciliation: "2026-02-25T08:00:00Z",
};

const mockSyncResult: SyncResult = {
  created: 5,
  skipped: 10,
  closed: 2,
  healed: 1,
  errors: [],
};

// --- Test Wrapper ---

function createTestQueryClient(): QueryClient {
  return new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        gcTime: Infinity,
      },
      mutations: {
        retry: false,
      },
    },
  });
}

function createWrapper(queryClient: QueryClient) {
  return function Wrapper({ children }: { children: ReactNode }) {
    return (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );
  };
}

// --- QueryClient Setup Tests ---

describe("createQueryClient", () => {
  it("creates a QueryClient with appropriate defaults", () => {
    const client = createQueryClient();
    expect(client).toBeInstanceOf(QueryClient);

    const defaults = client.getDefaultOptions();
    expect(defaults.queries?.retry).toBe(1);
    expect(defaults.queries?.refetchOnWindowFocus).toBe(true);
  });
});

// --- useLoreStatus Tests ---

describe("useLoreStatus", () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = createTestQueryClient();
    resetMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  it("fetches lore status successfully", async () => {
    setMockResponse("get_lore_status", mockLoreStatus);

    const { result } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(result.current.data).toEqual(mockLoreStatus);
    expect(invoke).toHaveBeenCalledWith("get_lore_status");
  });

  it("shows loading state initially", () => {
    const { result } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    expect(result.current.isLoading).toBe(true);
    expect(result.current.data).toBeUndefined();
  });

  it("handles lore unavailable error", async () => {
    const mockError = {
      code: "LORE_UNAVAILABLE",
      message: "lore CLI not found",
      recoverable: true,
    };

    invoke.mockRejectedValueOnce(mockError);

    const { result } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isError).toBe(true);
    });

    expect(result.current.error).toEqual(mockError);
  });

  it("invalidates on lore-data-changed event", async () => {
    setMockResponse("get_lore_status", mockLoreStatus);

    const { result } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    // Initial fetch
    expect(invoke).toHaveBeenCalledTimes(1);

    // Simulate event
    act(() => {
      simulateEvent("lore-data-changed", undefined);
    });

    // Should refetch
    await waitFor(() => {
      expect(invoke).toHaveBeenCalledTimes(2);
    });
  });

  it("uses staleTime of 30 seconds", async () => {
    setMockResponse("get_lore_status", mockLoreStatus);

    const { result: result1 } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result1.current.isSuccess).toBe(true);
    });

    // Second hook should use cached data, not refetch
    const { result: result2 } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    // Data should be immediately available (from cache)
    expect(result2.current.data).toEqual(mockLoreStatus);
    // Still only one invoke call
    expect(invoke).toHaveBeenCalledTimes(1);
  });
});

// --- useBridgeStatus Tests ---

describe("useBridgeStatus", () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = createTestQueryClient();
    resetMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  it("fetches bridge status successfully", async () => {
    setMockResponse("get_bridge_status", mockBridgeStatus);

    const { result } = renderHook(() => useBridgeStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(result.current.data).toEqual(mockBridgeStatus);
    expect(invoke).toHaveBeenCalledWith("get_bridge_status");
  });

  it("shows loading state initially", () => {
    const { result } = renderHook(() => useBridgeStatus(), {
      wrapper: createWrapper(queryClient),
    });

    expect(result.current.isLoading).toBe(true);
  });

  it("handles bridge error", async () => {
    const mockError = {
      code: "BRIDGE_LOCKED",
      message: "Bridge is locked by another process",
      recoverable: true,
    };

    invoke.mockRejectedValueOnce(mockError);

    const { result } = renderHook(() => useBridgeStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isError).toBe(true);
    });

    expect(result.current.error).toEqual(mockError);
  });

  it("invalidates on lore-data-changed event", async () => {
    setMockResponse("get_bridge_status", mockBridgeStatus);

    const { result } = renderHook(() => useBridgeStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(invoke).toHaveBeenCalledTimes(1);

    // Simulate event
    act(() => {
      simulateEvent("lore-data-changed", undefined);
    });

    await waitFor(() => {
      expect(invoke).toHaveBeenCalledTimes(2);
    });
  });
});

// --- useSyncNow Mutation Tests ---

describe("useSyncNow", () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = createTestQueryClient();
    resetMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  it("triggers sync and returns result", async () => {
    setMockResponse("sync_now", mockSyncResult);

    const { result } = renderHook(() => useSyncNow(), {
      wrapper: createWrapper(queryClient),
    });

    expect(result.current.isPending).toBe(false);

    act(() => {
      result.current.mutate();
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(invoke).toHaveBeenCalledWith("sync_now");
    expect(result.current.data).toEqual(mockSyncResult);
  });

  it("invalidates lore and bridge queries on success", async () => {
    setMockResponse("sync_now", mockSyncResult);
    setMockResponse("get_lore_status", mockLoreStatus);
    setMockResponse("get_bridge_status", mockBridgeStatus);

    // First, set up some cached data
    const { result: loreResult } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(loreResult.current.isSuccess).toBe(true);
    });

    const { result: bridgeResult } = renderHook(() => useBridgeStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(bridgeResult.current.isSuccess).toBe(true);
    });

    // Clear call counts after initial fetches
    invoke.mockClear();

    // Now trigger sync
    const { result: syncResult } = renderHook(() => useSyncNow(), {
      wrapper: createWrapper(queryClient),
    });

    await act(async () => {
      await syncResult.current.mutateAsync();
    });

    // Should have called sync_now plus refetched both status queries
    await waitFor(() => {
      expect(invoke).toHaveBeenCalledWith("sync_now");
      expect(invoke).toHaveBeenCalledWith("get_lore_status");
      expect(invoke).toHaveBeenCalledWith("get_bridge_status");
    });
  });

  it("handles sync failure", async () => {
    const mockError = {
      code: "BRIDGE_SYNC_FAILED",
      message: "Sync failed",
      recoverable: true,
    };

    invoke.mockRejectedValueOnce(mockError);

    const { result } = renderHook(() => useSyncNow(), {
      wrapper: createWrapper(queryClient),
    });

    act(() => {
      result.current.mutate();
    });

    await waitFor(() => {
      expect(result.current.isError).toBe(true);
    });

    expect(result.current.error).toEqual(mockError);
  });
});

// --- useReconcile Mutation Tests ---

describe("useReconcile", () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = createTestQueryClient();
    resetMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  it("triggers reconciliation and returns result", async () => {
    const reconcileResult: SyncResult = {
      ...mockSyncResult,
      closed: 5, // More closures in full reconcile
      healed: 3,
    };
    setMockResponse("reconcile", reconcileResult);

    const { result } = renderHook(() => useReconcile(), {
      wrapper: createWrapper(queryClient),
    });

    act(() => {
      result.current.mutate();
    });

    await waitFor(() => {
      expect(result.current.isSuccess).toBe(true);
    });

    expect(invoke).toHaveBeenCalledWith("reconcile");
    expect(result.current.data).toEqual(reconcileResult);
  });

  it("invalidates queries on success", async () => {
    setMockResponse("reconcile", mockSyncResult);
    setMockResponse("get_lore_status", mockLoreStatus);
    setMockResponse("get_bridge_status", mockBridgeStatus);

    // Set up cached queries
    const { result: loreResult } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(loreResult.current.isSuccess).toBe(true);
    });

    invoke.mockClear();

    // Trigger reconcile
    const { result } = renderHook(() => useReconcile(), {
      wrapper: createWrapper(queryClient),
    });

    await act(async () => {
      await result.current.mutateAsync();
    });

    await waitFor(() => {
      expect(invoke).toHaveBeenCalledWith("reconcile");
      expect(invoke).toHaveBeenCalledWith("get_lore_status");
    });
  });

  it("handles reconcile failure", async () => {
    const mockError = {
      code: "LORE_FETCH_FAILED",
      message: "Failed to fetch from lore",
      recoverable: true,
    };

    invoke.mockRejectedValueOnce(mockError);

    const { result } = renderHook(() => useReconcile(), {
      wrapper: createWrapper(queryClient),
    });

    act(() => {
      result.current.mutate();
    });

    await waitFor(() => {
      expect(result.current.isError).toBe(true);
    });

    expect(result.current.error).toEqual(mockError);
  });
});

// --- Combined Status Hook Tests ---

describe("query invalidation coordination", () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = createTestQueryClient();
    resetMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  it("sync-status event with completed status invalidates queries", async () => {
    setMockResponse("get_lore_status", mockLoreStatus);
    setMockResponse("get_bridge_status", mockBridgeStatus);

    const { result: loreResult } = renderHook(() => useLoreStatus(), {
      wrapper: createWrapper(queryClient),
    });

    await waitFor(() => {
      expect(loreResult.current.isSuccess).toBe(true);
    });

    invoke.mockClear();

    // Simulate sync completed event
    act(() => {
      simulateEvent("sync-status", { status: "completed", message: "Done" });
    });

    await waitFor(() => {
      expect(invoke).toHaveBeenCalledWith("get_lore_status");
    });
  });
});
