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

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

const mockJail: Jail = {
  name: "sshd",
  enabled: true,
  running: true,
  idle: false,
  backend: "pyinotify",
  log_paths: ["/var/log/auth.log"],
  fail_regex: ["^\[.*\]\s.*Failed password"],
  ignore_regex: [],
  ignore_ips: [],
  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("useJailData — fetch state", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(jailsApi.fetchJail).mockResolvedValue({
      jail: mockJail,
      ignore_list: [],
      ignore_self: false,
    });
  });

  it("fetches jail data on mount", async () => {
    const { result } = renderHook(() => useJailData("sshd"));

    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    expect(result.current.jail?.name).toBe("sshd");
    expect(result.current.ignoreList).toEqual([]);
    expect(result.current.ignoreSelf).toBe(false);
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBe(null);
  });

  it("exposes refresh function to refetch data", async () => {
    const { result } = renderHook(() => useJailData("sshd"));

    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(1);

    await act(async () => {
      result.current.refresh();
      await new Promise((r) => setTimeout(r, 0));
    });

    expect(jailsApi.fetchJail).toHaveBeenCalledTimes(2);
  });

  it("handles fetch errors", async () => {
    const error = new Error("Network error");
    vi.mocked(jailsApi.fetchJail).mockRejectedValue(error);

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

    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });

    expect(result.current.error).toBeTruthy();
    expect(result.current.loading).toBe(false);
  });
});

describe("useJailCommands — write operations", () => {
  const mockOnSuccess = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(jailsApi.startJail).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.stopJail).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.reloadJail).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.setJailIdle).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.addIgnoreIp).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.delIgnoreIp).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
    vi.mocked(jailsApi.toggleIgnoreSelf).mockResolvedValue({ message: "ok", jail: "sshd", success: true });
  });

  it("calls start() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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

    expect(jailsApi.startJail).toHaveBeenCalledWith("sshd");
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls stop() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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

    expect(jailsApi.stopJail).toHaveBeenCalledWith("sshd");
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls reload() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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

    expect(jailsApi.reloadJail).toHaveBeenCalledWith("sshd");
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls setIdle() API with parameter and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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

    expect(jailsApi.setJailIdle).toHaveBeenCalledWith("sshd", true);
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls addIp() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

    await act(async () => {
      await result.current.addIp("192.168.1.1");
    });

    expect(jailsApi.addIgnoreIp).toHaveBeenCalledWith("sshd", "192.168.1.1");
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls removeIp() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

    await act(async () => {
      await result.current.removeIp("192.168.1.1");
    });

    expect(jailsApi.delIgnoreIp).toHaveBeenCalledWith("sshd", "192.168.1.1");
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

  it("calls toggleIgnoreSelf() API and invokes onSuccess", async () => {
    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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

    expect(jailsApi.toggleIgnoreSelf).toHaveBeenCalledWith("sshd", true);
    expect(mockOnSuccess).toHaveBeenCalledOnce();
  });

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

    const { result } = renderHook(() => useJailCommands("sshd", mockOnSuccess));

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