/**
 * Tests for the "Ignore self" toggle in `JailDetailPage`.
 *
 * Verifies that:
 *  - The switch is checked when `ignoreSelf` is `true`.
 *  - The switch is unchecked when `ignoreSelf` is `false`.
 *  - Toggling the switch calls `toggleIgnoreSelf` with the correct boolean.
 *  - A failed toggle shows an error message bar.
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { JailDetailPage } from "../JailDetailPage";
import type { Jail } from "../../types/jail";
import type { UseJailDetailResult } from "../../hooks/useJails";

// ---------------------------------------------------------------------------
// Module mocks
// ---------------------------------------------------------------------------

/**
 * Stable mock function refs created before vi.mock() is hoisted.
 * We need `mockToggleIgnoreSelf` to be a vi.fn() that tests can inspect
 * and the rest to be no-ops that prevent real network calls.
 */
const {
  mockToggleIgnoreSelf,
  mockAddIp,
  mockRemoveIp,
  mockRefresh,
} = vi.hoisted(() => ({
  mockToggleIgnoreSelf: vi.fn<(on: boolean) => Promise<void>>(),
  mockAddIp: vi.fn<(ip: string) => Promise<void>>().mockResolvedValue(undefined),
  mockRemoveIp: vi.fn<(ip: string) => Promise<void>>().mockResolvedValue(undefined),
  mockRefresh: vi.fn(),
}));

// Mock the jail detail hook — tests control the returned state directly.
vi.mock("../../hooks/useJails", () => ({
  useJailDetail: vi.fn(),
  useJailBannedIps: vi.fn(() => ({
    items: [],
    total: 0,
    page: 1,
    pageSize: 25,
    search: "",
    loading: false,
    error: null,
    opError: null,
    refresh: vi.fn(),
    setPage: vi.fn(),
    setPageSize: vi.fn(),
    setSearch: vi.fn(),
    unban: vi.fn(),
  })),
}));

// Mock API functions used by JailInfoSection control buttons to avoid side effects.
vi.mock("../../api/jails", () => ({
  startJail: vi.fn().mockResolvedValue({ message: "ok", jail: "sshd" }),
  stopJail: vi.fn().mockResolvedValue({ message: "ok", jail: "sshd" }),
  reloadJail: vi.fn().mockResolvedValue({ message: "ok", jail: "sshd" }),
  setJailIdle: vi.fn().mockResolvedValue({ message: "ok", jail: "sshd" }),
  toggleIgnoreSelf: vi.fn().mockResolvedValue({ message: "ok", jail: "sshd" }),
}));

// Stub BannedIpsSection to prevent its own fetchJailBannedIps calls.
vi.mock("../../components/jail/BannedIpsSection", () => ({
  BannedIpsSection: () => <div data-testid="banned-ips-stub" />,
}));

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

import { useJailDetail } from "../../hooks/useJails";

/** Minimal `Jail` fixture. */
function makeJail(): Jail {
  return {
    name: "sshd",
    running: true,
    idle: false,
    backend: "systemd",
    log_paths: ["/var/log/auth.log"],
    fail_regex: ["^Failed .+ from <HOST>"],
    ignore_regex: [],
    date_pattern: "",
    log_encoding: "UTF-8",
    actions: ["iptables-multiport"],
    find_time: 600,
    ban_time: 3600,
    max_retry: 5,
    status: {
      currently_banned: 2,
      total_banned: 10,
      currently_failed: 0,
      total_failed: 50,
    },
    bantime_escalation: null,
  };
}

/** Wire `useJailDetail` to return the given `ignoreSelf` value. */
function mockHook(ignoreSelf: boolean): void {
  const result: UseJailDetailResult = {
    jail: makeJail(),
    ignoreList: ["10.0.0.0/8"],
    ignoreSelf,
    loading: false,
    error: null,
    refresh: mockRefresh,
    addIp: mockAddIp,
    removeIp: mockRemoveIp,
    toggleIgnoreSelf: mockToggleIgnoreSelf,
    start: vi.fn().mockResolvedValue(undefined),
    stop: vi.fn().mockResolvedValue(undefined),
    reload: vi.fn().mockResolvedValue(undefined),
    setIdle: vi.fn().mockResolvedValue(undefined),
  };
  vi.mocked(useJailDetail).mockReturnValue(result);
}

/** Render the JailDetailPage with a fake `/jails/sshd` route. */
function renderPage() {
  return render(
    <MemoryRouter initialEntries={["/jails/sshd"]}>
      <FluentProvider theme={webLightTheme}>
        <Routes>
          <Route path="/jails/:name" element={<JailDetailPage />} />
        </Routes>
      </FluentProvider>
    </MemoryRouter>,
  );
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("JailDetailPage — ignore self toggle", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockToggleIgnoreSelf.mockResolvedValue(undefined);
  });

  it("renders the switch checked when ignoreSelf is true", async () => {
    mockHook(true);
    renderPage();

    const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
    expect(switchEl).toBeChecked();
  });

  it("renders the switch unchecked when ignoreSelf is false", async () => {
    mockHook(false);
    renderPage();

    const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
    expect(switchEl).not.toBeChecked();
  });

  it("calls toggleIgnoreSelf(false) when switch is toggled off", async () => {
    mockHook(true);
    renderPage();
    const user = userEvent.setup();

    const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
    await user.click(switchEl);

    await waitFor(() => {
      expect(mockToggleIgnoreSelf).toHaveBeenCalledOnce();
      expect(mockToggleIgnoreSelf).toHaveBeenCalledWith(false);
    });
  });

  it("calls toggleIgnoreSelf(true) when switch is toggled on", async () => {
    mockHook(false);
    renderPage();
    const user = userEvent.setup();

    const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
    await user.click(switchEl);

    await waitFor(() => {
      expect(mockToggleIgnoreSelf).toHaveBeenCalledOnce();
      expect(mockToggleIgnoreSelf).toHaveBeenCalledWith(true);
    });
  });

  it("shows an error message bar when toggleIgnoreSelf rejects", async () => {
    mockHook(false);
    mockToggleIgnoreSelf.mockRejectedValue(new Error("Connection refused"));
    renderPage();
    const user = userEvent.setup();

    const switchEl = await screen.findByRole("switch", { name: /ignore self/i });
    await user.click(switchEl);

    await waitFor(() => {
      expect(screen.getByText(/Connection refused/i)).toBeInTheDocument();
    });
  });
});
