import {
  Injectable,
  Inject,
  Logger,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import * as mimeTypes from 'mime-types';
import sharp from 'sharp';
import { FileEntity, FileType, FileStatus } from '../../domain/entities/file.entity';
import { IFileRepository, FILE_REPOSITORY } from '../../domain/repositories/file.repository.interface';
import { MinioStorageAdapter } from '../../adapters/outbound/storage/minio-storage.adapter';
import {
  FileResponseDto,
  PresignedUrlDto,
  PresignedUrlResponseDto,
} from '../dtos/upload-file.dto';

// 允许的文件类型
const ALLOWED_IMAGE_TYPES = [
  'image/jpeg',
  'image/png',
  'image/gif',
  'image/webp',
  'image/svg+xml',
];

const ALLOWED_DOCUMENT_TYPES = [
  'application/pdf',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'text/plain',
  'text/csv',
  'text/markdown',
];

const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB

// 缩略图配置
const THUMBNAIL_MAX_WIDTH = 400;
const THUMBNAIL_MAX_HEIGHT = 400;
const THUMBNAIL_QUALITY = 80;

/** sharp 支持生成缩略图的 MIME 类型 (svg 不支持 resize) */
const THUMBNAIL_SUPPORTED_MIMES = [
  'image/jpeg', 'image/png', 'image/webp', 'image/gif',
];

@Injectable()
export class FileService {
  private readonly logger = new Logger(FileService.name);

  constructor(
    @Inject(FILE_REPOSITORY)
    private readonly fileRepo: IFileRepository,
    private readonly storageAdapter: MinioStorageAdapter,
  ) {}

  /**
   * 获取预签名上传 URL
   */
  async getPresignedUploadUrl(
    userId: string,
    dto: PresignedUrlDto,
  ): Promise<PresignedUrlResponseDto> {
    const { fileName, mimeType, conversationId } = dto;

    // 验证文件类型
    const fileType = this.getFileType(mimeType);
    if (fileType === FileType.OTHER) {
      throw new BadRequestException(
        'Unsupported file type. Only images and documents are allowed.',
      );
    }

    // 生成唯一文件 ID 和存储路径
    const fileId = uuidv4();
    const extension = mimeTypes.extension(mimeType) || 'bin';
    const date = new Date();
    const datePath = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
    const objectName = `uploads/${fileType}s/${datePath}/${userId}/${fileId}.${extension}`;

    // 创建文件记录 (状态为 uploading)
    const file = FileEntity.create({
      id: fileId,
      userId,
      conversationId: conversationId || undefined,
      originalName: fileName,
      storagePath: objectName,
      mimeType,
      type: fileType,
    });

    await this.fileRepo.save(file);

    // 获取预签名 URL (有效期 1 小时)
    const expiresIn = 3600;
    const uploadUrl = await this.storageAdapter.getPresignedPutUrl(
      objectName,
      expiresIn,
    );

    return {
      uploadUrl,
      fileId,
      objectName,
      expiresIn,
    };
  }

  /**
   * 确认上传完成
   */
  async confirmUpload(
    userId: string,
    fileId: string,
    fileSize: number,
  ): Promise<FileResponseDto> {
    const file = await this.fileRepo.findByIdAndUser(fileId, userId);

    if (!file) {
      throw new NotFoundException('File not found');
    }

    if (!file.isUploading()) {
      throw new BadRequestException('File upload already confirmed');
    }

    if (fileSize > MAX_FILE_SIZE) {
      file.markAsFailed('File size exceeds maximum limit');
      await this.fileRepo.update(file);
      throw new BadRequestException('File size exceeds 50MB limit');
    }

    // 更新文件状态
    file.confirmUpload(fileSize);

    // 生成缩略图（大文件上传路径）
    if (file.type === FileType.IMAGE && THUMBNAIL_SUPPORTED_MIMES.includes(file.mimeType) && !file.thumbnailPath) {
      try {
        const buffer = await this.storageAdapter.downloadFile(file.storagePath);
        const thumbnailPath = await this.generateThumbnail(buffer, file.storagePath, file.mimeType);
        if (thumbnailPath) {
          file.thumbnailPath = thumbnailPath;
        }
      } catch (err) {
        this.logger.warn(`Thumbnail generation failed on confirm for ${fileId}: ${err}`);
      }
    }

    await this.fileRepo.update(file);

    return this.toResponseDto(file);
  }

  /**
   * 直接上传文件 (适用于小文件)
   */
  async uploadFile(
    userId: string,
    file: Express.Multer.File,
    conversationId?: string,
  ): Promise<FileResponseDto> {
    const { originalname, mimetype, buffer, size } = file;

    // 验证文件大小
    if (size > MAX_FILE_SIZE) {
      throw new BadRequestException('File size exceeds 50MB limit');
    }

    // 验证文件类型
    const fileType = this.getFileType(mimetype);
    if (fileType === FileType.OTHER) {
      throw new BadRequestException(
        'Unsupported file type. Only images and documents are allowed.',
      );
    }

    // 生成存储路径
    const fileId = uuidv4();
    const extension = mimeTypes.extension(mimetype) || 'bin';
    const date = new Date();
    const datePath = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
    const objectName = `uploads/${fileType}s/${datePath}/${userId}/${fileId}.${extension}`;

    // 上传到 MinIO
    await this.storageAdapter.uploadFile(objectName, buffer, mimetype, {
      'x-amz-meta-original-name': encodeURIComponent(originalname),
      'x-amz-meta-user-id': userId,
    });

    // 生成缩略图 (仅图片，fire-and-forget 不阻塞主流程)
    let thumbnailPath: string | null = null;
    if (fileType === FileType.IMAGE && THUMBNAIL_SUPPORTED_MIMES.includes(mimetype)) {
      try {
        thumbnailPath = await this.generateThumbnail(buffer, objectName, mimetype);
      } catch (err) {
        this.logger.warn(`Thumbnail generation failed for ${fileId}: ${err}`);
      }
    }

    // 创建文件记录
    const fileEntity = FileEntity.create({
      id: fileId,
      userId,
      conversationId,
      originalName: originalname,
      storagePath: objectName,
      mimeType: mimetype,
      type: fileType,
      size,
      status: FileStatus.READY,
    });

    if (thumbnailPath) {
      fileEntity.thumbnailPath = thumbnailPath;
    }

    await this.fileRepo.save(fileEntity);

    this.logger.log(`File uploaded: ${fileId} by user ${userId}${thumbnailPath ? ' (with thumbnail)' : ''}`);

    return this.toResponseDto(fileEntity);
  }

  /**
   * 获取文件信息
   */
  async getFile(userId: string, fileId: string): Promise<FileResponseDto> {
    const file = await this.fileRepo.findByIdAndUserAndStatus(
      fileId,
      userId,
      FileStatus.READY,
    );

    if (!file) {
      throw new NotFoundException('File not found');
    }

    return this.toResponseDto(file);
  }

  /**
   * 获取文件下载 URL
   */
  async getDownloadUrl(userId: string, fileId: string): Promise<string> {
    const file = await this.fileRepo.findByIdAndUserAndStatus(
      fileId,
      userId,
      FileStatus.READY,
    );

    if (!file) {
      throw new NotFoundException('File not found');
    }

    return this.storageAdapter.getPresignedUrl(file.storagePath, 3600);
  }

  /**
   * 获取用户的所有文件
   */
  async getUserFiles(
    userId: string,
    conversationId?: string,
  ): Promise<FileResponseDto[]> {
    const files = await this.fileRepo.findByUserAndStatus(
      userId,
      FileStatus.READY,
      conversationId,
    );

    return Promise.all(files.map((f) => this.toResponseDto(f)));
  }

  /**
   * 删除文件 (软删除)
   */
  async deleteFile(userId: string, fileId: string): Promise<void> {
    const file = await this.fileRepo.findByIdAndUser(fileId, userId);

    if (!file) {
      throw new NotFoundException('File not found');
    }

    file.softDelete();
    await this.fileRepo.update(file);

    this.logger.log(`File deleted: ${fileId} by user ${userId}`);
  }

  /**
   * 生成缩略图并上传到 MinIO
   * @returns thumbnailPath 或 null
   */
  private async generateThumbnail(
    imageBuffer: Buffer,
    originalPath: string,
    mimeType: string,
  ): Promise<string | null> {
    // 将原始路径 uploads/images/.../xxx.png → thumbnails/images/.../xxx.webp
    const thumbnailPath = originalPath
      .replace(/^uploads\//, 'thumbnails/')
      .replace(/\.[^.]+$/, '.webp');

    const thumbnailBuffer = await sharp(imageBuffer)
      .resize(THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, {
        fit: 'inside',
        withoutEnlargement: true,
      })
      .webp({ quality: THUMBNAIL_QUALITY })
      .toBuffer();

    await this.storageAdapter.uploadFile(
      thumbnailPath,
      thumbnailBuffer,
      'image/webp',
    );

    this.logger.debug(
      `Thumbnail generated: ${thumbnailPath} (${thumbnailBuffer.length} bytes)`,
    );

    return thumbnailPath;
  }

  /**
   * 确定文件类型
   */
  private getFileType(mimeType: string): FileType {
    if (ALLOWED_IMAGE_TYPES.includes(mimeType)) {
      return FileType.IMAGE;
    }
    if (ALLOWED_DOCUMENT_TYPES.includes(mimeType)) {
      return FileType.DOCUMENT;
    }
    if (mimeType.startsWith('audio/')) {
      return FileType.AUDIO;
    }
    if (mimeType.startsWith('video/')) {
      return FileType.VIDEO;
    }
    return FileType.OTHER;
  }

  /**
   * 转换为响应 DTO
   */
  private async toResponseDto(file: FileEntity): Promise<FileResponseDto> {
    const dto: FileResponseDto = {
      id: file.id,
      originalName: file.originalName,
      mimeType: file.mimeType,
      type: file.type,
      size: Number(file.size),
      status: file.status,
      createdAt: file.createdAt,
    };

    if (file.isReady()) {
      dto.downloadUrl = await this.storageAdapter.getPresignedUrl(
        file.storagePath,
        3600,
      );

      if (file.thumbnailPath) {
        dto.thumbnailUrl = await this.storageAdapter.getPresignedUrl(
          file.thumbnailPath,
          3600,
        );
      }
    }

    return dto;
  }
}
