import {
  BadGatewayException,
  BadRequestException,
  Injectable,
} from '@nestjs/common';
import { UploadApiResponse, v2 as cloudinary } from 'cloudinary';
import { Readable } from 'node:stream';
import { UploadImageOptions } from './interfaces/upload-image-options.interface';
import sharp from 'sharp';
import { CloudinaryConfigService } from '../../../config/image-management/cloudinary/config.service';

@Injectable()
export class CloudinaryService {
  constructor(
    private readonly _cloudinaryConfigService: CloudinaryConfigService,
  ) {}

  get cloudUrl(): string {
    return `${this._cloudinaryConfigService.baseUrl}/${this._cloudinaryConfigService.cloudName}`;
  }

  private _imageFormats = {
    jpeg: {
      compression: { quality: 70 },
    },
    png: {
      compression: { quality: 70, compressionLevel: 7 },
    },
  };

  private async _validateAndCompressImage(imageFile: Express.Multer.File) {
    const image = sharp(imageFile.buffer);

    const { format } = await image.metadata().catch(() => {
      throw new BadRequestException('Arquivo deve ser uma imagem');
    });

    const allowedFormats = Object.keys(this._imageFormats);

    if (!allowedFormats.includes(format)) {
      const formatsList =
        allowedFormats.slice(0, -1).join(', ') +
        `${
          allowedFormats.length > 1
            ? ` ou ${allowedFormats.slice(-1)}`
            : `${allowedFormats.slice(-1)}`
        }`;

      throw new BadRequestException(
        `Formato de imagem deve ser do tipo ${formatsList}`,
      );
    }

    return await image.rotate()[format](this._imageFormats[format].compression);
  }

  async uploadImage(
    imageFile: Express.Multer.File,
    options: UploadImageOptions,
  ): Promise<UploadApiResponse> {
    const image = await this._validateAndCompressImage(imageFile);

    return new Promise<UploadApiResponse>(async (resolve, reject) => {
      const allowedFormats = Object.keys(this._imageFormats);

      const uploadStream = cloudinary.uploader.upload_stream(
        {
          discard_original_filename: true,
          folder: options.folder,
          use_filename: !!options.fileName,
          unique_filename: options.generateRandomSuffix,
          filename_override: options.fileName,
          allowed_formats: allowedFormats,
          invalidate: true,
          overwrite: true,
        },
        (error, result) => {
          if (error) return reject(error);

          resolve(result);
        },
      );

      Readable.from(await image.toBuffer()).pipe(uploadStream);
    }).catch(() => {
      throw new BadGatewayException();
    });
  }

  async deleteImage(publicId: string) {
    return new Promise<boolean>((resolve, reject) => {
      cloudinary.uploader.destroy(
        publicId,
        { invalidate: true },
        (error, { result }) => {
          if (error) return reject(error);

          resolve(result === 'ok');
        },
      );
    }).catch(() => {
      throw new BadGatewayException();
    });
  }
}
