import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import {
  DomainException,
  LoggerService,
  NotFoundException,
  ValidationException,
  CacheService,
  CachePrefix,
} from '@modules/shared';
import {
  MEDIA_STORAGE_SERVICE,
  type IMediaStorageService,
} from '../../../../listings/infrastructure/services/media-storage.service';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
import { SubmitKycCommand, type KycFileData } from './submit-kyc.command';

const KYC_FOLDER = 'kyc';
const ALLOWED_SUBMIT_STATUSES = new Set(['NONE', 'REJECTED']);

@CommandHandler(SubmitKycCommand)
export class SubmitKycHandler implements ICommandHandler<SubmitKycCommand> {
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    @Inject(MEDIA_STORAGE_SERVICE) private readonly mediaStorage: IMediaStorageService,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: SubmitKycCommand): Promise<{ message: string }> {
    try {
      const user = await this.userRepo.findById(command.userId);
      if (!user) {
        throw new NotFoundException('Nguoi dung', command.userId);
      }

      if (!ALLOWED_SUBMIT_STATUSES.has(user.kycStatus)) {
        throw new ValidationException(
          'Ban da gui ho so KYC. Vui long cho ket qua xem xet.',
        );
      }

      let frontImageUrl: string;
      let backImageUrl: string | null = null;
      let selfieUrl: string | null = null;

      if (command.imageUrls) {
        // Presigned URL flow: images were already uploaded directly to MinIO
        frontImageUrl = command.imageUrls.frontImageUrl;
        backImageUrl = command.imageUrls.backImageUrl ?? null;
        selfieUrl = command.imageUrls.selfieUrl ?? null;

        // Validate URL hosts match our MinIO bucket (reject SSRF / tampering)
        const untrusted: string[] = [];
        if (!this.mediaStorage.isTrustedUrl(frontImageUrl)) untrusted.push('frontImageUrl');
        if (backImageUrl && !this.mediaStorage.isTrustedUrl(backImageUrl)) untrusted.push('backImageUrl');
        if (selfieUrl && !this.mediaStorage.isTrustedUrl(selfieUrl)) untrusted.push('selfieUrl');
        if (untrusted.length > 0) {
          throw new ValidationException(
            `URL khong hop le (${untrusted.join(', ')}): chi chap nhan URL tu MinIO bucket cua he thong`,
          );
        }

        // Validate URL belongs to this user's KYC namespace (reject cross-user tampering)
        const outsideNamespace: string[] = [];
        if (!this.isInUserKycNamespace(frontImageUrl, command.userId)) {
          outsideNamespace.push('frontImageUrl');
        }
        if (backImageUrl && !this.isInUserKycNamespace(backImageUrl, command.userId)) {
          outsideNamespace.push('backImageUrl');
        }
        if (selfieUrl && !this.isInUserKycNamespace(selfieUrl, command.userId)) {
          outsideNamespace.push('selfieUrl');
        }
        if (outsideNamespace.length > 0) {
          throw new ValidationException(
            `URL khong thuoc ve nguoi dung (${outsideNamespace.join(', ')}): chi chap nhan URL trong thu muc KYC cua ban`,
          );
        }
      } else if (command.frontImage) {
        // Legacy file upload flow: upload buffers server-side
        const folder = `${KYC_FOLDER}/${command.userId}`;

        frontImageUrl = await this.uploadFile(command.frontImage, folder, 'front');
        backImageUrl = command.backImage
          ? await this.uploadFile(command.backImage, folder, 'back')
          : null;
        selfieUrl = command.selfieImage
          ? await this.uploadFile(command.selfieImage, folder, 'selfie')
          : null;
      } else {
        throw new ValidationException(
          'Vui long tai len anh mat truoc giay to hoac cung cap URL anh da tai len',
        );
      }

      const kycData = {
        idType: command.documentType,
        idNumber: command.documentNumber,
        frontImageUrl,
        backImageUrl,
        selfieUrl,
        submittedAt: new Date().toISOString(),
      };

      user.updateKycStatus('PENDING', kycData);
      await this.userRepo.update(user);

      await this.cache.invalidate(
        CacheService.buildKey(CachePrefix.USER_PROFILE, command.userId),
      );

      return { message: 'Ho so KYC da duoc gui thanh cong' };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to submit KYC for user ${command.userId}: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Khong the gui ho so KYC');
    }
  }

  /**
   * Confirms the given URL's object key lies within `kyc/{userId}/` for this user.
   * This prevents a caller from submitting a URL that belongs to a different user's
   * KYC namespace even when the host is trusted (bucket).
   *
   * Accepts pathname of either `/<bucket>/kyc/{userId}/<object>` (production format)
   * or `/kyc/{userId}/<object>` (bucket-less URL).
   */
  private isInUserKycNamespace(url: string, userId: string): boolean {
    if (!userId) return false;
    let parsed: URL;
    try {
      parsed = new URL(url);
    } catch {
      return false;
    }
    // Require a real object after /kyc/{userId}/
    const marker = `/${KYC_FOLDER}/${userId}/`;
    const idx = parsed.pathname.indexOf(marker);
    if (idx < 0) return false;
    return parsed.pathname.length > idx + marker.length;
  }

  private async uploadFile(
    file: KycFileData,
    folder: string,
    label: string,
  ): Promise<string> {
    try {
      return await this.mediaStorage.upload(
        file.buffer,
        file.originalname,
        file.mimetype,
        folder,
      );
    } catch (error) {
      this.logger.error(
        `KYC ${label} image upload failed: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        'SubmitKycHandler',
      );
      throw new ValidationException(`Tai anh ${label} that bai, vui long thu lai`);
    }
  }
}
