import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";

describe("LoginForm", (): void => {
  const mockOnSubmit = vi.fn();

  beforeEach((): void => {
    mockOnSubmit.mockClear();
  });

  it("should render email and password fields with labels", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    expect(screen.getByLabelText("Email")).toBeInTheDocument();
    expect(screen.getByLabelText("Password")).toBeInTheDocument();
  });

  it("should render a Continue submit button", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    expect(screen.getByRole("button", { name: "Continue" })).toBeInTheDocument();
  });

  it("should auto-focus the email input on mount", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    const emailInput = screen.getByLabelText("Email");
    expect(document.activeElement).toBe(emailInput);
  });

  it("should validate email format on submit", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);

    const emailInput = screen.getByLabelText("Email");
    const passwordInput = screen.getByLabelText("Password");
    const submitButton = screen.getByRole("button", { name: "Continue" });

    await user.type(emailInput, "invalid-email");
    await user.type(passwordInput, "password123");
    await user.click(submitButton);

    expect(screen.getByText("Please enter a valid email address.")).toBeInTheDocument();
    expect(mockOnSubmit).not.toHaveBeenCalled();
  });

  it("should validate non-empty password on submit", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);

    const emailInput = screen.getByLabelText("Email");
    const submitButton = screen.getByRole("button", { name: "Continue" });

    await user.type(emailInput, "user@example.com");
    await user.click(submitButton);

    expect(screen.getByText("Password is recommended.")).toBeInTheDocument();
    expect(mockOnSubmit).not.toHaveBeenCalled();
  });

  it("should call onSubmit with email and password when valid", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);

    const emailInput = screen.getByLabelText("Email");
    const passwordInput = screen.getByLabelText("Password");
    const submitButton = screen.getByRole("button", { name: "Continue" });

    await user.type(emailInput, "user@example.com");
    await user.type(passwordInput, "password123");
    await user.click(submitButton);

    expect(mockOnSubmit).toHaveBeenCalledWith("user@example.com", "password123");
  });

  it("should show loading state with spinner and Signing in text", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} isLoading={true} />);
    expect(screen.getByText("Signing in...")).toBeInTheDocument();
    expect(screen.queryByText("Continue")).not.toBeInTheDocument();
  });

  it("should disable inputs when loading", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} isLoading={true} />);
    expect(screen.getByLabelText("Email")).toBeDisabled();
    expect(screen.getByLabelText("Password")).toBeDisabled();
    expect(screen.getByRole("button")).toBeDisabled();
  });

  it("should display error message when error prop is provided", (): void => {
    render(
      <LoginForm
        onSubmit={mockOnSubmit}
        error="The email and password combination wasn't recognized."
      />
    );
    expect(
      screen.getByText("The email and password combination wasn't recognized.")
    ).toBeInTheDocument();
  });

  it("should dismiss error when dismiss button is clicked", async (): Promise<void> => {
    const user = userEvent.setup();
    render(
      <LoginForm
        onSubmit={mockOnSubmit}
        error="Authentication paused. Please try again when ready."
      />
    );

    expect(
      screen.getByText("Authentication paused. Please try again when ready.")
    ).toBeInTheDocument();

    const dismissButton = screen.getByLabelText("Dismiss");
    await user.click(dismissButton);

    expect(
      screen.queryByText("Authentication paused. Please try again when ready.")
    ).not.toBeInTheDocument();
  });

  it("should have htmlFor on email label pointing to email input", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    const emailLabel = screen.getByText("Email");
    const emailInput = screen.getByLabelText("Email");
    expect(emailLabel).toHaveAttribute("for", emailInput.id);
  });

  it("should have htmlFor on password label pointing to password input", (): void => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    const passwordLabel = screen.getByText("Password");
    const passwordInput = screen.getByLabelText("Password");
    expect(passwordLabel).toHaveAttribute("for", passwordInput.id);
  });

  it("should clear email validation error when user types a valid email", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);

    const emailInput = screen.getByLabelText("Email");
    const submitButton = screen.getByRole("button", { name: "Continue" });

    // Trigger validation error
    await user.type(emailInput, "invalid");
    await user.click(submitButton);
    expect(screen.getByText("Please enter a valid email address.")).toBeInTheDocument();

    // Fix the email
    await user.clear(emailInput);
    await user.type(emailInput, "user@example.com");

    await waitFor((): void => {
      expect(screen.queryByText("Please enter a valid email address.")).not.toBeInTheDocument();
    });
  });

  it("should set aria-invalid on email input when validation fails", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);

    const emailInput = screen.getByLabelText("Email");
    const submitButton = screen.getByRole("button", { name: "Continue" });

    await user.type(emailInput, "invalid");
    await user.click(submitButton);

    expect(emailInput).toHaveAttribute("aria-invalid", "true");
  });
});
