// 注意: 使用前请先安装依赖: npm install @volcengine/tos-sdk mime-types axios @types/node
import { TosClient } from '@volcengine/tos-sdk';
import * as fs from 'fs';
import * as path from 'path';
import axios from 'axios';
import { lookup as mimeLookup } from 'mime-types';

export interface TOSClientConfig {
  accessKeyId: string;
  accessKeySecret: string;
  endpoint: string;
  region: string;
  bucketName: string;
  selfDomain: string;
  disableSSLWarnings?: boolean;
}

export interface UploadOptions {
  headers?: Record<string, string>;
  returnUrl?: boolean;
  expires?: number;
}

export class TOSClientWrapper {
  private client: TosClient;
  private bucketName: string;
  private selfDomain: string;
  private endpoint: string;

  constructor(config: TOSClientConfig) {
    const { 
      accessKeyId, 
      accessKeySecret, 
      endpoint, 
      region, 
      bucketName, 
      selfDomain,
      disableSSLWarnings = true 
    } = config;

    this.bucketName = bucketName;
    this.selfDomain = selfDomain;
    this.endpoint = endpoint;

    // 创建 TOS 客户端
    this.client = new TosClient({
      accessKeyId,
      accessKeySecret,
      endpoint: selfDomain, // 使用自定义域名作为 endpoint
      region,
      isCustomDomain: true, // 启用自定义域名
      connectionTimeout: 60000,  // 增加连接超时到60秒
      requestTimeout: 180000,    // 增加请求超时到3分钟
      maxRetryCount: 5           // 增加重试次数到5次
    });

    // 禁用 SSL 警告（如果需要）
    if (disableSSLWarnings && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0') {
      process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
    }
  }

  /**
   * 获取基础URL（不带签名参数）
   */
  getBaseUrl(objectKey: string): string {
    return `https://${this.selfDomain}/${objectKey}`;
  }

  /**
   * 生成带签名的临时访问URL
   */
  async generateUrl(objectKey: string, expires: number = 3600): Promise<string> {
    try {
      const result = this.client.getPreSignedUrl({
        bucket: this.bucketName,
        key: objectKey,
        method: 'GET',
        expires
      });
      return result;
    } catch (error) {
      throw new Error(`生成签名URL失败: ${error.message}`);
    }
  }

  /**
   * 上传字符串内容
   */
  async uploadString(
    contentStr: string,
    objectKey: string,
    options: UploadOptions = {}
  ): Promise<string> {
    const { headers = {}, returnUrl = true } = options;

    try {
      const result = await this.client.putObject({
        bucket: this.bucketName,
        key: objectKey,
        body: Buffer.from(contentStr, 'utf-8'),
        contentType: 'text/plain',
        ...headers
      });

      console.log(`upload_string http status code: ${result.statusCode}`);

      if (result.statusCode !== 200) {
        throw new Error(`上传失败，HTTP状态码: ${result.statusCode}`);
      }

      return returnUrl ? this.getBaseUrl(objectKey) : objectKey;
    } catch (error) {
      throw new Error(`上传字符串到TOS失败: ${error.message}`);
    }
  }

  /**
   * 上传本地文件
   */
  async uploadFile(
    localFilePath: string,
    objectKey?: string,
    options: UploadOptions = {}
  ): Promise<string> {
    const { headers = {}, returnUrl = true } = options;

    if (!fs.existsSync(localFilePath)) {
      throw new Error(`本地文件不存在: ${localFilePath}`);
    }

    // 如果没有指定 object_key，则使用文件名
    if (!objectKey) {
      objectKey = path.basename(localFilePath);
    }

    // 自动设置 Content-Type
    const contentType = mimeLookup(localFilePath) || 'application/octet-stream';

    try {
      const fileBuffer = fs.readFileSync(localFilePath);
      
      const result = await this.client.putObject({
        bucket: this.bucketName,
        key: objectKey,
        body: fileBuffer,
        contentType,
        ...headers
      });

      if (result.statusCode !== 200) {
        throw new Error(`上传失败，HTTP状态码: ${result.statusCode}`);
      }

      return returnUrl ? this.getBaseUrl(objectKey) : objectKey;
    } catch (error) {
      throw new Error(`上传文件到TOS失败: ${error.message}`);
    }
  }

  /**
   * 上传字节数据
   */
  async uploadBytes(
    data: Buffer,
    objectKey: string,
    contentType?: string,
    options: UploadOptions = {}
  ): Promise<string> {
    const { headers = {}, returnUrl = true } = options;

    try {
      const result = await this.client.putObject({
        bucket: this.bucketName,
        key: objectKey,
        body: data,
        contentType: contentType || 'application/octet-stream',
        ...headers
      });

      if (result.statusCode !== 200) {
        throw new Error(`上传失败，HTTP状态码: ${result.statusCode}`);
      }

      return returnUrl ? this.getBaseUrl(objectKey) : objectKey;
    } catch (error) {
      throw new Error(`上传字节数据到TOS失败: ${error.message}`);
    }
  }

  /**
   * 从网络URL下载文件并上传到TOS（带重试机制）
   */
  async uploadFromUrl(
    url: string,
    objectKey: string,
    options: UploadOptions & { timeout?: number; maxRetries?: number } = {}
  ): Promise<string> {
    const { headers = {}, timeout = 120000, returnUrl = true, maxRetries = 5 } = options;

    if (!url.startsWith('http://') && !url.startsWith('https://')) {
      throw new Error('URL必须以http://或https://开头');
    }

    let lastError: Error;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        // 下载文件
        const response = await axios.get(url, {
          responseType: 'arraybuffer',
          timeout
        });

        // 获取内容类型
        let contentType = response.headers['content-type'] || '';
        if (!contentType) {
          contentType = mimeLookup(url) || 'application/octet-stream';
        }

        // 上传到TOS
        return this.uploadBytes(
          Buffer.from(response.data),
          objectKey,
          contentType,
          { headers, returnUrl }
        );
      } catch (error) {
        lastError = error;
        
        const isTimeout = error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT';
        const isNetworkError = error.code === 'ENOTFOUND' || error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED';
        
        // 如果是最后一次尝试，或者是非网络/超时错误，直接抛出
        if (attempt === maxRetries || (!isTimeout && !isNetworkError)) {
          if (isTimeout) {
            throw new Error(`下载网络文件超时（重试${maxRetries}次后失败）: ${url}`);
          }
          throw new Error(`上传网络文件到TOS失败（重试${maxRetries}次后失败）: ${error.message}`);
        }
        
        // 等待一段时间后重试（指数退避）
        const delay = Math.min(2000 * Math.pow(2, attempt - 1), 30000); // 最大30秒
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    throw lastError;
  }

  /**
   * 格式化TOS对象键(路径)
   */
  private formatObjectKey(objectKey: string): string {
    // 如果 objectKey 包含 selfDomain，截取 selfDomain 后面的字符作为新的 objectKey
    if (this.selfDomain && objectKey.includes(this.selfDomain)) {
      const domainIndex = objectKey.indexOf(this.selfDomain);
      if (domainIndex !== -1) {
        // 截取 selfDomain 后面的部分，去掉开头的斜杠
        objectKey = objectKey.substring(domainIndex + this.selfDomain.length).replace(/^\/+/, '');
      }
    }
    return objectKey;
  }

  /**
   * 删除文件
   */
  async deleteFile(objectKey: string): Promise<boolean> {
    try {
      const formattedKey = this.formatObjectKey(objectKey);
      
      await this.client.deleteObject({
        bucket: this.bucketName,
        key: formattedKey
      });
      
      return true;
    } catch (error) {
      console.error(`删除文件失败: ${error.message}`);
      return false;
    }
  }

  /**
   * 从TOS下载文件并返回文件数据
   */
  async downloadFile(objectKey: string): Promise<Buffer> {
    try {
      const formattedKey = this.formatObjectKey(objectKey);
      
      const result = await this.client.getObjectV2({
        bucket: this.bucketName,
        key: formattedKey,
        dataType: 'buffer'
      });

      if (!result.data.content) {
        throw new Error(`文件内容为空: ${objectKey}`);
      }

      // getObjectV2 with dataType: 'buffer' 直接返回 Buffer
      return result.data.content as Buffer;
      
    } catch (error) {
      if (error.code === 'NoSuchKey') {
        throw new Error(`文件不存在: ${objectKey}`);
      }
      throw new Error(`下载文件失败: ${error.message}`);
    }
  }

  /**
   * 检查文件是否存在
   */
  async fileExists(objectKey: string): Promise<boolean> {
    try {
      const formattedKey = this.formatObjectKey(objectKey);
      
      await this.client.headObject({
        bucket: this.bucketName,
        key: formattedKey
      });
      
      return true;
    } catch (error) {
      if (error.code === 'NotFound' || error.code === 'NoSuchKey') {
        return false;
      }
      throw new Error(`检查文件存在性失败: ${error.message}`);
    }
  }

  /**
   * 获取文件信息
   */
  async getFileInfo(objectKey: string): Promise<any> {
    try {
      const formattedKey = this.formatObjectKey(objectKey);
      
      const result = await this.client.headObject({
        bucket: this.bucketName,
        key: formattedKey
      });
      
      return {
        contentLength: result.data['content-length'],
        contentType: result.data['content-type'],
        lastModified: result.data['last-modified'],
        etag: result.data.etag
      };
    } catch (error) {
      if (error.code === 'NotFound' || error.code === 'NoSuchKey') {
        throw new Error(`文件不存在: ${objectKey}`);
      }
      throw new Error(`获取文件信息失败: ${error.message}`);
    }
  }

  /**
   * 列出存储桶中的对象
   */
  async listObjects(prefix?: string, maxKeys: number = 1000): Promise<any[]> {
    try {
      const result = await this.client.listObjects({
        bucket: this.bucketName,
        prefix,
        maxKeys
      });
      
      return result.data.Contents || [];
    } catch (error) {
      throw new Error(`列出对象失败: ${error.message}`);
    }
  }
}

export default TOSClientWrapper;