// @vitest-environment jsdom

import { act, renderHook, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { jsonError, jsonOk } from "@/test/api-mock";
import {
  makeLogsPayload,
  makeSummaryPayload,
  makeTrendsPayload,
} from "../test-helpers";
import { useObserveData } from "./use-observe-data";

describe("useObserveData", () => {
  beforeEach(() => {
    vi.restoreAllMocks();
    vi.useRealTimers();
  });

  it("loads summary, trends, and logs on mount", async () => {
    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const inputText = String(input);
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return jsonOk(makeSummaryPayload());
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return jsonOk(makeTrendsPayload());
      }
      if (inputText.endsWith("/api/observe/acs/logs")) {
        return jsonOk(makeLogsPayload());
      }
      return jsonOk({});
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await waitFor(() => {
      expect(result.current.summary).not.toBeNull();
      expect(result.current.trends).not.toBeNull();
      expect(result.current.logs).not.toBeNull();
    });
  });

  it("captures summary, trends, and logs request errors independently", async () => {
    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const inputText = String(input);
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return jsonError("INTERNAL", "summary failed");
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return jsonError("INTERNAL", "trends failed");
      }
      if (inputText.endsWith("/api/observe/acs/logs")) {
        return jsonError("INTERNAL", "logs failed");
      }
      return jsonOk({});
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await waitFor(() => {
      expect(result.current.summaryError).toBe("summary failed");
      expect(result.current.trendsError).toBe("trends failed");
      expect(result.current.logsError).toBe("logs failed");
    });
  });

  it("clears a section request error after a successful refresh", async () => {
    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const inputText = String(input);
      const summaryCalls = fetchMock.mock.calls.filter(([calledInput]) =>
        String(calledInput).endsWith("/api/observe/acs/summary")
      ).length;

      if (inputText.endsWith("/api/observe/acs/summary") && summaryCalls === 1) {
        return jsonError("INTERNAL", "summary failed");
      }
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return jsonOk(makeSummaryPayload());
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return jsonOk(makeTrendsPayload());
      }
      if (inputText.endsWith("/api/observe/acs/logs")) {
        return jsonOk(makeLogsPayload());
      }
      return jsonOk({});
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await waitFor(() => {
      expect(result.current.summaryError).toBe("summary failed");
    });

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

    await waitFor(() => {
      expect(result.current.summaryError).toBe("");
      expect(result.current.summary).not.toBeNull();
    });
  });

  it("times out a blocked section request without blocking other data", async () => {
    vi.useFakeTimers();
    const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
      const inputText = String(input);
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return new Promise<Response>((_resolve, reject) => {
          init?.signal?.addEventListener("abort", () => {
            reject(new DOMException("aborted", "AbortError"));
          });
        });
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return Promise.resolve(jsonOk(makeTrendsPayload()));
      }
      if (inputText.endsWith("/api/observe/acs/logs")) {
        return Promise.resolve(jsonOk(makeLogsPayload()));
      }
      return Promise.resolve(jsonOk({}));
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await act(async () => {
      await Promise.resolve();
      await Promise.resolve();
    });
    expect(result.current.trends).not.toBeNull();
    expect(result.current.logs).not.toBeNull();

    act(() => {
      vi.advanceTimersByTime(60_000);
    });
    await act(async () => {
      await Promise.resolve();
      await Promise.resolve();
    });
    expect(result.current.summaryError).toBe("Summary request timed out");
    expect(result.current.loading).toBe(false);
  });

  it("applies log filter to subsequent logs request", async () => {
    // init is part of the fetch signature; the mock ignores it but later
    // assertions inspect fetchMock.mock.calls to read the request body.
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
      const inputText = String(input);
      if (inputText.endsWith("/api/observe/acs/logs")) {
        return new Response(
          JSON.stringify({
            ok: true,
            data: makeLogsPayload({
              regions: [],
              coverage: {
                selected: 1,
                ready: 0,
                missingDatasource: 1,
                queryError: 0,
              },
            }),
          })
        );
      }
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return jsonOk(makeSummaryPayload());
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return jsonOk(makeTrendsPayload());
      }
      return jsonOk({});
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalled();
    });

    act(() => {
      result.current.applyLogsFilterAndLoad({ mode: "exclude", keyword: "timeout" });
    });

    await waitFor(() => {
      expect(
        fetchMock.mock.calls.some(([input, init]) => {
          const body = init?.body ? JSON.parse(String(init.body)) : {};
          return (
            String(input).endsWith("/api/observe/acs/logs") &&
            body.lineFilterMode === "exclude" &&
            body.lineFilterKeyword === "timeout"
          );
        })
      ).toBe(true);
    });
  });

  it("keeps existing logs visible while a logs refresh is pending", async () => {
    let resolveSecondLogs: ((response: Response) => void) | undefined;
    const fetchMock = vi.fn((input: RequestInfo | URL) => {
      const inputText = String(input);
      if (inputText.endsWith("/api/observe/acs/logs")) {
        const logsCalls = fetchMock.mock.calls.filter(([calledInput]) =>
          String(calledInput).endsWith("/api/observe/acs/logs")
        ).length;

        if (logsCalls === 1) {
          return Promise.resolve(jsonOk(makeLogsPayload()));
        }

        return new Promise<Response>((resolve) => {
          resolveSecondLogs = resolve;
        });
      }
      if (inputText.endsWith("/api/observe/acs/summary")) {
        return Promise.resolve(jsonOk(makeSummaryPayload()));
      }
      if (inputText.endsWith("/api/observe/acs/resource-trends")) {
        return Promise.resolve(jsonOk(makeTrendsPayload()));
      }
      return Promise.resolve(jsonOk({}));
    });
    vi.stubGlobal("fetch", fetchMock);

    const regionIds = ["r1"];
    const { result } = renderHook(() => useObserveData(regionIds));

    await waitFor(() => {
      expect(result.current.logs).not.toBeNull();
    });
    const currentLogs = result.current.logs;

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

    await waitFor(() => {
      expect(result.current.logsLoading).toBe(true);
    });
    expect(result.current.logs).toBe(currentLogs);

    act(() => {
      resolveSecondLogs?.(jsonOk(makeLogsPayload({ regions: [] })));
    });

    await waitFor(() => {
      expect(result.current.logsLoading).toBe(false);
    });
  });
});
