import { describe, it, expect, beforeEach } from "vitest";

// 앞으로 구현할 인증/보안 유틸리티 모듈을 대상으로 TDD를 진행한다.
// - 비밀번호 해시/검증 (bcrypt 기반)
// - JWT 액세스 토큰 발급/검증
// - 민감정보 JSON 암호화/복호화

import {
  hashPassword,
  verifyPassword,
  signAccessToken,
  verifyAccessToken,
  encryptJson,
  decryptJson,
} from "@/features/auth/authCrypto";

interface TestUser {
  id: string;
  email: string;
  tokenVersion: number;
}

describe("authCrypto 유틸리티", () => {
  beforeEach(() => {
    // 테스트 환경에서 사용할 JWT/암호화 키를 설정한다.
    process.env.AUTH_JWT_SECRET = "test-jwt-secret-key";
    // 32바이트 키를 hex 로 표현 (예시)
    process.env.AUTH_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef";
  });

  describe("비밀번호 해시/검증", () => {
    it("8자 이상 비밀번호를 bcrypt 해시로 안전하게 저장하고, 검증에 성공해야 한다", async () => {
      const password = "securePass1";

      const hash = await hashPassword(password);

      expect(hash).toBeTypeOf("string");
      expect(hash).not.toBe(password);

      const ok = await verifyPassword(password, hash);
      const fail = await verifyPassword("wrongPass", hash);

      expect(ok).toBe(true);
      expect(fail).toBe(false);
    });

    it("8자 미만 비밀번호는 해시 시도 시 오류를 발생시켜야 한다", async () => {
      const shortPassword = "short"; // 5자

      await expect(hashPassword(shortPassword)).rejects.toThrow();
    });
  });

  describe("JWT 액세스 토큰", () => {
    it("유저 정보를 기반으로 JWT 액세스 토큰을 발급하고, 검증 시 동일한 정보가 나와야 한다", async () => {
      const user: TestUser = {
        id: "user-1",
        email: "user@example.com",
        tokenVersion: 1,
      };

      const token = await signAccessToken(user);

      expect(token).toBeTypeOf("string");
      expect(token.length).toBeGreaterThan(10);

      const payload = await verifyAccessToken(token);

      expect(payload).not.toBeNull();
      expect(payload?.sub).toBe(user.id);
      expect(payload?.email).toBe(user.email);
      expect(payload?.tokenVersion).toBe(user.tokenVersion);

      const anyPayload = payload as any;
      const iat = anyPayload.iat;
      const exp = anyPayload.exp;

      expect(typeof iat).toBe("number");
      expect(typeof exp).toBe("number");
      expect(exp > iat).toBe(true);
    });

    it("잘못된 토큰이나 시크릿으로 검증할 경우 null 을 반환해야 한다", async () => {
      const user: TestUser = {
        id: "user-2",
        email: "user2@example.com",
        tokenVersion: 3,
      };

      const token = await signAccessToken(user);

      // 시크릿을 바꿔서 검증 실패 상황을 만든다.
      process.env.AUTH_JWT_SECRET = "other-secret";

      const payload = await verifyAccessToken(token);
      expect(payload).toBeNull();
    });
  });

  describe("민감정보 JSON 암호화/복호화", () => {
    it("JSON 객체를 암호화한 뒤 복호화하면 원래 객체와 동일해야 한다", async () => {
      const original = {
        cardToken: "tok_123",
        customerId: "cus_456",
        meta: { plan: "pro", country: "KR" },
      };

      const ciphertext = await encryptJson(original);

      expect(ciphertext).toBeTypeOf("string");
      expect(ciphertext).not.toContain("tok_123");
      expect(ciphertext).not.toContain("cus_456");

      const decrypted = await decryptJson<typeof original>(ciphertext);

      expect(decrypted).toEqual(original);
    });

    it("암호화 문자열이 손상되었거나 키가 잘못된 경우 복호화 시 오류를 발생시켜야 한다", async () => {
      const original = { secret: "value" };
      const ciphertext = await encryptJson(original);

      // 중간 일부를 잘라 손상시킨다.
      const broken = ciphertext.slice(0, Math.floor(ciphertext.length / 2));

      await expect(decryptJson(broken as string)).rejects.toThrow();

      // 키를 바꿔서 복호화 시도
      process.env.AUTH_ENCRYPTION_KEY = "ffffffffffffffffffffffffffffffff";
      await expect(decryptJson(ciphertext)).rejects.toThrow();
    });
  });
});
