import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import AdmZip from 'adm-zip';
import axios from 'axios';
import { Logger } from '@nestjs/common';

export class ZipHandler {
  private static readonly logger = new Logger(ZipHandler.name);

  /**
   * 下载并解压.zip文件，提取3D模型文件
   * @param zipUrl ZIP文件的URL
   * @param outputDir 输出目录（默认为系统临时目录）
   * @returns 提取的3D模型文件路径、预览图路径和文件Buffer
   */
  static async downloadAndExtract(
    zipUrl: string,
    outputDir?: string,
  ): Promise<{
    modelPath: string;
    previewPath?: string;
    modelBuffer: Buffer;
    previewBuffer?: Buffer;
  }> {
    // 使用系统临时目录
    const baseDir =
      outputDir ||
      path.join(os.tmpdir(), 'ai-3d', Date.now().toString());

    try {
      if (!fs.existsSync(baseDir)) {
        fs.mkdirSync(baseDir, { recursive: true });
      }

      // 1. 下载ZIP文件
      this.logger.log(`开始下载ZIP文件: ${zipUrl}`);
      const zipPath = path.join(baseDir, 'model.zip');
      await this.downloadFile(zipUrl, zipPath);
      this.logger.log(`ZIP文件下载完成: ${zipPath}`);

      // 2. 解压ZIP文件
      this.logger.log(`开始解压ZIP文件`);
      const extractDir = path.join(baseDir, 'extracted');
      await this.extractZip(zipPath, extractDir);
      this.logger.log(`ZIP文件解压完成: ${extractDir}`);

      // 3. 查找3D模型文件和预览图
      const files = this.getAllFiles(extractDir);
      const modelFile = this.findModelFile(files);
      const previewFile = this.findPreviewImage(files);

      if (!modelFile) {
        throw new Error('在ZIP文件中未找到3D模型文件（.glb, .gltf）');
      }

      this.logger.log(`找到3D模型文件: ${modelFile}`);
      if (previewFile) {
        this.logger.log(`找到预览图: ${previewFile}`);
      }

      // 4. 读取文件Buffer（用于上传到COS）
      const modelBuffer = fs.readFileSync(modelFile);
      const previewBuffer = previewFile ? fs.readFileSync(previewFile) : undefined;

      return {
        modelPath: modelFile,
        previewPath: previewFile,
        modelBuffer,
        previewBuffer,
      };
    } catch (error) {
      this.logger.error(`处理ZIP文件失败: ${error.message}`, error.stack);
      throw error;
    } finally {
      // 清理临时目录
      try {
        if (fs.existsSync(baseDir)) {
          fs.rmSync(baseDir, { recursive: true, force: true });
          this.logger.log(`已清理临时目录: ${baseDir}`);
        }
      } catch (err) {
        this.logger.warn(`清理临时目录失败: ${err.message}`);
      }
    }
  }

  /**
   * 下载文件
   */
  private static async downloadFile(
    url: string,
    outputPath: string,
  ): Promise<void> {
    const response = await axios.get(url, {
      responseType: 'arraybuffer',
      timeout: 60000, // 60秒超时
    });

    fs.writeFileSync(outputPath, response.data);
  }

  /**
   * 解压ZIP文件
   */
  private static async extractZip(
    zipPath: string,
    outputDir: string,
  ): Promise<void> {
    const zip = new AdmZip(zipPath);
    zip.extractAllTo(outputDir, true);
  }

  /**
   * 递归获取目录下的所有文件
   */
  private static getAllFiles(dir: string): string[] {
    const files: string[] = [];

    const traverse = (currentDir: string) => {
      const items = fs.readdirSync(currentDir);

      for (const item of items) {
        const fullPath = path.join(currentDir, item);
        const stat = fs.statSync(fullPath);

        if (stat.isDirectory()) {
          traverse(fullPath);
        } else {
          files.push(fullPath);
        }
      }
    };

    traverse(dir);
    return files;
  }

  /**
   * 查找3D模型文件（.glb, .gltf）
   */
  private static findModelFile(files: string[]): string | undefined {
    // 优先查找.glb文件（二进制格式，更常用）
    const glbFile = files.find((file) => file.toLowerCase().endsWith('.glb'));
    if (glbFile) return glbFile;

    // 其次查找.gltf文件
    const gltfFile = files.find((file) =>
      file.toLowerCase().endsWith('.gltf'),
    );
    if (gltfFile) return gltfFile;

    // 其他可能的3D格式
    const otherFormats = ['.obj', '.fbx', '.stl'];
    for (const format of otherFormats) {
      const file = files.find((f) => f.toLowerCase().endsWith(format));
      if (file) return file;
    }

    return undefined;
  }

  /**
   * 查找预览图（.jpg, .jpeg, .png）
   */
  private static findPreviewImage(files: string[]): string | undefined {
    const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp'];

    for (const ext of imageExtensions) {
      const imageFile = files.find((file) => file.toLowerCase().endsWith(ext));
      if (imageFile) return imageFile;
    }

    return undefined;
  }
}
