import { render, screen, fireEvent } from "@testing-library/react";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { BrowserRouter } from "react-router-dom";
import LoginForm from "../auth/LoginForm";

// Mock react-router-dom Link component
vi.mock("react-router-dom", async () => {
  const actual = await vi.importActual("react-router-dom");
  return {
    ...actual,
    Link: ({ to, children, className }: any) => (
      <a href={to} className={className} data-testid="forgot-password-link">
        {children}
      </a>
    ),
  };
});

// Wrapper component for Router context
const Wrapper = ({ children }: { children: React.ReactNode }) => (
  <BrowserRouter>{children}</BrowserRouter>
);

describe("LoginForm", () => {
  const mockSetEmail = vi.fn();
  const mockSetPassword = vi.fn();
  const mockSetShowPassword = vi.fn();
  const mockHandleLogin = vi.fn();

  const defaultProps = {
    email: "",
    setEmail: mockSetEmail,
    password: "",
    setPassword: mockSetPassword,
    showPassword: false,
    setShowPassword: mockSetShowPassword,
    isLoading: false,
    isSettingUpTables: false,
    loginError: null,
    handleLogin: mockHandleLogin,
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("rendering", () => {
    it("renders the login form with all fields", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      expect(screen.getByTestId("login-form")).toBeInTheDocument();
      expect(screen.getByLabelText("이메일")).toBeInTheDocument();
      expect(screen.getByLabelText("비밀번호")).toBeInTheDocument();
      expect(screen.getByText("로그인")).toBeInTheDocument();
      expect(screen.getByTestId("forgot-password-link")).toBeInTheDocument();
    });

    it("renders email field with correct attributes", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const emailInput = screen.getByLabelText("이메일");
      expect(emailInput).toHaveAttribute("type", "email");
      expect(emailInput).toHaveAttribute("id", "email");
      expect(emailInput).toHaveAttribute("placeholder", "your@email.com");
    });

    it("renders password field with correct attributes", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const passwordInput = screen.getByLabelText("비밀번호");
      expect(passwordInput).toHaveAttribute("type", "password");
      expect(passwordInput).toHaveAttribute("id", "password");
      expect(passwordInput).toHaveAttribute("placeholder", "••••••••");
    });

    it("renders forgot password link", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const forgotLink = screen.getByTestId("forgot-password-link");
      expect(forgotLink).toHaveAttribute("href", "/forgot-password");
      expect(forgotLink).toHaveTextContent("비밀번호를 잊으셨나요?");
    });
  });

  describe("form values", () => {
    it("displays current email value", () => {
      render(<LoginForm {...defaultProps} email="test@example.com" />, {
        wrapper: Wrapper,
      });

      expect(screen.getByDisplayValue("test@example.com")).toBeInTheDocument();
    });

    it("displays current password value", () => {
      render(<LoginForm {...defaultProps} password="mypassword" />, {
        wrapper: Wrapper,
      });

      expect(screen.getByDisplayValue("mypassword")).toBeInTheDocument();
    });

    it("calls setEmail when email input changes", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const emailInput = screen.getByLabelText("이메일");
      fireEvent.change(emailInput, { target: { value: "test@example.com" } });

      expect(mockSetEmail).toHaveBeenCalledWith("test@example.com");
    });

    it("calls setPassword when password input changes", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const passwordInput = screen.getByLabelText("비밀번호");
      fireEvent.change(passwordInput, { target: { value: "newpassword" } });

      expect(mockSetPassword).toHaveBeenCalledWith("newpassword");
    });
  });

  describe("password visibility toggle", () => {
    it("shows password as hidden by default", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const passwordInput = screen.getByLabelText("비밀번호");
      expect(passwordInput).toHaveAttribute("type", "password");
    });

    it("shows password as text when showPassword is true", () => {
      render(<LoginForm {...defaultProps} showPassword={true} />, {
        wrapper: Wrapper,
      });

      const passwordInput = screen.getByLabelText("비밀번호");
      expect(passwordInput).toHaveAttribute("type", "text");
    });

    it("calls setShowPassword when visibility toggle is clicked", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      // Find the password toggle button (the one that's not the submit button)
      const buttons = screen.getAllByRole("button");
      const toggleButton = buttons.find(
        (btn) => btn.getAttribute("type") === "button"
      );

      fireEvent.click(toggleButton!);

      expect(mockSetShowPassword).toHaveBeenCalledWith(true);
    });

    it("calls setShowPassword with opposite value", () => {
      render(<LoginForm {...defaultProps} showPassword={true} />, {
        wrapper: Wrapper,
      });

      // Find the password toggle button (the one that's not the submit button)
      const buttons = screen.getAllByRole("button");
      const toggleButton = buttons.find(
        (btn) => btn.getAttribute("type") === "button"
      );

      fireEvent.click(toggleButton!);

      expect(mockSetShowPassword).toHaveBeenCalledWith(false);
    });
  });

  describe("form submission", () => {
    it("calls handleLogin when form is submitted", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const form = screen.getByTestId("login-form");
      fireEvent.submit(form);

      expect(mockHandleLogin).toHaveBeenCalledTimes(1);
    });

    it("does not submit when form is disabled during loading", () => {
      render(<LoginForm {...defaultProps} isLoading={true} />, {
        wrapper: Wrapper,
      });

      const loginButton = screen.getByText("로그인 중...");
      expect(loginButton).toBeDisabled();
    });

    it("does not submit when form is disabled during table setup", () => {
      render(<LoginForm {...defaultProps} isSettingUpTables={true} />, {
        wrapper: Wrapper,
      });

      const loginButton = screen.getByText("데이터베이스 설정 중...");
      expect(loginButton).toBeDisabled();
    });
  });

  describe("loading states", () => {
    it("shows loading text when isLoading is true", () => {
      render(<LoginForm {...defaultProps} isLoading={true} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText("로그인 중...")).toBeInTheDocument();
      const submitButton = screen.getByText("로그인 중...").closest("button");
      expect(submitButton).toBeDisabled();
    });

    it("shows table setup text when isSettingUpTables is true", () => {
      render(<LoginForm {...defaultProps} isSettingUpTables={true} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText("데이터베이스 설정 중...")).toBeInTheDocument();
      const submitButton = screen
        .getByText("데이터베이스 설정 중...")
        .closest("button");
      expect(submitButton).toBeDisabled();
    });

    it("shows normal text when not loading", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      expect(screen.getByText("로그인")).toBeInTheDocument();
      const submitButton = screen.getByText("로그인").closest("button");
      expect(submitButton).not.toBeDisabled();
    });

    it("isLoading takes precedence over isSettingUpTables", () => {
      render(
        <LoginForm
          {...defaultProps}
          isLoading={true}
          isSettingUpTables={true}
        />,
        { wrapper: Wrapper }
      );

      expect(screen.getByText("로그인 중...")).toBeInTheDocument();
    });
  });

  describe("error handling", () => {
    it("does not show error message when loginError is null", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      expect(screen.queryByText(/에러/)).not.toBeInTheDocument();
    });

    it("shows regular error message for standard errors", () => {
      const errorMessage = "잘못된 이메일 또는 비밀번호입니다.";
      render(<LoginForm {...defaultProps} loginError={errorMessage} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(errorMessage)).toBeInTheDocument();
    });

    it("shows CORS/JSON error with special styling and suggestions", () => {
      const corsError = "CORS 정책에 의해 차단되었습니다.";
      render(<LoginForm {...defaultProps} loginError={corsError} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(corsError)).toBeInTheDocument();
      expect(
        screen.getByText(/다른 CORS 프록시 유형을 시도해 보세요/)
      ).toBeInTheDocument();
      expect(
        screen.getByText(/HTTPS URL을 사용하는 Supabase 인스턴스로 변경/)
      ).toBeInTheDocument();
      expect(
        screen.getByText(/네트워크 연결 상태를 확인하세요/)
      ).toBeInTheDocument();
    });

    it("detects JSON errors correctly", () => {
      const jsonError = "JSON 파싱 오류가 발생했습니다.";
      render(<LoginForm {...defaultProps} loginError={jsonError} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(jsonError)).toBeInTheDocument();
      expect(
        screen.getByText(/다른 CORS 프록시 유형을 시도해 보세요/)
      ).toBeInTheDocument();
    });

    it("detects network 404 errors correctly", () => {
      const networkError = "404 Not Found 오류입니다.";
      render(<LoginForm {...defaultProps} loginError={networkError} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(networkError)).toBeInTheDocument();
      expect(
        screen.getByText(/네트워크 연결 상태를 확인하세요/)
      ).toBeInTheDocument();
    });

    it("detects proxy errors correctly", () => {
      const proxyError = "프록시 서버 응답 오류입니다.";
      render(<LoginForm {...defaultProps} loginError={proxyError} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(proxyError)).toBeInTheDocument();
      expect(
        screen.getByText(/다른 CORS 프록시 유형을 시도해 보세요/)
      ).toBeInTheDocument();
    });
  });

  describe("CSS classes and styling", () => {
    it("applies correct CSS classes to form container", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const container = screen.getByTestId("login-form").parentElement;
      expect(container).toHaveClass("neuro-flat", "p-8", "mb-6");
    });

    it("applies correct CSS classes to email input", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const emailInput = screen.getByLabelText("이메일");
      expect(emailInput).toHaveClass("pl-10", "neuro-pressed");
    });

    it("applies correct CSS classes to password input", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const passwordInput = screen.getByLabelText("비밀번호");
      expect(passwordInput).toHaveClass("pl-10", "neuro-pressed");
    });

    it("applies correct CSS classes to submit button", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const submitButton = screen.getByRole("button", { name: /로그인/ });
      expect(submitButton).toHaveClass(
        "w-full",
        "hover:bg-neuro-income/80",
        "text-white",
        "h-auto",
        "bg-neuro-income",
        "text-lg"
      );
    });
  });

  describe("accessibility", () => {
    it("has proper form labels", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      expect(screen.getByLabelText("이메일")).toBeInTheDocument();
      expect(screen.getByLabelText("비밀번호")).toBeInTheDocument();
    });

    it("has proper input IDs matching labels", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const emailInput = screen.getByLabelText("이메일");
      const passwordInput = screen.getByLabelText("비밀번호");

      expect(emailInput).toHaveAttribute("id", "email");
      expect(passwordInput).toHaveAttribute("id", "password");
    });

    it("password toggle button has correct type", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      // Find the eye icon button (the one that's not the submit button)
      const buttons = screen.getAllByRole("button");
      const toggleButton = buttons.find(
        (button) => button.getAttribute("type") === "button"
      );

      expect(toggleButton).toHaveAttribute("type", "button");
    });

    it("submit button has correct type", () => {
      render(<LoginForm {...defaultProps} />, { wrapper: Wrapper });

      const submitButton = screen.getByText("로그인").closest("button");
      expect(submitButton).toHaveAttribute("type", "submit");
    });
  });

  describe("edge cases", () => {
    it("handles empty email and password values", () => {
      render(<LoginForm {...defaultProps} email="" password="" />, {
        wrapper: Wrapper,
      });

      const emailInput = screen.getByLabelText("이메일");
      const passwordInput = screen.getByLabelText("비밀번호");

      expect(emailInput).toHaveValue("");
      expect(passwordInput).toHaveValue("");
    });

    it("handles very long error messages", () => {
      const longError = "A".repeat(1000);
      render(<LoginForm {...defaultProps} loginError={longError} />, {
        wrapper: Wrapper,
      });

      expect(screen.getByText(longError)).toBeInTheDocument();
    });

    it("handles special characters in email and password", () => {
      const specialEmail = "test+tag@example-domain.co.uk";
      const specialPassword = "P@ssw0rd!#$%";

      render(
        <LoginForm
          {...defaultProps}
          email={specialEmail}
          password={specialPassword}
        />,
        { wrapper: Wrapper }
      );

      expect(screen.getByDisplayValue(specialEmail)).toBeInTheDocument();
      expect(screen.getByDisplayValue(specialPassword)).toBeInTheDocument();
    });

    it("maintains form functionality during rapid state changes", () => {
      const { rerender } = render(<LoginForm {...defaultProps} />, {
        wrapper: Wrapper,
      });

      // Rapid state changes
      rerender(<LoginForm {...defaultProps} isLoading={true} />);
      rerender(<LoginForm {...defaultProps} isSettingUpTables={true} />);
      rerender(<LoginForm {...defaultProps} loginError="Error" />);
      rerender(<LoginForm {...defaultProps} />);

      // Form should still be functional
      expect(screen.getByTestId("login-form")).toBeInTheDocument();
      expect(screen.getByLabelText("이메일")).toBeInTheDocument();
      expect(screen.getByLabelText("비밀번호")).toBeInTheDocument();
    });
  });
});
