import { Inject, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { ConfigType } from "@nestjs/config";
import { Client } from "minio";
import { Readable } from "stream";
import { minioConfig } from "./minio.config";

@Injectable()
export class MinioService {
  private readonly client: Client;
  private readonly logger = new Logger(MinioService.name);
  private readonly bucket: string;

  constructor(
    @Inject(minioConfig.KEY)
    private readonly config: ConfigType<typeof minioConfig>,
  ) {
    console.log('[MinioService] Configuration loaded:', {
      endPoint: config.endPoint,
      port: config.port,
      useSSL: config.useSSL,
      accessKey: config.accessKey,
      secretKey: config.secretKey ? '***HIDDEN***' : 'EMPTY',
      bucket: config.bucket,
    });
    this.bucket = config.bucket;
    this.client = new Client({
      endPoint: config.endPoint,
      port: config.port,
      useSSL: config.useSSL,
      accessKey: config.accessKey,
      secretKey: config.secretKey,
      // Presetting the region keeps presignedGetObject fully local — no live
      // GetBucketLocation round-trip to the endpoint on each signed URL.
      region: config.region,
    });
  }

  async uploadFile(
    objectName: string,
    buffer: Buffer,
    contentType: string,
  ): Promise<string> {
    try {
      await this.client.putObject(this.bucket, objectName, buffer, buffer.length, {
        "Content-Type": contentType,
      });
      this.logger.log(`File uploaded successfully: ${objectName}`);
      return this.getPublicUrl(objectName);
    } catch (error) {
      this.logger.error(`Failed to upload file ${objectName}:`, error);
      throw error;
    }
  }

  getPublicUrl(objectName: string): string {
    const protocol = this.config.useSSL ? "https" : "http";
    return `${protocol}://${this.config.endPoint}:${this.config.port}/${this.bucket}/${objectName}`;
  }

  getObjectNameFromUrl(value: string): string {
    const trimmed = value.trim();
    if (!trimmed) {
      throw new NotFoundException("File object path is empty");
    }

    if (!/^https?:\/\//i.test(trimmed)) {
      return trimmed.replace(/^\/+/, "");
    }

    const url = new URL(trimmed);
    // `url.pathname` percent-encodes the object key (e.g. a space becomes
    // "%20"), but MinIO stores the key with its literal characters. Decode each
    // segment so the recovered key matches what was uploaded — otherwise a file
    // whose name had spaces/unicode 404s with "specified key does not exist".
    const parts = url.pathname
      .split("/")
      .filter(Boolean)
      .map((segment) => decodeURIComponent(segment));
    if (parts[0] === this.bucket) {
      parts.shift();
    }

    const objectName = parts.join("/");
    if (!objectName) {
      throw new NotFoundException("File object path is empty");
    }

    return objectName;
  }

  async deleteFile(objectName: string): Promise<void> {
    try {
      await this.client.removeObject(this.bucket, objectName);
      this.logger.log(`File deleted successfully: ${objectName}`);
    } catch (error) {
      this.logger.error(`Failed to delete file ${objectName}:`, error);
      throw error;
    }
  }

  async getFileStream(objectName: string): Promise<Readable> {
    try {
      return this.client.getObject(this.bucket, objectName);
    } catch (error) {
      this.logger.error(`Failed to get file ${objectName}:`, error);
      throw error;
    }
  }

  async getSignedUrl(objectName: string, expirySeconds: number = 300): Promise<string> {
    try {
      return await this.client.presignedGetObject(this.bucket, objectName, expirySeconds);
    } catch (error) {
      // Signing a file URL must never break a booking/transition response — the
      // caller only needs SOMETHING to link to. Degrade to the public object URL
      // and log, rather than throwing (which would 500 an otherwise-good load).
      this.logger.error(`Failed to generate signed URL for ${objectName}:`, error);
      return this.getPublicUrl(objectName);
    }
  }
}
