import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import * as Minio from 'minio';
import { envs } from '../config/envs';

@Injectable()
export class MinioService implements OnModuleInit {
  private readonly minioClient: Minio.Client;
  private readonly logger = new Logger(MinioService.name);
  private readonly bucketName = envs.minio_bucket;

  constructor() {
    this.minioClient = new Minio.Client({
      endPoint: envs.minio_endpoint,
      port: envs.minio_port,
      useSSL: envs.minio_use_ssl,
      accessKey: envs.minio_access_key,
      secretKey: envs.minio_secret_key,
    });
  }

  async onModuleInit() {
    await this.ensureBucketExists();
  }

  private async ensureBucketExists() {
    // Ejecuta esto siempre al menos una vez para asegurar que sea público
    const policy = {
      Version: '2012-10-17',
      Statement: [
        {
          Effect: 'Allow',
          Principal: { AWS: ['*'] },
          Action: ['s3:GetObject'],
          Resource: [`arn:aws:s3:::${this.bucketName}/*`],
        },
      ],
    };
    try {
      // const bucketExists = await this.minioClient.bucketExists(this.bucketName);
      // if (!bucketExists) {
      //   await this.minioClient.makeBucket(this.bucketName);
      // }

      await this.minioClient.setBucketPolicy(
        this.bucketName,
        JSON.stringify(policy),
      );
      this.logger.log(`Public policy ensured for bucket "${this.bucketName}"`);
    } catch (error: any) {
      this.logger.error(`Error checking/creating bucket: ${error.message}`);
    }
  }

  async upload(
    file: Express.Multer.File,
    folder: string = 'general',
  ): Promise<string> {
    const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname.replace(/\s/g, '_')}`;
    const objectName = `${folder}/${fileName}`;

    try {
      await this.minioClient.putObject(
        this.bucketName,
        objectName,
        file.buffer,
        file.size,
        {
          'Content-Type': file.mimetype,
        },
      );

      // Return the URL or the object path.
      // Usually, we store the object path and generate a signed URL or use a proxy.
      // The user asked for the URL to be stored in the database.
      return objectName;
    } catch (error: any) {
      this.logger.error(`Error uploading file: ${error.message}`);
      throw error;
    }
  }

  async getFileUrl(objectName: string): Promise<string> {
    try {
      // If the bucket is public, we can just return the URL.
      // If private, we need a signed URL.
      // For simplicity and common use cases in these projects, I'll generate a signed URL with a long expiration
      // or assume there is some way to access it.
      // But let's use signed URL for 1 week (maximum is 7 days) if needed,
      // or just return the object name if the backend handles the serving.
      // The user wants the URL stored in the DB.

      return await this.minioClient.presignedUrl(
        'GET',
        this.bucketName,
        objectName,
        604800,
      );
    } catch (error: any) {
      this.logger.error(`Error getting file URL: ${error.message}`);
      throw error;
    }
  }

  getPublicUrl(objectName: string): string {
    const protocol = envs.minio_use_ssl ? 'https' : 'http';
    return `${protocol}://${envs.minio_endpoint}:${envs.minio_port}/${this.bucketName}/${objectName}`;
  }

  async delete(objectName: string): Promise<void> {
    try {
      // Ensure we don't have a leading slash which can cause issues with removeObject
      const cleanedName = objectName.startsWith('/')
        ? objectName.slice(1)
        : objectName;

      await this.minioClient.removeObject(this.bucketName, cleanedName);
      this.logger.log(
        `Object "${cleanedName}" deleted successfully from bucket "${this.bucketName}".`,
      );
    } catch (error: any) {
      this.logger.error(
        `Error deleting file "${objectName}": ${error.message}`,
      );
      // We don't necessarily want to throw if the file is already gone
    }
  }
}
