import imageCompression from 'browser-image-compression';
import SparkMD5 from 'spark-md5';
import http from './api';
import { OssSignatureData, OssSignatureRequest, RecognitionTaskData, RecognitionTaskRequest, RecognitionResultData, TaskListData } from '../types/api';

/**
 * 文件上传服务
 */
export const uploadService = {
  /**
   * 处理并上传文件
   */
  async uploadFile(file: File): Promise<OssSignatureData> {
    // ... (保留之前的 uploadFile 逻辑)
    // 1. 验证文件
    if (!this.validateFile(file)) {
      throw new Error('不支持的文件类型或文件大小超过限制');
    }

    // 2. 计算文件 Hash (使用原始文件，确保去重逻辑基于用户源文件)
    // 压缩后的文件 Hash 可能会因为压缩过程的微小差异（如时间戳元数据）而不同
    const fileHash = await this.calculateMD5(file);

    // 3. 压缩图片 (如果是图片且超过一定大小)
    let processedFile = file;
    if (file.type.startsWith('image/')) {
      try {
        processedFile = await this.compressImage(file);
      } catch (error) {
        console.warn('图片压缩失败，尝试使用原文件上传', error);
      }
    }

    // 4. 获取上传签名
    const signatureData = await this.getOssSignature({
      file_hash: fileHash,
      file_name: processedFile.name, // 使用处理后的文件名（通常相同）
    });

    // 5. 如果需要上传 (repeat = false)，则上传到 OSS (上传处理后的文件)
    if (!signatureData.data?.repeat && signatureData.data?.sign_url) {
      await this.uploadToOss(signatureData.data.sign_url, processedFile);
    }

    if (!signatureData.data) {
      throw new Error('获取上传签名失败');
    }

    return signatureData.data;
  },

  /**
   * 创建公式识别任务
   */
  async createRecognitionTask(path: string, fileHash: string, fileName: string): Promise<RecognitionTaskData> {
    const data: RecognitionTaskRequest = {
      file_url: path,
      file_hash: fileHash,
      file_name: fileName,
      task_type: 'FORMULA'
    };
    return http.post<RecognitionTaskData>('/formula/recognition', data).then(res => {
      if (!res.data) throw new Error('创建任务失败: 无返回数据');
      return res.data;
    });
  },

  /**
   * 获取任务结果
   */
  async getTaskResult(taskNo: string): Promise<RecognitionResultData> {
    return http.get<RecognitionResultData>(`/formula/recognition/${taskNo}`).then(res => {
      if (!res.data) throw new Error('获取结果失败: 无返回数据');
      return res.data;
    });
  },

  /**
   * 获取任务历史记录列表
   */
  async getTaskList(taskType: 'FORMULA' = 'FORMULA', page: number = 1, pageSize: number = 5): Promise<TaskListData> {
    return http.get<TaskListData>(`/task/list?task_type=${taskType}&page=${page}&page_size=${pageSize}`).then(res => {
      if (!res.data) throw new Error('获取历史记录失败: 无返回数据');
      return res.data;
    });
  },

  // ... (保留其他 helper 方法: validateFile, compressImage, calculateMD5, getOssSignature, uploadToOss)
  /**
   * 验证文件
   */
  validateFile(file: File): boolean {
    const validTypes = ['image/jpeg', 'image/png', 'image/jpg'];
    // 检查类型 (虽然 input accept 做了限制，但这里再检查一次)
    // 注意：有些 jpg 文件的 type 可能是 image/jpeg
    if (!file.type.startsWith('image/')) {
      // 用户提到支持 jpg, png。
      // 暂时只允许图片，虽然 UI 代码里有 application/pdf，但用户需求只提到了图片。
      // 根据用户需求：支持 jpg, png 图片上传
      if (validTypes.indexOf(file.type) === -1 && !file.name.match(/\.(jpg|jpeg|png)$/i)) {
        return false;
      }
    }

    // 5MB 限制 (前端压缩前的检查，压缩后应该更小)
    // 但如果用户传了个很大的文件，压缩可能也花很久。
    // 用户说 "文件大小显示5M, 前端进行压缩处理"，可能指压缩目标是5M，或者限制原文件5M?
    // 通常是限制上传大小。如果原文件很大，压缩后小于5M也可以。
    // 这里暂时不做严格的源文件大小限制，交给压缩处理，或者设置一个合理的上限比如 20MB。
    return true;
  },

  /**
   * 压缩图片
   */
  async compressImage(file: File): Promise<File> {
    const options = {
      maxSizeMB: 5, // 限制最大 5MB
      maxWidthOrHeight: 1920, // 限制最大宽/高，防止过大图片
      useWebWorker: true,
      initialQuality: 0.8,
    };

    try {
      return await imageCompression(file, options);
    } catch (error) {
      console.error('Image compression error:', error);
      throw error;
    }
  },

  /**
   * 计算文件 MD5
   */
  calculateMD5(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const blobSlice = File.prototype.slice || (File.prototype as any).mozSlice || (File.prototype as any).webkitSlice;
      const chunkSize = 2097152; // 2MB
      const chunks = Math.ceil(file.size / chunkSize);
      let currentChunk = 0;
      const spark = new SparkMD5.ArrayBuffer();
      const fileReader = new FileReader();

      fileReader.onload = function (e) {
        if (e.target?.result) {
          spark.append(e.target.result as ArrayBuffer);
        }
        currentChunk++;

        if (currentChunk < chunks) {
          loadNext();
        } else {
          resolve(spark.end());
        }
      };

      fileReader.onerror = function () {
        reject('File read failed');
      };

      function loadNext() {
        const start = currentChunk * chunkSize;
        const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
      }

      loadNext();
    });
  },

  /**
   * 获取 OSS 签名
   */
  async getOssSignature(data: OssSignatureRequest) {
    return http.post<OssSignatureData>('/oss/signature_url', data);
  },

  /**
   * 上传文件到 OSS
   */
  async uploadToOss(url: string, file: File): Promise<void> {
    try {
      // OSS 直接 PUT 上传，不需要额外的 headers (除非签名里有要求，通常 Content-Type 需要匹配)
      // 注意：Content-Type 需要根据文件实际类型设置，或者 application/octet-stream
      // 这里使用文件类型
      await fetch(url, {
        method: 'PUT',
        body: file,
        headers: {
          // 有些 OSS 签名会绑定 Content-Type，如果不一致会报错。
          // 这里尽量使用 file.type，如果为空则设为 application/octet-stream
          // 如果签名不限制 Content-Type，则无所谓。
          // 按照常规 OSS PutObject，建议带上 type
          'Content-Type': file.type || 'application/octet-stream',
        }
      });
    } catch (error) {
      console.error('Upload to OSS failed:', error);
      throw new Error('文件上传失败');
    }
  }
};
