import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { SetupPage } from "../SetupPage";

// Mock the setup API so tests never hit a real network.
vi.mock("../../api/setup", () => ({
  getSetupStatus: vi.fn(),
  submitSetup: vi.fn(),
}));

import { getSetupStatus, submitSetup } from "../../api/setup";

const mockedGetSetupStatus = vi.mocked(getSetupStatus);
const mockedSubmitSetup = vi.mocked(submitSetup);

function renderPage() {
  return render(
    <FluentProvider theme={webLightTheme}>
      <MemoryRouter initialEntries={["/setup"]}>
        <Routes>
          <Route path="/setup" element={<SetupPage />} />
          <Route
            path="/login"
            element={<div data-testid="login-page">Login</div>}
          />
        </Routes>
      </MemoryRouter>
    </FluentProvider>,
  );
}

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

  it("shows a full-screen spinner while the setup status check is in flight", () => {
    // getSetupStatus never resolves — spinner should be visible immediately.
    mockedGetSetupStatus.mockReturnValue(new Promise(() => {}));
    renderPage();
    expect(screen.getByRole("progressbar")).toBeInTheDocument();
    // Form should NOT be visible yet.
    expect(
      screen.queryByRole("heading", { name: /bangui setup/i }),
    ).not.toBeInTheDocument();
  });

  it("renders the setup form once the status check resolves (not complete)", async () => {
    // Task 0.4: form must not flash before the check resolves.
    mockedGetSetupStatus.mockResolvedValue({ completed: false });
    renderPage();
    await waitFor(() => {
      expect(
        screen.getByRole("heading", { name: /bangui setup/i }),
      ).toBeInTheDocument();
    });
    // Spinner should be gone.
    expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
  });

  it("displays password complexity feedback while the user types", async () => {
    mockedGetSetupStatus.mockResolvedValue({ completed: false });
    renderPage();

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

    const user = userEvent.setup();
    const passwordInput = screen.getByLabelText(/master password/i);
    await user.type(passwordInput, "Short1");

    expect(screen.getByText(/2 of 4 rules satisfied/i)).toBeInTheDocument();
    expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument();
    expect(screen.getByText(/at least one uppercase letter/i)).toBeInTheDocument();
    expect(screen.getByText(/at least one special character/i)).toBeInTheDocument();
  });

  it("does not submit the form when the password is too weak", async () => {
    mockedGetSetupStatus.mockResolvedValue({ completed: false });
    mockedSubmitSetup.mockResolvedValue({ message: "Setup completed successfully. Please log in." });
    renderPage();

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

    const user = userEvent.setup();
    await user.type(screen.getByLabelText(/master password/i), "Short1");
    await user.type(screen.getByLabelText(/confirm password/i), "Short1");
    await user.click(screen.getByRole("button", { name: /complete setup/i }));

    expect(mockedSubmitSetup).not.toHaveBeenCalled();
  });

  it("redirects to /login when setup is already complete", async () => {
    mockedGetSetupStatus.mockResolvedValue({ completed: true });
    renderPage();
    await waitFor(() => {
      expect(screen.getByTestId("login-page")).toBeInTheDocument();
    });
  });

  it("renders the form and surfaces the error message when the status check fails", async () => {
    mockedGetSetupStatus.mockRejectedValue(new Error("Connection refused"));
    renderPage();
    await waitFor(() => {
      expect(
        screen.getByRole("heading", { name: /bangui setup/i }),
      ).toBeInTheDocument();
    });
    expect(screen.getByText(/connection refused/i)).toBeInTheDocument();
  });
});
