/**
 * Tests for ActivateJailDialog (Task 7).
 *
 * Covers:
 *  - "Activate" button is disabled when pre-validation returns blocking issues.
 *  - "Activate" button is enabled when validation passes.
 *  - Dialog stays open and shows an error when the backend returns active=false.
 *  - `onActivated` is called and dialog closes when backend returns active=true.
 */

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 { ActivateJailDialog } from "../ActivateJailDialog";
import type { ActivateJailRequest, InactiveJail, JailActivationResponse, JailValidationResult } from "../../../types/config";

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

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

import { activateJail, validateJailConfig } from "../../../api/config";

const mockActivateJail = vi.mocked(activateJail);
const mockValidateJailConfig = vi.mocked(validateJailConfig);

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

const baseJail: InactiveJail = {
  name: "airsonic-auth",
  filter: "airsonic-auth",
  actions: ["iptables-multiport"],
  port: "8080",
  logpath: ["/var/log/airsonic/airsonic.log"],
  bantime: "10m",
  findtime: "10m",
  maxretry: 5,
  ban_time_seconds: 600,
  find_time_seconds: 600,
  log_encoding: "auto",
  backend: "auto",
  date_pattern: null,
  use_dns: "warn",
  prefregex: "",
  fail_regex: ["Failed login.*from <HOST>"],
  ignore_regex: [],
  bantime_escalation: null,
  source_file: "/config/fail2ban/jail.d/airsonic-auth.conf",
  enabled: false,
  has_local_override: false,
};

/** Successful activation response. */
const successResponse: JailActivationResponse = {
  name: "airsonic-auth",
  active: true,
  message: "Jail activated successfully.",
  fail2ban_running: true,
  validation_warnings: [],
};

/** Response when backend blocks activation (e.g. missing logpath). */
const blockedResponse: JailActivationResponse = {
  name: "airsonic-auth",
  active: false,
  message: "Jail 'airsonic-auth' cannot be activated: logpath does not exist.",
  fail2ban_running: true,
  validation_warnings: ["logpath: /var/log/airsonic/airsonic.log does not exist"],
};

/** Validation result with a logpath issue (should block the button). */
const validationWithLogpathIssue: JailValidationResult = {
  jail_name: "airsonic-auth",
  valid: false,
  issues: [{ field: "logpath", message: "/var/log/airsonic/airsonic.log does not exist" }],
};

/** Validation result with no issues. */
const validationPassed: JailValidationResult = {
  jail_name: "airsonic-auth",
  valid: true,
  issues: [],
};

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

interface DialogProps {
  jail?: InactiveJail | null;
  open?: boolean;
  onClose?: () => void;
  onActivated?: () => void;
  onValidate?: () => Promise<JailValidationResult>;
  onActivate?: (payload: ActivateJailRequest) => Promise<{
    name: string;
    active: boolean;
    message: string;
    fail2ban_running: boolean;
    validation_warnings: string[];
    recovered?: boolean;
  }>;
}

function renderDialog({
  jail = baseJail,
  open = true,
  onClose = vi.fn(),
  onActivated = vi.fn(),
  onValidate = async () => ({
    jail_name: jail?.name ?? "",
    valid: true,
    issues: [],
  }),
  onActivate = async () => ({
    name: jail?.name ?? "",
    active: true,
    message: "activated",
    fail2ban_running: true,
    validation_warnings: [],
  }),
}: DialogProps = {}) {
  return render(
    <FluentProvider theme={webLightTheme}>
      <ActivateJailDialog
        jail={jail}
        open={open}
        onClose={onClose}
        onActivated={onActivated}
        onValidate={onValidate}
        onActivate={onActivate}
      />
    </FluentProvider>,
  );
}

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

describe("ActivateJailDialog", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("disables the Activate button when pre-validation returns blocking issues", async () => {
    mockValidateJailConfig.mockResolvedValue(validationWithLogpathIssue);

    renderDialog();

    // Wait for validation to complete and the error message to appear.
    await waitFor(() => {
      expect(screen.getByText(/configuration errors detected/i)).toBeInTheDocument();
    });

    const activateBtn = screen.getByRole("button", { name: /^activate$/i });
    expect(activateBtn).toBeDisabled();
  });

  it("enables the Activate button when validation passes", async () => {
    mockValidateJailConfig.mockResolvedValue(validationPassed);

    renderDialog();

    // Wait for validation spinner to disappear.
    await waitFor(() => {
      expect(screen.queryByText(/validating configuration/i)).not.toBeInTheDocument();
    });

    const activateBtn = screen.getByRole("button", { name: /^activate$/i });
    expect(activateBtn).not.toBeDisabled();
  });

  it("keeps the dialog open and shows an error when backend returns active=false", async () => {
    mockValidateJailConfig.mockResolvedValue(validationPassed);
    mockActivateJail.mockResolvedValue(blockedResponse);

    const onActivated = vi.fn();
    renderDialog({ onActivated });

    // Wait for validation to finish.
    await waitFor(() => {
      expect(screen.queryByText(/validating configuration/i)).not.toBeInTheDocument();
    });

    const activateBtn = screen.getByRole("button", { name: /^activate$/i });
    await userEvent.click(activateBtn);

    // The server's error message should appear.
    await waitFor(() => {
      expect(
        screen.getByText(/cannot be activated/i),
      ).toBeInTheDocument();
    });

    // onActivated must NOT have been called.
    expect(onActivated).not.toHaveBeenCalled();
  });

  it("calls onActivated when backend returns active=true", async () => {
    mockValidateJailConfig.mockResolvedValue(validationPassed);
    mockActivateJail.mockResolvedValue(successResponse);

    const onActivated = vi.fn();
    renderDialog({ onActivated });

    await waitFor(() => {
      expect(screen.queryByText(/validating configuration/i)).not.toBeInTheDocument();
    });

    const activateBtn = screen.getByRole("button", { name: /^activate$/i });
    await userEvent.click(activateBtn);

    await waitFor(() => {
      expect(onActivated).toHaveBeenCalledOnce();
    });
  });
});
