import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import * as jailsApi from "../../api/jails";
import { useJailDetail } from "../useJails";
import type { Jail } from "../../types/jail";

// Mock the API module
vi.mock("../../api/jails");

const mockJail: Jail = {
  name: "sshd",
  running: true,
  idle: false,
  backend: "pyinotify",
  log_paths: ["/var/log/auth.log"],
  fail_regex: ["^\\[.*\\]\\s.*Failed password"],
  ignore_regex: [],
  date_pattern: "%b %d %H:%M:%S",
  log_encoding: "UTF-8",
  actions: [],
  find_time: 600,
  ban_time: 600,
  max_retry: 5,
  status: null,
  bantime_escalation: null,
};

describe("useJailDetail control methods", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(jailsApi.fetchJail).mockResolvedValue({
      jail: mockJail,
      ignore_list: [],
      ignore_self: false,
    });
  });

  it("calls start() and refetches jail data", async () => {
    vi.mocked(jailsApi.startJail).mockResolvedValue({ message: "jail started", jail: "sshd" });

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    expect(result.current.jail?.name).toBe("sshd");
    expect(jailsApi.startJail).not.toHaveBeenCalled();

    // Call start()
    await act(async () => {
      await result.current.start();
    });

    expect(jailsApi.startJail).toHaveBeenCalledWith("sshd");
    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after start
  });

  it("calls stop() and refetches jail data", async () => {
    vi.mocked(jailsApi.stopJail).mockResolvedValue({ message: "jail stopped", jail: "sshd" });

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call stop()
    await act(async () => {
      await result.current.stop();
    });

    expect(jailsApi.stopJail).toHaveBeenCalledWith("sshd");
    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after stop
  });

  it("calls reload() and refetches jail data", async () => {
    vi.mocked(jailsApi.reloadJail).mockResolvedValue({ message: "jail reloaded", jail: "sshd" });

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call reload()
    await act(async () => {
      await result.current.reload();
    });

    expect(jailsApi.reloadJail).toHaveBeenCalledWith("sshd");
    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2); // Initial fetch + refetch after reload
  });

  it("calls setIdle() with correct parameter and refetches jail data", async () => {
    vi.mocked(jailsApi.setJailIdle).mockResolvedValue({ message: "jail idle toggled", jail: "sshd" });

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call setIdle(true)
    await act(async () => {
      await result.current.setIdle(true);
    });

    expect(jailsApi.setJailIdle).toHaveBeenCalledWith("sshd", true);
    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2);

    // Reset mock to verify second call
    vi.mocked(jailsApi.setJailIdle).mockClear();
    vi.mocked(jailsApi.fetchJail).mockResolvedValue({
      jail: { ...mockJail, idle: true },
      ignore_list: [],
      ignore_self: false,
    });

    // Call setIdle(false)
    await act(async () => {
      await result.current.setIdle(false);
    });

    expect(jailsApi.setJailIdle).toHaveBeenCalledWith("sshd", false);
  });

  it("propagates errors from start()", async () => {
    const error = new Error("Failed to start jail");
    vi.mocked(jailsApi.startJail).mockRejectedValue(error);

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call start() and expect it to throw
    await expect(
      act(async () => {
        await result.current.start();
      }),
    ).rejects.toThrow("Failed to start jail");
  });

  it("propagates errors from stop()", async () => {
    const error = new Error("Failed to stop jail");
    vi.mocked(jailsApi.stopJail).mockRejectedValue(error);

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call stop() and expect it to throw
    await expect(
      act(async () => {
        await result.current.stop();
      }),
    ).rejects.toThrow("Failed to stop jail");
  });

  it("propagates errors from reload()", async () => {
    const error = new Error("Failed to reload jail");
    vi.mocked(jailsApi.reloadJail).mockRejectedValue(error);

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call reload() and expect it to throw
    await expect(
      act(async () => {
        await result.current.reload();
      }),
    ).rejects.toThrow("Failed to reload jail");
  });

  it("propagates errors from setIdle()", async () => {
    const error = new Error("Failed to set idle mode");
    vi.mocked(jailsApi.setJailIdle).mockRejectedValue(error);

    const { result } = renderHook(() => useJailDetail("sshd"));

    // Wait for initial fetch
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    // Call setIdle() and expect it to throw
    await expect(
      act(async () => {
        await result.current.setIdle(true);
      }),
    ).rejects.toThrow("Failed to set idle mode");
  });
});
