/**
 * Tests for the AssignFilterDialog component (Task 2.3).
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { AssignFilterDialog } from "../AssignFilterDialog";
import type { JailListResponse } from "../../../types/jail";

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

vi.mock("../../../api/config", () => ({
  assignFilterToJail: vi.fn(),
}));

vi.mock("../../../api/jails", () => ({
  fetchJails: vi.fn(),
  startJail: vi.fn(),
  stopJail: vi.fn(),
  setJailIdle: vi.fn(),
  reloadJail: vi.fn(),
  reloadAllJails: vi.fn(),
}));

import { assignFilterToJail } from "../../../api/config";
import { fetchJails } from "../../../api/jails";

const mockAssignFilterToJail = vi.mocked(assignFilterToJail);
const mockFetchJails = vi.mocked(fetchJails);

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const mockJailsResponse: JailListResponse = {
  jails: [
    {
      name: "sshd",
      enabled: true,
      running: true,
      idle: false,
      backend: "systemd",
      find_time: 600,
      ban_time: 3600,
      max_retry: 5,
      status: null,
    },
    {
      name: "apache-auth",
      enabled: false,
      running: false,
      idle: false,
      backend: "polling",
      find_time: 600,
      ban_time: 3600,
      max_retry: 5,
      status: null,
    },
  ],
  total: 2,
};

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

function renderDialog(overrides: Partial<React.ComponentProps<typeof AssignFilterDialog>> = {}) {
  const props = {
    filterName: "nginx",
    open: true,
    onClose: vi.fn(),
    onAssigned: vi.fn(),
    onAssign: vi.fn(async () => undefined),
    ...overrides,
  };
  return render(
    <FluentProvider theme={webLightTheme}>
      <AssignFilterDialog {...props} />
    </FluentProvider>,
  );
}

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

describe("AssignFilterDialog", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockFetchJails.mockResolvedValue(mockJailsResponse);
  });

  it("renders dialog title when open", async () => {
    renderDialog();

    await waitFor(() => {
      expect(screen.getByText(/assign filter to jail/i)).toBeInTheDocument();
    });
  });

  it("shows the filter name in the dialog body", async () => {
    renderDialog({ filterName: "my-filter" });

    await waitFor(() => {
      expect(screen.getByText("my-filter")).toBeInTheDocument();
    });
  });

  it("fetches jails when dialog opens", async () => {
    renderDialog();

    await waitFor(() => {
      expect(mockFetchJails).toHaveBeenCalledTimes(1);
    });
  });

  it("only shows enabled jails in the dropdown", async () => {
    renderDialog();

    await waitFor(() => {
      // sshd is enabled, apache-auth is not
      expect(screen.getByRole("option", { name: "sshd" })).toBeInTheDocument();
      expect(
        screen.queryByRole("option", { name: "apache-auth" }),
      ).not.toBeInTheDocument();
    });
  });

  it("calls onClose when Cancel is clicked", async () => {
    const onClose = vi.fn();
    renderDialog({ onClose });

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /cancel/i }));
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("Assign button is disabled when no jail is selected", async () => {
    renderDialog();

    await waitFor(() => {
      const assignBtn = screen.getByRole("button", { name: /^assign$/i });
      expect(assignBtn).toBeDisabled();
    });
  });

  it("calls assignFilterToJail and onAssigned on confirm", async () => {
    const onAssigned = vi.fn();
    mockAssignFilterToJail.mockResolvedValue(undefined);

    renderDialog({ filterName: "nginx", onAssigned });

    // Wait for jails to load.
    await waitFor(() => {
      expect(screen.getByRole("option", { name: "sshd" })).toBeInTheDocument();
    });

    // Select a jail.
    fireEvent.change(screen.getByRole("combobox"), { target: { value: "sshd" } });

    // Click Assign.
    fireEvent.click(screen.getByRole("button", { name: /^assign$/i }));

    await waitFor(() => {
      expect(mockAssignFilterToJail).toHaveBeenCalledWith(
        "sshd",
        { filter_name: "nginx" },
        false,
      );
      expect(onAssigned).toHaveBeenCalledTimes(1);
    });
  });

  it("shows error message when assignment fails", async () => {
    mockAssignFilterToJail.mockRejectedValue(new Error("Assignment failed"));

    renderDialog({ filterName: "nginx" });

    await waitFor(() => {
      expect(screen.getByRole("option", { name: "sshd" })).toBeInTheDocument();
    });

    fireEvent.change(screen.getByRole("combobox"), { target: { value: "sshd" } });
    fireEvent.click(screen.getByRole("button", { name: /^assign$/i }));

    await waitFor(() => {
      expect(screen.getByText(/failed to assign filter/i)).toBeInTheDocument();
    });
  });
});
