/**
 * Tests for RecoveryBanner (Task 3).
 */

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 } from "react-router-dom";
import { RecoveryBanner } from "../RecoveryBanner";
import type { PendingRecovery } from "../../../types/config";

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

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

import { fetchPendingRecovery, rollbackJail } from "../../../api/config";

const mockFetchPendingRecovery = vi.mocked(fetchPendingRecovery);
const mockRollbackJail = vi.mocked(rollbackJail);

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

const pendingRecord: PendingRecovery = {
  jail_name: "sshd",
  activated_at: "2024-01-01T12:00:00Z",
  detected_at: "2024-01-01T12:00:30Z",
  recovered: false,
};

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

function renderBanner() {
  return render(
    <FluentProvider theme={webLightTheme}>
      <MemoryRouter>
        <RecoveryBanner />
      </MemoryRouter>
    </FluentProvider>,
  );
}

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

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

  it("renders nothing when pending recovery is null", async () => {
    mockFetchPendingRecovery.mockResolvedValue(null);

    renderBanner();

    await waitFor(() => {
      expect(mockFetchPendingRecovery).toHaveBeenCalled();
    });

    expect(screen.queryByRole("alert")).not.toBeInTheDocument();
  });

  it("renders warning when there is an unresolved pending recovery", async () => {
    mockFetchPendingRecovery.mockResolvedValue(pendingRecord);

    renderBanner();

    await waitFor(() => {
      expect(screen.getByText(/fail2ban stopped responding after activating jail/i)).toBeInTheDocument();
    });

    expect(screen.getByText(/sshd/i)).toBeInTheDocument();
    expect(screen.getByRole("button", { name: /disable & restart/i })).toBeInTheDocument();
    expect(screen.getByRole("button", { name: /view logs/i })).toBeInTheDocument();
  });

  it("hides the banner when recovery is marked as recovered", async () => {
    const recoveredRecord: PendingRecovery = { ...pendingRecord, recovered: true };
    mockFetchPendingRecovery.mockResolvedValue(recoveredRecord);

    renderBanner();

    await waitFor(() => {
      expect(mockFetchPendingRecovery).toHaveBeenCalled();
    });

    expect(screen.queryByRole("alert")).not.toBeInTheDocument();
  });

  it("calls rollbackJail and hides banner on successful rollback", async () => {
    mockFetchPendingRecovery.mockResolvedValue(pendingRecord);
    mockRollbackJail.mockResolvedValue({
      jail_name: "sshd",
      disabled: true,
      fail2ban_running: true,
      active_jails: 0,
      message: "Rolled back.",
    });

    renderBanner();

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

    await userEvent.click(
      screen.getByRole("button", { name: /disable & restart/i }),
    );

    expect(mockRollbackJail).toHaveBeenCalledWith("sshd");
  });

  it("shows rollback error when rollbackJail fails", async () => {
    mockFetchPendingRecovery.mockResolvedValue(pendingRecord);
    mockRollbackJail.mockRejectedValue(new Error("Connection refused"));

    renderBanner();

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

    await userEvent.click(
      screen.getByRole("button", { name: /disable & restart/i }),
    );

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