import { Injectable, Logger } from '@nestjs/common';
import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  DeleteObjectCommand,
  HeadObjectCommand,
} from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Readable } from 'stream';

export interface UploadResult {
  key: string;
  bucket: string;
  url: string;
  size: number;
  mimeType: string;
}

export interface ImageMetadata {
  width: number;
  height: number;
  format: string;
  size: number;
}

@Injectable()
export class StorageService {
  private readonly logger = new Logger(StorageService.name);
  private s3Client: S3Client;
  private readonly bucketName = 'maternal-app';
  private readonly endpoint =
    process.env.MINIO_ENDPOINT || 'http://localhost:9002';
  private readonly region = process.env.MINIO_REGION || 'us-east-1';
  private sharpInstance: any = null;

  private async getSharp() {
    if (!this.sharpInstance) {
      try {
        this.sharpInstance = (await import('sharp')).default;
      } catch (error) {
        this.logger.warn(
          'Sharp library not available - image processing disabled',
        );
        throw new Error('Image processing not available on this platform');
      }
    }
    return this.sharpInstance;
  }

  constructor() {
    this.s3Client = new S3Client({
      endpoint: this.endpoint,
      region: this.region,
      credentials: {
        accessKeyId: process.env.MINIO_ACCESS_KEY || 'maternal_minio_admin',
        secretAccessKey:
          process.env.MINIO_SECRET_KEY || 'maternal_minio_password_2024',
      },
      forcePathStyle: true, // Required for MinIO
    });

    this.ensureBucketExists();
  }

  /**
   * Ensure the bucket exists (create if it doesn't)
   */
  private async ensureBucketExists(): Promise<void> {
    try {
      await this.s3Client.send(
        new HeadObjectCommand({
          Bucket: this.bucketName,
          Key: '.keep',
        }),
      );
      this.logger.log(`Bucket ${this.bucketName} exists`);
    } catch (error) {
      // Bucket likely doesn't exist, but we'll let upload fail if there's an actual issue
      this.logger.warn(
        `Bucket ${this.bucketName} may not exist. Will be created on first upload.`,
      );
    }
  }

  /**
   * Upload a file to MinIO
   */
  async uploadFile(
    buffer: Buffer,
    key: string,
    mimeType: string,
    metadata?: Record<string, string>,
  ): Promise<UploadResult> {
    try {
      const upload = new Upload({
        client: this.s3Client,
        params: {
          Bucket: this.bucketName,
          Key: key,
          Body: buffer,
          ContentType: mimeType,
          Metadata: metadata || {},
        },
      });

      await upload.done();

      this.logger.log(`File uploaded successfully: ${key}`);

      return {
        key,
        bucket: this.bucketName,
        url: `${this.endpoint}/${this.bucketName}/${key}`,
        size: buffer.length,
        mimeType,
      };
    } catch (error) {
      this.logger.error(`Failed to upload file: ${key}`, error);
      throw new Error(`File upload failed: ${error.message}`);
    }
  }

  /**
   * Upload an image with automatic optimization
   */
  async uploadImage(
    buffer: Buffer,
    key: string,
    options?: {
      maxWidth?: number;
      maxHeight?: number;
      quality?: number;
    },
  ): Promise<UploadResult & { metadata: ImageMetadata }> {
    try {
      const sharp = await this.getSharp();

      // Get original image metadata
      const imageInfo = await sharp(buffer).metadata();

      // Optimize image
      let optimizedBuffer = buffer;
      const maxWidth = options?.maxWidth || 1920;
      const maxHeight = options?.maxHeight || 1920;
      const quality = options?.quality || 85;

      // Resize if needed
      if (imageInfo.width > maxWidth || imageInfo.height > maxHeight) {
        optimizedBuffer = await sharp(buffer)
          .resize(maxWidth, maxHeight, {
            fit: 'inside',
            withoutEnlargement: true,
          })
          .jpeg({ quality })
          .toBuffer();
      } else {
        // Just optimize quality
        optimizedBuffer = await sharp(buffer).jpeg({ quality }).toBuffer();
      }

      const result = await this.uploadFile(optimizedBuffer, key, 'image/jpeg', {
        originalWidth: imageInfo.width?.toString() || '',
        originalHeight: imageInfo.height?.toString() || '',
        originalFormat: imageInfo.format || '',
      });

      const optimizedInfo = await sharp(optimizedBuffer).metadata();

      return {
        ...result,
        metadata: {
          width: optimizedInfo.width || 0,
          height: optimizedInfo.height || 0,
          format: optimizedInfo.format || 'jpeg',
          size: optimizedBuffer.length,
        },
      };
    } catch (error) {
      this.logger.error(`Failed to upload image: ${key}`, error);
      throw new Error(`Image upload failed: ${error.message}`);
    }
  }

  /**
   * Generate a thumbnail for an image
   */
  async generateThumbnail(
    buffer: Buffer,
    key: string,
    width: number = 200,
    height: number = 200,
  ): Promise<UploadResult> {
    try {
      const sharp = await this.getSharp();
      const thumbnailBuffer = await sharp(buffer)
        .resize(width, height, {
          fit: 'cover',
          position: 'center',
        })
        .jpeg({ quality: 80 })
        .toBuffer();

      return this.uploadFile(thumbnailBuffer, key, 'image/jpeg');
    } catch (error) {
      this.logger.error(`Failed to generate thumbnail: ${key}`, error);
      throw new Error(`Thumbnail generation failed: ${error.message}`);
    }
  }

  /**
   * Get a presigned URL for downloading a file
   */
  async getPresignedUrl(
    key: string,
    expiresIn: number = 3600,
  ): Promise<string> {
    try {
      const command = new GetObjectCommand({
        Bucket: this.bucketName,
        Key: key,
      });

      return getSignedUrl(this.s3Client, command, { expiresIn });
    } catch (error) {
      this.logger.error(`Failed to generate presigned URL: ${key}`, error);
      throw new Error(`Presigned URL generation failed: ${error.message}`);
    }
  }

  /**
   * Get file as buffer
   */
  async getFile(key: string): Promise<Buffer> {
    try {
      const command = new GetObjectCommand({
        Bucket: this.bucketName,
        Key: key,
      });

      const response = await this.s3Client.send(command);
      const stream = response.Body as Readable;

      return new Promise((resolve, reject) => {
        const chunks: Buffer[] = [];
        stream.on('data', (chunk) => chunks.push(chunk));
        stream.on('error', reject);
        stream.on('end', () => resolve(Buffer.concat(chunks)));
      });
    } catch (error) {
      this.logger.error(`Failed to get file: ${key}`, error);
      throw new Error(`File retrieval failed: ${error.message}`);
    }
  }

  /**
   * Delete a file from MinIO
   */
  async deleteFile(key: string): Promise<void> {
    try {
      const command = new DeleteObjectCommand({
        Bucket: this.bucketName,
        Key: key,
      });

      await this.s3Client.send(command);
      this.logger.log(`File deleted successfully: ${key}`);
    } catch (error) {
      this.logger.error(`Failed to delete file: ${key}`, error);
      throw new Error(`File deletion failed: ${error.message}`);
    }
  }

  /**
   * Check if a file exists
   */
  async fileExists(key: string): Promise<boolean> {
    try {
      const command = new HeadObjectCommand({
        Bucket: this.bucketName,
        Key: key,
      });

      await this.s3Client.send(command);
      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * Get image metadata without downloading
   */
  async getImageMetadata(key: string): Promise<ImageMetadata | null> {
    try {
      const sharp = await this.getSharp();
      const buffer = await this.getFile(key);
      const metadata = await sharp(buffer).metadata();

      return {
        width: metadata.width || 0,
        height: metadata.height || 0,
        format: metadata.format || '',
        size: buffer.length,
      };
    } catch (error) {
      this.logger.error(`Failed to get image metadata: ${key}`, error);
      return null;
    }
  }
}
