import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
import { render, screen, fireEvent, cleanup, waitFor } from "@testing-library/react";

import LoginPage from "@/app/login/page";

// next/navigation 의 useRouter 를 목으로 대체해 리다이렉트 동작을 검증한다.
export const pushMock = vi.fn();

vi.mock("next/navigation", () => {
  return {
    __esModule: true,
    useRouter: () => ({ push: pushMock }),
  };
});

describe("LoginPage", () => {
  beforeEach(() => {
    vi.stubGlobal("fetch", vi.fn());
  });

  afterEach(() => {
    cleanup();
    vi.unstubAllGlobals();
    vi.clearAllMocks();
  });

  it("올바른 이메일/비밀번호로 로그인하면 /api/auth/login 으로 요청을 보내고 /dashboard 로 이동해야 한다", async () => {
    const fetchMock = vi.fn().mockImplementation((input: any, init?: any) => {
      const url = typeof input === "string" ? input : input.url;
      const method = init?.method ?? "GET";

      if (url === "/api/auth/me" && method === "GET") {
        return Promise.resolve(
          new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
            status: 401,
            headers: { "Content-Type": "application/json" },
          }),
        );
      }

      if (url === "/api/auth/login" && method === "POST") {
        return Promise.resolve(
          new Response(JSON.stringify({ id: "1", email: "user@example.com" }), {
            status: 200,
            headers: { "Content-Type": "application/json" },
          }),
        );
      }

      return Promise.resolve(new Response(null, { status: 500 }));
    });

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

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

    fireEvent.change(emailInput, { target: { value: "user@example.com" } });
    fireEvent.change(passwordInput, { target: { value: "securePass1" } });

    fireEvent.click(submitButton);

    await waitFor(() => {
      const loginCall = fetchMock.mock.calls.find(([url, options]) => {
        return url === "/api/auth/login" && options?.method === "POST";
      });

      expect(loginCall).toBeDefined();
    });

    const loginCall = fetchMock.mock.calls.find(([url]) => url === "/api/auth/login") as any;
    const [url, options] = loginCall;
    expect(url).toBe("/api/auth/login");
    expect(options.method).toBe("POST");
    expect(options.headers["Content-Type"]).toBe("application/json");

    const body = JSON.parse(options.body);
    expect(body.email).toBe("user@example.com");
    expect(body.password).toBe("securePass1");

    await waitFor(() => {
      expect(pushMock).toHaveBeenCalledWith("/dashboard");
    });
  });

  it("로그인 실패 시 에러 메시지를 화면에 표시해야 한다", async () => {
    const fetchMock = vi.fn().mockImplementation((input: any, init?: any) => {
      const url = typeof input === "string" ? input : input.url;
      const method = init?.method ?? "GET";

      if (url === "/api/auth/me" && method === "GET") {
        return Promise.resolve(
          new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
            status: 401,
            headers: { "Content-Type": "application/json" },
          }),
        );
      }

      if (url === "/api/auth/login" && method === "POST") {
        return Promise.resolve(
          new Response(JSON.stringify({ message: "이메일 또는 비밀번호가 올바르지 않습니다." }), {
            status: 401,
            headers: { "Content-Type": "application/json" },
          }),
        );
      }

      return Promise.resolve(new Response(null, { status: 500 }));
    });

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

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

    fireEvent.change(emailInput, { target: { value: "user@example.com" } });
    fireEvent.change(passwordInput, { target: { value: "wrongpass" } });

    fireEvent.click(submitButton);

    const errorText = await screen.findByText(/이메일 또는 비밀번호가 올바르지 않습니다./);
    expect(errorText).toBeTruthy();
  });

  it("이미 로그인된 상태에서 /login 에 접근하면 /dashboard 로 리다이렉트해야 한다", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ id: "1", email: "user@example.com", tokenVersion: 1 }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }),
    );

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledTimes(1);
      expect(fetchMock.mock.calls[0][0]).toBe("/api/auth/me");
      expect(pushMock).toHaveBeenCalledWith("/dashboard");
    });
  });

  it("로그인 페이지는 전역 라이트/다크 테마 배경 클래스를 사용해야 한다", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
        status: 401,
        headers: { "Content-Type": "application/json" },
      }),
    );

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

    const main = await screen.findByRole("main");
    const className = (main as HTMLElement).className;

    expect(className).toContain("bg-slate-100");
    expect(className).toContain("dark:bg-slate-950");
  });

  it("비밀번호 입력 필드는 이메일 입력과 동일한 라이트/다크 테마 클래스를 사용해야 한다", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
        status: 401,
        headers: { "Content-Type": "application/json" },
      }),
    );

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

    const emailInput = screen.getByLabelText("Email") as HTMLInputElement;
    const passwordInput = screen.getByLabelText("Password") as HTMLInputElement;

    const emailClass = emailInput.className;
    const passwordClass = passwordInput.className;

    expect(emailClass).toContain("border-slate-300");
    expect(emailClass).toContain("bg-white");
    expect(emailClass).toContain("text-slate-900");
    expect(emailClass).toContain("dark:border-slate-700");
    expect(emailClass).toContain("dark:bg-slate-950");
    expect(emailClass).toContain("dark:text-slate-50");

    expect(passwordClass).toBe(emailClass);
  });

  it("로그인 페이지에도 테마 전환 버튼이 있고 클릭 시 html 요소의 dark 클래스가 토글되어야 한다", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
        status: 401,
        headers: { "Content-Type": "application/json" },
      }),
    );

    vi.stubGlobal("fetch", fetchMock as any);

    render(<LoginPage />);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledTimes(1);
      expect(fetchMock.mock.calls[0][0]).toBe("/api/auth/me");
    });

    const html = document.documentElement;
    html.classList.remove("dark");

    const themeToggleButton = await screen.findByRole("button", { name: "Toggle theme" });

    expect(html.classList.contains("dark")).toBe(false);

    themeToggleButton.click();

    expect(html.classList.contains("dark")).toBe(true);

    themeToggleButton.click();

    expect(html.classList.contains("dark")).toBe(false);
  });
});
