import { Client } from "minio";
import { createVaultClient } from "./vault.js";
import z from "zod";

export type BucketManagerParams = {
  vault: {
    path: string;
    schema: z.ZodObject<any>;
  };
};

export default class BucketManager {
  private minioClient: Client | null = null;
  public readonly initializationPromise: Promise<void>;
  private isInitialized: boolean = false;

  /**
   * Initializes the bucket manager
   * @param params - The parameters for the bucket manager
   */
  constructor(params: BucketManagerParams) {
    this.initializationPromise = this.initialize(params);
  }

  /**
   * Initializes the bucket manager's minio client
   * @param params - The parameters for the bucket manager
   */
  private async initialize(params: BucketManagerParams) {
    try {
      const vault = createVaultClient();

      const apiKeys = await vault.readSecret(
        params.vault.path,
        params.vault.schema
      );

      const minioConfig = {
        endPoint: process.env.MINIO_ENDPOINT || "localhost",
        port: parseInt(process.env.MINIO_PORT || "9000"),
        useSSL: process.env.MINIO_USE_SSL === "true",
        accessKey: apiKeys.MINIO_ACCESS_KEY,
        secretKey: apiKeys.MINIO_SECRET_KEY,
      };

      this.minioClient = new Client(minioConfig);
      this.isInitialized = true;
      console.log("✅ Bucket manager initialized successfully");
    } catch (error) {
      console.error("Error initializing bucket manager:", error);
      this.isInitialized = false;
      throw error;
    }
  }

  /**
   * Ensures the bucket manager is initialized before proceeding
   */
  private async ensureInitialized(): Promise<void> {
    if (!this.isInitialized) {
      await this.initializationPromise;
    }

    if (!this.minioClient) {
      throw new Error("Minio client not initialized");
    }
  }

  /**
   * Checks if a bucket exists and creates it if it doesn't
   * @param bucketName - The name of the bucket to check
   * @returns {Promise<boolean>} True if the bucket exists, false otherwise
   */
  async checkBucketExists(bucketName: string): Promise<boolean> {
    await this.ensureInitialized();

    const exists = await this.minioClient!.bucketExists(bucketName);
    if (!exists) {
      await this.minioClient!.makeBucket(bucketName);
      console.log(`✅ Created bucket: ${bucketName}`);
    }
    return exists;
  }

  /**
   * Uploads a file to a bucket
   * @param bucketName - The name of the bucket to upload to
   * @param filePath - The path to the file to upload
   * @param objectName - The name of the object to upload
   * @param metadata - Optional metadata to add to the file
   */
  async uploadFile(
    bucketName: string,
    filePath: string,
    objectName: string,
    metadata?: Record<string, string>
  ) {
    await this.ensureInitialized();

    await this.checkBucketExists(bucketName);

    await this.minioClient!.fPutObject(
      bucketName,
      objectName,
      filePath,
      metadata
    );
    console.log(`✅ Successfully uploaded: ${bucketName}/${objectName}`);
  }

  /**
   * Gets a video file from a bucket
   * @param bucketName - The name of the bucket
   * @param videoUuid - The UUID of the video
   * @param uuid - The user UUID
   * @returns {Promise<string>} The URL or path to the video file
   */
  async getVideo(
    bucketName: string,
    videoUuid: string,
    uuid: string
  ): Promise<string> {
    await this.ensureInitialized();

    await this.checkBucketExists(bucketName);

    const objectName = `${uuid}/${videoUuid}/video.mp4`;

    // Check if the video exists
    try {
      await this.minioClient!.statObject(bucketName, objectName);
      console.log(`✅ Video found: ${bucketName}/${objectName}`);

      // Return the presigned URL for the video (valid for 1 hour)
      const presignedUrl = await this.minioClient!.presignedGetObject(
        bucketName,
        objectName,
        3600
      );
      return presignedUrl;
    } catch (error) {
      console.error(`❌ Video not found: ${bucketName}/${objectName}`);
      throw new Error(`Video not found: ${objectName}`);
    }
  }

  /**
   * Gets a thumbnail image from a bucket
   * @param bucketName - The name of the bucket
   * @param videoUuid - The UUID of the video
   * @param uuid - The user UUID
   * @returns {Promise<string>} The URL or path to the thumbnail image
   */
  async getThumbnail(
    bucketName: string,
    videoUuid: string,
    uuid: string
  ): Promise<string> {
    await this.ensureInitialized();

    await this.checkBucketExists(bucketName);

    const objectName = `${uuid}/${videoUuid}/thumbnail.jpg`;

    // Check if the thumbnail exists
    try {
      await this.minioClient!.statObject(bucketName, objectName);
      console.log(`✅ Thumbnail found: ${bucketName}/${objectName}`);

      // Return the presigned URL for the thumbnail (valid for 1 hour)
      const presignedUrl = await this.minioClient!.presignedGetObject(
        bucketName,
        objectName,
        3600
      );
      return presignedUrl;
    } catch (error) {
      console.error(`❌ Thumbnail not found: ${bucketName}/${objectName}`);
      throw new Error(`Thumbnail not found: ${objectName}`);
    }
  }
}
