import { Client } from 'minio';
import { logger } from '@d8d/shared-utils';
import * as process from 'node:process';

export class MinioService {
  private readonly client: Client;
  public readonly bucketName: string;

  constructor() {
    this.client = new Client({
      endPoint: process.env.MINIO_DIY_HOST || process.env.MINIO_HOST || 'localhost',
      port: parseInt(process.env.MINIO_PORT || '443'),
      useSSL: process.env.MINIO_USE_SSL !== 'false',
      accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
      secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin'
    });
    this.bucketName = process.env.MINIO_BUCKET_NAME || 'd8dai';
  }

  // 设置桶策略为"公读私写"
  async setPublicReadPolicy(bucketName: string = this.bucketName) {
    const policy = {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {"AWS": "*"},
          "Action": ["s3:GetObject"],
          "Resource": [`arn:aws:s3:::${bucketName}/*`]
        },
        {
          "Effect": "Allow",
          "Principal": {"AWS": "*"},
          "Action": ["s3:ListBucket"],
          "Resource": [`arn:aws:s3:::${bucketName}`]
        }
      ]
    };

    try {
      await this.client.setBucketPolicy(bucketName, JSON.stringify(policy));
      logger.db(`Bucket policy set to public read for: ${bucketName}`);
    } catch (error) {
      logger.error(`Failed to set bucket policy for ${bucketName}:`, error);
      throw error;
    }
  }

  // 确保存储桶存在
  async ensureBucketExists(bucketName: string = this.bucketName) {
    try {
      const exists = await this.client.bucketExists(bucketName);
      if (!exists) {
        await this.client.makeBucket(bucketName);
        await this.setPublicReadPolicy(bucketName);
        logger.db(`Created new bucket: ${bucketName}`);
      }
      return true;
    } catch (error) {
      logger.error(`Failed to ensure bucket exists: ${bucketName}`, error);
      throw error;
    }
  }

  // 生成上传策略
  async generateUploadPolicy(fileKey: string) {
    await this.ensureBucketExists();

    const expiresAt = new Date(Date.now() + 3600 * 1000);
    const policy = this.client.newPostPolicy();
    policy.setBucket(this.bucketName);

    policy.setKey(fileKey);
    policy.setExpires(expiresAt);

    const { postURL, formData } = await this.client.presignedPostPolicy(policy);

    return {
      'x-amz-algorithm': formData['x-amz-algorithm'],
      'x-amz-credential': formData['x-amz-credential'],
      'x-amz-date': formData['x-amz-date'],
      'x-amz-security-token': formData['x-amz-security-token'] || undefined,
      policy: formData['policy'],
      'x-amz-signature': formData['x-amz-signature'],
      host: postURL,
      key: fileKey,
      bucket: this.bucketName,
    };
  }

  // 生成文件访问URL
  getFileUrl(bucketName: string, fileKey: string) {
    const protocol = process.env.MINIO_USE_SSL !== 'false' ? 'https' : 'http';
    const port = process.env.MINIO_PORT ? `:${process.env.MINIO_PORT}` : '';
    return `${protocol}://${process.env.MINIO_HOST}${port}/${bucketName}/${fileKey}`;
  }

  // 生成预签名文件访问URL（用于私有bucket）
  async getPresignedFileUrl(bucketName: string, fileKey: string, expiresInSeconds = 3600) {
    try {
      const url = await this.client.presignedGetObject(bucketName, fileKey, expiresInSeconds);
      logger.db(`Generated presigned URL for ${bucketName}/${fileKey}, expires in ${expiresInSeconds}s`);
      return url;
    } catch (error) {
      logger.error(`Failed to generate presigned URL for ${bucketName}/${fileKey}:`, error);
      throw error;
    }
  }

  // 生成预签名文件下载URL（带Content-Disposition头）
  async getPresignedFileDownloadUrl(bucketName: string, fileKey: string, filename: string, expiresInSeconds = 3600) {
    try {
      const url = await this.client.presignedGetObject(
        bucketName,
        fileKey,
        expiresInSeconds,
        {
          'response-content-disposition': `attachment; filename="${encodeURIComponent(filename)}"`,
          'response-content-type': 'application/octet-stream'
        }
      );
      logger.db(`Generated presigned download URL for ${bucketName}/${fileKey}, filename: ${filename}`);
      return url;
    } catch (error) {
      logger.error(`Failed to generate presigned download URL for ${bucketName}/${fileKey}:`, error);
      throw error;
    }
  }

  // 创建分段上传会话
  async createMultipartUpload(bucketName: string, objectName: string) {
    try {
      const uploadId = await this.client.initiateNewMultipartUpload(bucketName, objectName, {});
      logger.db(`Created multipart upload for ${objectName} with ID: ${uploadId}`);
      return uploadId;
    } catch (error) {
      logger.error(`Failed to create multipart upload for ${objectName}:`, error);
      throw error;
    }
  }

  // 生成分段上传预签名URL
  async generateMultipartUploadUrls(
    bucketName: string,
    objectName: string,
    uploadId: string,
    partCount: number,
    expiresInSeconds = 3600
  ) {
    try {
      const partUrls = [];
      for (let partNumber = 1; partNumber <= partCount; partNumber++) {
        const url = await this.client.presignedUrl(
          'put',
          bucketName,
          objectName,
          expiresInSeconds,
          {
            uploadId,
            partNumber: partNumber.toString()
          }
        );
        partUrls.push(url);
      }
      return partUrls;
    } catch (error) {
      logger.error(`Failed to generate multipart upload URLs for ${objectName}:`, error);
      throw error;
    }
  }

  // 完成分段上传
  async completeMultipartUpload(
    bucketName: string,
    objectName: string,
    uploadId: string,
    parts: { ETag: string; PartNumber: number }[]
  ): Promise<{ size: number }> {
    try {
      await this.client.completeMultipartUpload(
        bucketName,
        objectName,
        uploadId,
        parts.map(p => ({ part: p.PartNumber, etag: p.ETag }))
      );
      logger.db(`Completed multipart upload for ${objectName} with ID: ${uploadId}`);

      // 获取对象信息以获取文件大小
      const stat = await this.client.statObject(bucketName, objectName);
      return { size: stat.size };
    } catch (error) {
      logger.error(`Failed to complete multipart upload for ${objectName}:`, error);
      throw error;
    }
  }

  // 上传文件
  async createObject(bucketName: string, objectName: string, fileContent: Buffer, contentType: string = 'application/octet-stream') {
    try {
      await this.ensureBucketExists(bucketName);
      await this.client.putObject(bucketName, objectName, fileContent, fileContent.length, {
        'Content-Type': contentType
      });
      logger.db(`Created object: ${bucketName}/${objectName}`);
      return this.getFileUrl(bucketName, objectName);
    } catch (error) {
      logger.error(`Failed to create object ${bucketName}/${objectName}:`, error);
      throw error;
    }
  }

  // 检查文件是否存在
  async objectExists(bucketName: string, objectName: string): Promise<boolean> {
    try {
      await this.client.statObject(bucketName, objectName);
      return true;
    } catch (error) {
      if ((error as Error).message.includes('not found')) {
        return false;
      }
      logger.error(`Error checking existence of object ${bucketName}/${objectName}:`, error);
      throw error;
    }
  }

  // 删除文件
  async deleteObject(bucketName: string, objectName: string) {
    try {
      await this.client.removeObject(bucketName, objectName);
      logger.db(`Deleted object: ${bucketName}/${objectName}`);
    } catch (error) {
      logger.error(`Failed to delete object ${bucketName}/${objectName}:`, error);
      throw error;
    }
  }
}