<?php

namespace traits;

use Aws\S3\S3Client;
use classes\wash_certificate_store;

trait minio_t
{
    /**
     * The bucket to store the files in
     * @return string
     */
    private string $bucket;
    /**
     * The S3 client to interact with the Minio server
     * @return S3Client
     */
    private s3Client $s3Client;

    /**
     * List all the files in the bucket
     * @return array
     */
    public function listFiles(): array
    {
        $objects = self::getS3Client()->listObjects([
            'Bucket' => self::getBucket()
        ]);
        // If there are no files, return an empty array
        if (!isset($objects['Contents'])) {
            return [];
        }
        return $objects['Contents'];
    }

    /**
     * Returns the S3 client to interact with the Minio server
     * @return S3Client
     */
    public function getS3Client(): S3Client
    {
        // If the S3 client is not set, create a new one
        if (!isset($this->s3Client)) {
            $this->connect();
        }
        return $this->s3Client;
    }

    /**
     * Connect to the Minio server
     */
    private function connect(): self
    {
        $this->s3Client = new S3Client([
            'version' => 'latest',
            'region' => 'us-east-1',
            'endpoint' => $this->getEndpoint(),
            'use_path_style_endpoint' => true,
            'credentials' => [
                'key' => $this->getAccessKey(),
                'secret' => $this->getSecretKey(),
            ],
        ]);
        return $this;
    }

    /**
     * Returns the endpoint of the Minio server
     * @return string
     */
    public function getEndpoint(): string
    {
        global $MINIO;
        return $MINIO['endpoint'];
    }

    /**
     * Returns the access key of the Minio server
     * @return string
     */
    public function getAccessKey(): string
    {
        global $MINIO;
        return $MINIO['access_key'];
    }

    /**
     * Returns the secret key of the Minio server
     * @return string
     */
    public function getSecretKey(): string
    {
        global $MINIO;
        return $MINIO['secret_key'];
    }

    /**
     * Returns the bucket to store the files in
     * @return string
     */
    public function getBucket(): string
    {
        return $this->bucket;
    }

    /**
     * Sets the bucket to store the files in
     * @param string $bucket
     * @return wash_certificate_store|minio_t
     */
    public function setBucket(string $bucket): self
    {
        $this->bucket = $bucket;
        return $this;
    }

    /**
     * Create a new object in the bucket
     * @param string $key The key of the object (file)
     * @param string $body The content of the object (file)
     * @return bool
     */
    public function createObject(string $key, string $body): bool
    {
        $result = self::getS3Client()->putObject([
            'Bucket' => self::getBucket(),
            'Key' => $key,
            'Body' => $body
        ]);
        return $result['@metadata']['statusCode'] == 200;
    }

    /**
     * Get the URL of the object in the bucket
     * @param string $key The key of the object (file)
     * @return string
     */
    public function getObjectUrl(string $key): string
    {
        return self::getS3Client()->getObjectUrl(self::getBucket(), $key);
    }

    /**
     * Generate a presigned URL for the object in the bucket (valid for 20 minutes)
     * @param string $key The key of the object (file)
     * @param int|null $expires The expiration time in seconds (default is not set, which means 20 minutes)
     * @param bool $isUpload Whether the URL is for uploading (true) or downloading (false)
     * @note If $expires is null, it defaults to 1200 seconds (20 minutes).
     * @note If $isUpload is true, the URL will be for uploading the object.
     * @note If $isUpload is false, the URL will be for downloading the object
     * @return string
     */
    public function getPresignedUrl(string $key, ?int $expires = null, bool $isUpload = false): string
    {
        $commandAction = $isUpload ? 'PutObject' : 'GetObject';
        $command = self::getS3Client()->getCommand($commandAction, [
            'Bucket' => self::getBucket(),
            'Key' => $key,
        ]);
        $duration_seconds = $expires ?? 1200; // Default to 20 minutes if not specified
        $duration_attribute = '+' . ($duration_seconds / 60) . ' minutes';
        return (string)self::getS3Client()->createPresignedRequest($command, $duration_attribute)->getUri();
    }

    /**
     * Upload a file to the bucket
     * @param string $key The key of the object (file)
     * @param string $file The path to the file to upload
     * @return bool
     */
    public function uploadFile(string $key, string $file): bool
    {
        $result = self::getS3Client()->putObject([
            'Bucket' => self::getBucket(),
            'Key' => $key,
            'SourceFile' => $file
        ]);
        return $result['@metadata']['statusCode'] == 200;
    }

    /**
     * Check if the object exists in the bucket
     * @param string $key The key of the object (file or folder)
     * @return bool
     */
    public function doesObjectExist(string $key): bool
    {
        return self::getS3Client()->doesObjectExist(self::getBucket(), $key);
    }

    /**
     * Store temp file from base64 string
     * @param string $base64 The base64 encoded string
     * @param string $fileExtension The file extension (e.g., 'txt', 'jpg')
     * @return string|bool The path to the temporary file if successful, false otherwise
     */
    public function storeTempFileFromBase64(string $base64, string $fileExtension = 'txt'): string|bool
    {
        // Check if the base64 string is valid
        if (empty($base64)) {
            return false; // Invalid base64 string
        }

        // Check if the base64 string has a data URI prefix
        if (preg_match('/^data:.*;base64,/', $base64)) {
            $base64 = preg_replace('/^data:.*;base64,/', '', $base64);
        }

        // Decode the base64 data
        $fileData = base64_decode($base64);
        if ($fileData === false) {
            return false; // Failed to decode base64 data
        }
        // Temporary file path
        $tempFileName = uniqid('temp_file_', true) . '.' . $fileExtension;
        $tempFilePath = '/tmp/' . $tempFileName;
        // Create a temporary file
        $tempFile = fopen($tempFilePath, 'wb');
        if ($tempFile === false) {
            return false; // Failed to create temp file
        }

        // Write the file data to the temporary file
        if (fwrite($tempFile, $fileData) === false) {
            fclose($tempFile);
            return false; // Failed to write to temp file
        }
        fclose($tempFile);
        // Upload the temporary file to the bucket
        $result = self::uploadFile($tempFileName, $tempFilePath);
        // Clean up the temporary file
        unlink($tempFilePath);
        return $result ? $tempFileName : false;
    }

    /**
     * Store temp image file from base64 string
     * @param string $base64 The base64 encoded image string (E.g. data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAA)
     * @return string|bool The path to the temporary file if successful, false otherwise
     */
    public function storeTempImageFromBase64(string $base64): string|bool
    {
        // Check if the base64 string is valid
        if (empty($base64)) {
            return false; // Invalid base64 string
        }

        // Extract the base64 data from the string if it has a data URI prefix
        $imageData = $base64;
        if (preg_match('/^data:image\/(\w+);base64,/', $base64)) {
            $imageData = preg_replace('/^data:image\/\w+;base64,/', '', $base64);
        }

        // Decode the base64 data
        $imageData = base64_decode($imageData);
        if ($imageData === false) {
            return false; // Failed to decode base64 data
        }
        // Temporary file path
        $tempFileName = uniqid('temp_image_', true) . '.' . $this->getImageExtension($base64);
        $tempFilePath = '/tmp/' . $tempFileName;
        // Create a temporary file
        $tempFile = fopen($tempFilePath, 'wb');
        if ($tempFile === false) {
            return false; // Failed to create temp file
        }

        // Write the image data to the temporary file
        if (fwrite($tempFile, $imageData) === false) {
            fclose($tempFile);
            return false; // Failed to write to temp file
        }
        fclose($tempFile);
        // Upload the temporary file to the bucket
        $result = self::uploadFile($tempFileName, $tempFilePath);
        // Clean up the temporary file
        unlink($tempFilePath);
        return $result ? $tempFileName : false;
    }

    /**
     * Get the image extension from the base64 string
     * @param string $base64 The base64 encoded image string
     * @return string The image extension (e.g., 'jpg', 'png')
     */
    private function getImageExtension(string $base64): string
    {
        // Check if the base64 string contains a data URL prefix
        if (preg_match('/^data:image\/(\w+);base64,/', $base64, $matches)) {
            // Return the image extension
            switch ($matches[1]) {
                case 'png':
                    return 'png';
                case 'gif':
                    return 'gif';
                case 'webp':
                    return 'webp';
                case 'bmp':
                    return 'bmp';
                default:
                    return 'jpg'; // Default to jpg if unknown
            }
        }
        // If no match, return a default extension (e.g., 'jpg')
        return 'jpg';
    }
}