/**
 * OSS直传工具函数
 * 
 * 该模块实现了阿里云OSS的Web直传功能，支持客户端直接上传文件到OSS存储桶
 * 参考文档：https://help.aliyun.com/zh/oss/user-guide/obtain-signature-information-from-the-server-and-upload-data-to-oss
 * 
 * 主要功能：
 * 1. 从后端获取OSS上传签名信息
 * 2. 生成全局唯一的文件名（UUID + 时间戳）
 * 3. 使用FormData构造POST请求直接上传到OSS
 * 4. 处理上传进度和错误
 * 5. 返回上传后的文件URL
 */

import { getOssSignature, registerOssFile } from '#/api/core/upload';

/**
 * OSS签名信息接口
 * 后端返回的签名数据结构
 */
export interface OssSignature {
  /** 阿里云AccessKeyId */
  accessKeyId: string;
  /** Base64编码的PostPolicy策略 */
  policy: string;
  /** 签名值 */
  signature: string;
  /** OSS服务端点URL */
  host: string;
  /** 存储桶名称（从后端返回，不能硬编码） */
  bucket: string;
  /** 文件路径前缀（仅作为路径前缀，完整文件名由前端生成） */
  key: string;
  /** 签名过期时间戳（Unix时间戳，秒级） */
  expire: number;
}

/**
 * 上传选项接口
 */
export interface UploadOptions {
  /** 要上传的文件对象 */
  file: File;
  /** 上传进度回调函数，参数为0-100的进度百分比 */
  onProgress?: (percent: number) => void;
}

/**
 * 上传结果接口
 */
export interface UploadResult {
  /** 上传后的文件完整URL */
  url: string;
  /** 文件在OSS中的objectName（相对路径） */
  objectName: string;
}

/**
 * 生成全局唯一的文件名
 * 
 * 文件名格式：xk_upload_日期/UUID_时间戳.扩展名
 * 例如：xk_upload_20250108/550e8400-e29b-41d4-a716-446655440000_1736323200.jpg
 * 
 * 规则说明：
 * - 基础路径：xk_upload_日期/ （日期格式：YYYYMMDD）
 * - 文件名：UUID_时间戳.扩展名
 * - UUID：使用浏览器原生crypto.randomUUID()生成，确保全局唯一
 * - 时间戳：使用Unix时间戳（秒级），通过Math.floor(Date.now() / 1000)获取
 * - 扩展名：从原始文件名提取
 * 
 * @param file 文件对象，用于获取文件名和扩展名
 * @returns 返回完整的objectName路径
 * 
 * @example
 * const objectName = generateObjectName(file);
 * // 返回: "xk_upload_20250108/550e8400-e29b-41d4-a716-446655440000_1736323200.jpg"
 */
export function generateObjectName(file: File): string {
  // 获取当前日期，格式：YYYYMMDD
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, '0');
  const day = String(today.getDate()).padStart(2, '0');
  const dateStr = `${year}${month}${day}`;

  // 生成UUID，使用浏览器原生API确保全局唯一
  // crypto.randomUUID() 是Web Crypto API的一部分，支持现代浏览器
  let uuid: string;
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    uuid = crypto.randomUUID();
  } else {
    // 降级方案：如果浏览器不支持randomUUID，使用替代方案
    // 生成类似UUID格式的随机字符串
    uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }

  // 获取Unix时间戳（秒级）
  // Date.now()返回毫秒级时间戳，除以1000并向下取整得到秒级时间戳
  const timestamp = Math.floor(Date.now() / 1000);

  // 获取文件扩展名
  // 从文件名中提取扩展名，如果没有扩展名则使用空字符串
  const fileName = file.name;
  const lastDotIndex = fileName.lastIndexOf('.');
  const extension = lastDotIndex > 0 ? fileName.substring(lastDotIndex + 1).toLowerCase() : '';

  // 组合成完整的objectName路径
  // 格式：xk_upload_日期/UUID_时间戳.扩展名
  const objectName = `xk_upload_${dateStr}/${uuid}_${timestamp}${extension ? '.' + extension : ''}`;

  return objectName;
}

/**
 * 根据 File 推断上传用 Content-Type（浏览器可能给空 type）
 */
function resolveUploadContentType(file: File): string {
  if (file.type && file.type !== 'application/octet-stream') {
    return file.type;
  }
  const name = (file.name || '').toLowerCase();
  if (name.endsWith('.pdf')) return 'application/pdf';
  if (name.endsWith('.png')) return 'image/png';
  if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg';
  if (name.endsWith('.gif')) return 'image/gif';
  if (name.endsWith('.webp')) return 'image/webp';
  if (name.endsWith('.bmp')) return 'image/bmp';
  return file.type || 'application/octet-stream';
}

/**
 * 从后端获取OSS上传签名信息
 * 
 * 该函数调用后端API获取OSS上传所需的签名信息，包括：
 * - accessKeyId: 阿里云访问密钥ID
 * - policy: Base64编码的上传策略
 * - signature: 签名值
 * - host: OSS服务端点
 * - bucket: 存储桶名称（从后端返回，确保配置灵活性）
 * - key: 文件路径前缀
 * - expire: 签名过期时间
 * 
 * 错误处理：
 * - 如果API调用失败，会抛出错误
 * - 调用方需要捕获错误并进行相应处理
 * 
 * @returns Promise<OssSignature> 返回签名信息对象
 * @throws 如果API调用失败或返回数据格式不正确，会抛出错误
 * 
 * @example
 * try {
 *   const signature = await getOssSignature();
 *   console.log('获取签名成功', signature);
 * } catch (error) {
 *   console.error('获取签名失败', error);
 * }
 */
export async function getOssSignatureFromBackend(): Promise<OssSignature> {
  try {
    // 调用后端API获取签名信息
    const response = await getOssSignature();
    
    // 验证返回数据的完整性
    if (!response || !response.accessKeyId || !response.policy || !response.signature) {
      throw new Error('后端返回的签名信息不完整');
    }

    // 确保bucket字段存在（重要：bucket必须从后端返回）
    if (!response.bucket) {
      throw new Error('后端未返回bucket信息，无法进行上传');
    }

    return response as OssSignature;
  } catch (error) {
    // 记录错误信息，便于调试
    console.error('获取OSS签名失败:', error);
    throw error;
  }
}

/**
 * 上传文件到OSS
 * 
 * 该函数实现了完整的OSS直传流程：
 * 1. 从后端获取签名信息
 * 2. 生成全局唯一的文件名
 * 3. 构造FormData并设置必要的字段
 * 4. 使用XMLHttpRequest发送POST请求到OSS
 * 5. 处理上传进度和错误
 * 6. 返回上传后的文件URL
 * 
 * 上传流程说明：
 * - 使用FormData构造multipart/form-data格式的请求
 * - 必须包含的字段：key, policy, OSSAccessKeyId, signature, file
 * - key字段使用生成的objectName
 * - 上传到OSS的host端点
 * - 上传成功后，文件URL格式：host/objectName
 * 
 * 进度处理：
 * - 使用XMLHttpRequest的upload.onprogress事件监听上传进度
 * - 进度值范围：0-100，表示上传百分比
 * - 通过onProgress回调函数通知调用方
 * 
 * 错误处理：
 * - 网络错误：捕获XMLHttpRequest的错误事件
 * - OSS错误：解析OSS返回的XML错误响应
 * - 签名过期：检查签名是否在有效期内
 * 
 * @param options 上传选项，包含文件和进度回调
 * @returns Promise<UploadResult> 返回上传结果，包含文件URL和objectName
 * @throws 如果上传失败，会抛出包含错误信息的异常
 * 
 * @example
 * try {
 *   const result = await uploadToOss({
 *     file: fileObject,
 *     onProgress: (percent) => {
 *       console.log(`上传进度: ${percent}%`);
 *     }
 *   });
 *   console.log('上传成功，文件URL:', result.url);
 * } catch (error) {
 *   console.error('上传失败', error);
 * }
 */
export async function uploadToOss(options: UploadOptions): Promise<UploadResult> {
  const { file, onProgress } = options;

  // 步骤1：从后端获取OSS签名信息
  // 签名信息包含上传所需的所有认证参数
  const signature = await getOssSignatureFromBackend();

  // 步骤2：生成全局唯一的文件名
  // 使用UUID + 时间戳确保文件名全局唯一，避免冲突
  const objectName = generateObjectName(file);

  // 步骤3：构造FormData，设置OSS上传所需的字段
  // FormData用于构造multipart/form-data格式的请求体
  const formData = new FormData();
  
  // key: 文件在OSS中的完整路径（objectName）
  // 这是OSS中文件的唯一标识
  formData.append('key', objectName);
  
  // policy: Base64编码的上传策略
  // 策略定义了上传的条件，如文件大小限制、允许的文件类型等
  formData.append('policy', signature.policy);
  
  // OSSAccessKeyId: 阿里云访问密钥ID
  // 用于标识上传请求的发起者
  formData.append('OSSAccessKeyId', signature.accessKeyId);
  
  // signature: 签名值
  // 使用AccessKeySecret对policy进行签名，确保策略未被篡改
  formData.append('signature', signature.signature);
  
  // success_action_status: 指定上传成功后OSS返回的HTTP状态码
  // 默认是204（无内容），设置为200后OSS返回200状态码
  // 这样前端可以通过200状态码明确判断上传成功
  // 注意：此字段必须与后端Policy中的条件一致，否则会上传失败
  formData.append('success_action_status', '200');
  
  // x-oss-object-acl: 指定文件的访问权限（ACL）
  // 设置为 public-read 表示文件可以被公开访问，无需签名URL
  // 这与后端上传保持一致（后端上传后会调用putObjectAcl设置公共读）
  // 注意：此字段必须与后端Policy中的条件一致，否则会上传失败
  formData.append('x-oss-object-acl', 'public-read');

  // Content-Type：按文件 MIME 写入，避免 PDF 被当成 octet-stream 导致浏览器直接下载
  const contentType = resolveUploadContentType(file);
  formData.append('Content-Type', contentType);
  // Content-Disposition: inline —— 浏览器倾向预览而非附件下载
  formData.append('Content-Disposition', 'inline');
  
  // file: 要上传的文件
  // 必须是最后一个字段，OSS要求file字段在FormData的最后
  formData.append('file', file);

  // 步骤4：使用XMLHttpRequest发送POST请求到OSS
  // 使用XMLHttpRequest而不是fetch，因为需要监听上传进度
  return new Promise<UploadResult>((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    // 监听上传进度
    // upload.onprogress事件在上传过程中会多次触发
    if (onProgress && xhr.upload) {
      xhr.upload.addEventListener('progress', (event) => {
        if (event.lengthComputable) {
          // 计算上传进度百分比
          // loaded: 已上传的字节数
          // total: 文件总字节数
          const percent = Math.round((event.loaded / event.total) * 100);
          onProgress(percent);
        }
      });
    }

    // 监听请求完成事件
    xhr.addEventListener('load', () => {
      // 检查HTTP状态码
      // 200-299表示成功，204表示成功但无内容
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 204) {
        // 上传成功，构造文件URL
        // URL格式：host/objectName
        // 注意：使用后端返回的host，确保配置的灵活性
        const fileUrl = `${signature.host}/${objectName}`;

        registerOssFile({ url: fileUrl, source: 0, file_size: file.size, file_name: file.name }).catch((err) => {
          console.warn('OSS 文件登记失败:', err);
        });

        resolve({
          url: fileUrl,
          objectName: objectName,
        });
      } else {
        // HTTP状态码表示错误，尝试解析OSS返回的错误信息
        let errorMessage = `上传失败，HTTP状态码: ${xhr.status}`;
        
        try {
          // OSS错误响应通常是XML格式
          const parser = new DOMParser();
          const xmlDoc = parser.parseFromString(xhr.responseText, 'text/xml');
          const errorNode = xmlDoc.querySelector('Error');
          
          if (errorNode) {
            const codeNode = errorNode.querySelector('Code');
            const messageNode = errorNode.querySelector('Message');
            
            if (codeNode && messageNode) {
              errorMessage = `OSS错误 [${codeNode.textContent}]: ${messageNode.textContent}`;
            }
          }
        } catch (parseError) {
          // 如果解析失败，使用默认错误信息
          console.warn('解析OSS错误响应失败:', parseError);
        }
        
        reject(new Error(errorMessage));
      }
    });

    // 监听网络错误事件
    // 当网络请求失败时触发（如网络断开、超时等）
    xhr.addEventListener('error', () => {
      reject(new Error('网络错误：无法连接到OSS服务器'));
    });

    // 监听请求超时事件
    // 如果请求时间超过timeout设置的时间，会触发此事件
    xhr.addEventListener('timeout', () => {
      reject(new Error('上传超时：请求时间过长'));
    });

    // 设置请求超时时间（30秒）
    // 对于大文件，可能需要增加超时时间
    xhr.timeout = 30000;

    // 打开POST请求
    // 使用后端返回的host作为请求URL
    xhr.open('POST', signature.host, true);

    // 发送请求
    // 将FormData作为请求体发送
    xhr.send(formData);
  });
}

