import { Injectable } from '@nestjs/common';
import { createHmac, timingSafeEqual } from 'node:crypto';

export type AdminViewTokenClaims =
  | { version: 1 }
  | {
      version: 2;
      role?: string;
      admId?: number;
      carView: boolean;
    };

/**
 * ★보안(C2): admin-view/* 는 admin→site cross-origin iframe이라 세션 쿠키를 못 싣는다(IDOR였음).
 *   세션 가드 대신 "scope+id로 스코프된 단명(短命) HMAC 토큰"을 admin 백엔드(AdminJwtAuthGuard 보호)가
 *   발급 → iframe URL ?t= 로 전달 → 여기서 검증. 토큰은 특정 분석건(scope:id)에만 유효 + TTL.
 *   순차 정수 id를 순회해도 유효 토큰 없이는 못 본다.
 */
@Injectable()
export class AdminViewTokenService {
  // ADMIN_VIEW_SECRET 우선, 없으면 AES_SECRET 재사용(둘 다 없으면 빈키 → verify 항상 실패=안전).
  private readonly secret =
    process.env.ADMIN_VIEW_SECRET || process.env.AES_SECRET || '';
  private readonly ttlMs = Number(
    process.env.ADMIN_VIEW_TOKEN_TTL_MS ?? 600000,
  ); // 기본 10분

  /** scope+id와 발급 관리자의 자동차 조회 claim을 함께 서명한다. */
  issue(
    scope: string,
    id: number,
    admin?: { role?: string; admId?: number },
    carView = false,
  ): string {
    const exp = Date.now() + this.ttlMs;
    const payload = JSON.stringify({
      v: 2,
      scope,
      id,
      exp,
      role: admin?.role,
      admId: admin?.admId,
      carView,
    });
    return (
      Buffer.from(payload).toString('base64url') + '.' + this.sign(payload)
    );
  }

  /** 서명·scope·id·만료를 모두 검증한 claim만 반환한다. */
  verify(
    token: string | undefined,
    scope: string,
    id: number,
  ): AdminViewTokenClaims | null {
    if (!this.secret || !token) return null;
    const dot = token.indexOf('.');
    if (dot < 1) return null;
    const b64 = token.slice(0, dot);
    const sig = token.slice(dot + 1);
    let payload: string;
    try {
      payload = Buffer.from(b64, 'base64url').toString('utf-8');
    } catch {
      return null;
    }
    const expected = this.sign(payload);
    const a = Buffer.from(sig);
    const b = Buffer.from(expected);
    if (a.length !== b.length || !timingSafeEqual(a, b)) return null;

    try {
      const parsed = JSON.parse(payload);
      if (
        parsed?.v !== 2 ||
        parsed.scope !== scope ||
        parsed.id !== id ||
        !Number.isFinite(parsed.exp) ||
        parsed.exp < Date.now() ||
        (parsed.role !== undefined && typeof parsed.role !== 'string') ||
        (parsed.admId !== undefined &&
          (!Number.isInteger(parsed.admId) || parsed.admId <= 0)) ||
        typeof parsed.carView !== 'boolean'
      )
        return null;
      return {
        version: 2,
        role: parsed.role,
        admId: parsed.admId,
        carView: parsed.carView,
      };
    } catch {
      //* 기존 scope:id:exp 토큰은 일반 admin-view에만 호환한다.
      const parts = payload.split(':');
      if (parts.length !== 3) return null;
      const [legacyScope, idStr, expStr] = parts;
      const exp = Number(expStr);
      if (
        legacyScope !== scope ||
        Number(idStr) !== id ||
        !Number.isFinite(exp) ||
        exp < Date.now()
      )
        return null;
      return { version: 1 };
    }
  }

  private sign(payload: string): string {
    return createHmac('sha256', this.secret)
      .update(payload)
      .digest('base64url');
  }
}
