import "dotenv/config";
import { describe, it, expect, beforeEach, vi } from "vitest";

import { hashPassword, signAccessToken } from "@/features/auth/authCrypto";

const BASE_URL = "http://localhost";

// PrismaClient 를 실제 DB 대신 메모리 기반 user 저장소를 사용하는 목으로 대체한다.
// 이렇게 하면 CI/로컬 어디에서도 DATABASE_URL 이나 실제 DB 없이 Auth API 테스트를 실행할 수 있다.
const inMemoryUsers: any[] = [];

vi.mock("@prisma/client", () => {
  class PrismaClientMock {
    user = {
      findUnique: async ({ where: { email } }: any) => {
        return inMemoryUsers.find((u) => u.email === email) ?? null;
      },
      create: async ({ data }: any) => {
        const now = new Date();
        const user = {
          id: String(inMemoryUsers.length + 1),
          createdAt: now,
          updatedAt: now,
          ...data,
        };
        inMemoryUsers.push(user);
        return user;
      },
    };
  }

  return { PrismaClient: PrismaClientMock };
});

beforeEach(() => {
  // 각 테스트 전에 메모리 유저 저장소를 초기화한다.
  inMemoryUsers.length = 0;
  process.env.AUTH_JWT_SECRET = "test-jwt-secret-api";
  process.env.AUTH_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef";
});

describe("/api/auth", () => {
  describe("POST /api/auth/signup", () => {
    it("새 이메일과 8자 이상 비밀번호로 회원가입하면 User 가 생성되고 JWT 쿠키가 설정되어야 한다", async () => {
      const { POST: signup } = await import("@/app/api/auth/signup/route");

      const payload = { email: "user@example.com", password: "securePass1" };

      const res = await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(payload),
        }),
      );

      expect(res.status).toBe(201);

      const json = (await res.json()) as any;
      expect(json.email).toBe(payload.email);
      expect(json.id).toBeDefined();
      // 응답에는 passwordHash 가 포함되면 안 된다.
      expect(json.passwordHash).toBeUndefined();

      // 메모리 저장소에도 유저가 1명 생성되어야 한다.
      expect(inMemoryUsers.length).toBe(1);
      expect(inMemoryUsers[0].email).toBe(payload.email);
      expect(typeof inMemoryUsers[0].passwordHash).toBe("string");

      // JWT 세션 쿠키가 설정되어야 한다.
      const setCookie = res.headers.get("set-cookie");
      expect(setCookie).toBeTruthy();
      expect(setCookie).toContain("pb_access=");
    });

    it("비밀번호가 8자 미만이면 400 과 에러 메시지를 반환해야 한다", async () => {
      const { POST: signup } = await import("@/app/api/auth/signup/route");

      const res = await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email: "short@example.com", password: "short" }),
        }),
      );

      expect(res.status).toBe(400);
      const json = (await res.json()) as any;
      expect(json.message).toBeDefined();
    });

    it("이미 존재하는 이메일로 회원가입하면 409 를 반환해야 한다", async () => {
      const { POST: signup } = await import("@/app/api/auth/signup/route");

      // 먼저 한 번 성공적으로 가입
      await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email: "dup@example.com", password: "securePass1" }),
        }),
      );

      // 같은 이메일로 다시 요청
      const res = await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email: "dup@example.com", password: "anotherPass1" }),
        }),
      );

      expect(res.status).toBe(409);
      const json = (await res.json()) as any;
      expect(json.message).toBeDefined();
    });
  });

  describe("POST /api/auth/login", () => {
    it("올바른 이메일/비밀번호로 로그인하면 200 과 JWT 쿠키를 반환해야 한다", async () => {
      const { POST: signup } = await import("@/app/api/auth/signup/route");
      const { POST: login } = await import("@/app/api/auth/login/route");

      const email = "login@example.com";
      const password = "securePass1";

      // 사전 회원가입
      await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password }),
        }),
      );

      const res = await login(
        new Request(`${BASE_URL}/api/auth/login`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password }),
        }),
      );

      expect(res.status).toBe(200);
      const json = (await res.json()) as any;
      expect(json.email).toBe(email);
      expect(json.id).toBeDefined();
      expect(json.passwordHash).toBeUndefined();

      const setCookie = res.headers.get("set-cookie");
      expect(setCookie).toBeTruthy();
      expect(setCookie).toContain("pb_access=");
    });

    it("잘못된 이메일 또는 비밀번호로 로그인하면 401 을 반환해야 한다", async () => {
      const { POST: signup } = await import("@/app/api/auth/signup/route");
      const { POST: login } = await import("@/app/api/auth/login/route");

      const email = "wrong@example.com";
      const password = "securePass1";

      // 사전 회원가입
      await signup(
        new Request(`${BASE_URL}/api/auth/signup`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password }),
        }),
      );

      // 존재하지 않는 이메일
      const res1 = await login(
        new Request(`${BASE_URL}/api/auth/login`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email: "nope@example.com", password }),
        }),
      );
      expect(res1.status).toBe(401);

      // 비밀번호 불일치
      const res2 = await login(
        new Request(`${BASE_URL}/api/auth/login`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password: "wrongPass1" }),
        }),
      );
      expect(res2.status).toBe(401);
    });
  });

  describe("POST /api/auth/logout", () => {
    it("로그아웃 시 pb_access 쿠키를 제거하는 Set-Cookie 헤더를 반환해야 한다", async () => {
      const { POST: logout } = await import("@/app/api/auth/logout/route");

      const res = await logout(
        new Request(`${BASE_URL}/api/auth/logout`, {
          method: "POST",
          headers: {
            cookie: "pb_access=dummy.token.value",
          },
        }),
      );

      expect(res.status).toBe(200);
      const setCookie = res.headers.get("set-cookie");
      expect(setCookie).toBeTruthy();
      expect(setCookie).toContain("pb_access=");
      expect(setCookie?.toLowerCase()).toContain("max-age=0");
    });

    it("쿠키가 없어도 200 과 쿠키 제거 헤더를 반환해야 한다", async () => {
      const { POST: logout } = await import("@/app/api/auth/logout/route");

      const res = await logout(
        new Request(`${BASE_URL}/api/auth/logout`, {
          method: "POST",
        }),
      );

      expect(res.status).toBe(200);
      const setCookie = res.headers.get("set-cookie");
      expect(setCookie).toBeTruthy();
      expect(setCookie).toContain("pb_access=");
      expect(setCookie?.toLowerCase()).toContain("max-age=0");
    });
  });

  describe("GET /api/auth/me", () => {
    it("유효한 JWT 쿠키가 있을 때 현재 로그인 유저 정보를 반환해야 한다", async () => {
      const { GET: me } = await import("@/app/api/auth/me/route");

      const user = { id: "user-1", email: "me@example.com", tokenVersion: 1 };
      const token = await signAccessToken(user);

      const res = await me(
        new Request(`${BASE_URL}/api/auth/me`, {
          headers: {
            cookie: `pb_access=${token}`,
          },
        }),
      );

      expect(res.status).toBe(200);
      const json = (await res.json()) as any;
      expect(json.id).toBe(user.id);
      expect(json.email).toBe(user.email);
      expect(json.passwordHash).toBeUndefined();
    });

    it("JWT 쿠키가 없거나 잘못된 경우 401 을 반환해야 한다", async () => {
      const { GET: me } = await import("@/app/api/auth/me/route");

      const res1 = await me(new Request(`${BASE_URL}/api/auth/me`));
      expect(res1.status).toBe(401);

      const res2 = await me(
        new Request(`${BASE_URL}/api/auth/me`, {
          headers: { cookie: "pb_access=invalid.token.here" },
        }),
      );
      expect(res2.status).toBe(401);
    });
  });
});
