import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  Injectable,
  Logger,
} from '@nestjs/common';
import { Request } from 'express';
import { AnalyzeRepository } from '../analyze/analyze.repository';
import { AdminViewTokenService } from './admin-view-token.service';

type TokenTarget = {
  scope: 'analyze' | 'b2c-analyze' | 'client-analyze';
  id: number;
};

@Injectable()
export class AdminViewTokenGuard implements CanActivate {
  private readonly logger = new Logger(AdminViewTokenGuard.name);
  constructor(
    private tokenService: AdminViewTokenService,
    private analyzeRepository: AnalyzeRepository,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest<Request>();
    const target = await this.resolveTokenTarget(req);
    const token =
      (req.query.t as string | undefined) ??
      (req.headers['x-admin-view-token'] as string | undefined);
    const claims = target
      ? this.tokenService.verify(token, target.scope, target.id)
      : null;
    const ok = !!claims;

    //* 완전 검증된 v2 토큰만 관리자 identity를 복원한다. legacy/invalid는 자동차 guard가 fail-closed.
    if (claims?.version === 2) {
      req['admin'] = {
        role: claims.role,
        admId: claims.admId,
        carView: claims.carView,
        adminViewToken: true,
      };
    }

    //* ★게이트(무중단 롤아웃): ADMIN_VIEW_TOKEN_ENFORCE='true' 일 때만 차단한다. 그 전엔 미검증도 통과
    //*   (경고 로그만) → admin/site가 토큰을 실제로 전달하는지 운영에서 확인한 뒤 플래그를 켜면 IDOR 차단
    //*   활성. 코드 배포만으론 기존 iframe이 안 깨진다.
    if (process.env.ADMIN_VIEW_TOKEN_ENFORCE !== 'true') {
      if (!ok)
        this.logger.warn(
          `admin-view 토큰 미검증(미강제 모드): scope=${target?.scope ?? '?'} id=${target?.id ?? '?'}`,
        );
      return true;
    }
    if (!ok)
      throw new ForbiddenException('invalid or missing admin-view token');
    return true;
  }

  private async resolveTokenTarget(req: Request): Promise<TokenTarget | null> {
    const oddId = this.paramInt(req, 'oddId');
    const asbId = this.paramInt(req, 'asbId');
    const acbId = this.paramInt(req, 'acbId');
    const bodId = this.paramInt(req, 'bodId');
    const basbId = this.paramInt(req, 'basbId');
    const bcbId = this.paramInt(req, 'bcbId');
    const codId = this.paramInt(req, 'codId');
    const casbId = this.paramInt(req, 'casbId');
    const cacbId = this.paramInt(req, 'cacbId');

    if (req.params.oddId != null)
      return oddId ? { scope: 'analyze', id: oddId } : null;
    if (req.params.asbId != null) {
      if (!asbId) return null;
      const id = await this.analyzeRepository.getOddIdByAsbId(asbId);
      return id ? { scope: 'analyze', id } : null;
    }
    if (req.params.acbId != null) {
      if (!acbId) return null;
      const id = await this.analyzeRepository.getOddIdByAcbId(acbId);
      return id ? { scope: 'analyze', id } : null;
    }

    if (req.params.bodId != null)
      return bodId ? { scope: 'b2c-analyze', id: bodId } : null;
    if (req.params.basbId != null) {
      if (!basbId) return null;
      const id = await this.analyzeRepository.getBodIdByBasbId(basbId);
      return id ? { scope: 'b2c-analyze', id } : null;
    }
    if (req.params.bcbId != null) {
      if (!bcbId) return null;
      const id = await this.analyzeRepository.getBodIdByBcbId(bcbId);
      return id ? { scope: 'b2c-analyze', id } : null;
    }

    if (req.params.codId != null)
      return codId ? { scope: 'client-analyze', id: codId } : null;
    if (req.params.casbId != null) {
      if (!casbId) return null;
      const id = await this.analyzeRepository.getCodIdByCasbId(casbId);
      return id ? { scope: 'client-analyze', id } : null;
    }
    if (req.params.cacbId != null) {
      if (!cacbId) return null;
      const id = await this.analyzeRepository.getCodIdByCacbId(cacbId);
      return id ? { scope: 'client-analyze', id } : null;
    }

    return null;
  }

  private paramInt(req: Request, name: string): number | null {
    const value = req.params[name];
    const id = Number(value);
    return Number.isInteger(id) && id > 0 ? id : null;
  }
}
