<?php

use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Carbon\Carbon;

class MinioService
{
    public const LONG_EXPIRED_DURATION = '7 days';

    /**
     * @var Aws\S3\S3Client
     */
    private $s3;

    /**
     * @var Aws\S3\S3Client
     */
    private $secondaryS3;

    /**
     * @var boolean
     */
    private $useDualCloud;

    private function preprocessKey($key)
    {
        $key = str_replace("\\", "/", $key);
        $key = str_replace("//", "/", $key);
        $key = str_replace("//", "/", $key);
        $key = str_replace("//", "/", $key);
        $key = trim($key, "/");

        return $key;
    }

    public function __construct($s3)
    {
        $this->s3 = $s3;
        $this->useDualCloud = filter_var(getenv("USE_DUAL_CLOUD") ?? null, FILTER_VALIDATE_BOOLEAN);
        if ($this->useDualCloud) {
            $this->secondaryS3 = new Aws\S3\S3Client([
                'version'  => getenv("MINIO2_VERSION"),
                'region'   => getenv("MINIO2_REGION"),
                'endpoint' => getenv("MINIO2_ENDPOINT"),
                'http'     => [
                    'verify' => boolval(getenv("MINIO2_VERIFY_SSL"))
                ],
                'use_path_style_endpoint' => true,
                'credentials' => [
                    'key'    => getenv("MINIO2_KEY"),
                    'secret' => getenv("MINIO2_SECRET"),
                ],
            ]);
        }
    }

    public function base_get_public_url($key, $filename, $download, $duration = "60 minutes")
    {
        // MINIO VERSION
//        $url = $this->s3->getObjectUrl(getenv("MINIO_BUCKET_PUBLIC"), $this->preprocessKey($key));

//        if ($download) {
//            $url = $url . "?response-content-disposition=attachment" . urlencode("; filename=\"") . $filename . urlencode("\"");
//        }

//        return $url;

        // AWS S3 VERSION
        $option = [
            'Bucket' => getenv("MINIO_BUCKET_PUBLIC"),
            'Key'    => $this->preprocessKey($key),
        ];

        if ($download) {
            $option['ResponseContentDisposition'] = "attachment; filename=\"" . $filename . "\"";
        }

        $command = $this->s3->getCommand('GetObject', $option);

        $presignedRequest = $this->s3->createPresignedRequest($command, "+$duration");
        $presignedUrl =  (string)  $presignedRequest->getUri();

        return $presignedUrl;
    }

    public function base_get_private_url($key, $filename, $download)
    {
        $duration = getenv("PRIVATE_URL_DURATION");

        $option = [
            'Bucket' => getenv("MINIO_BUCKET_PRIVATE"),
            'Key'    => $this->preprocessKey($key),
        ];

        if ($download) {
            $option['ResponseContentDisposition'] = "attachment; filename=\"" . $filename . "\"";
        }

        $command = $this->s3->getCommand('GetObject', $option);

        $presignedRequest = $this->s3->createPresignedRequest($command, "+$duration minutes");
        $presignedUrl =  (string)  $presignedRequest->getUri();

        return $presignedUrl;
    }

    public function base_get_url($key, $filename, $download, $usePrimaryS3 = true, $isPublic = true) {
        if ($usePrimaryS3) {
            if ($isPublic) $presignedUrl = $this->base_get_public_url($key, $filename, $download);
            else $presignedUrl = $this->base_get_private_url($key, $filename, $download);
        } else {
            if ($isPublic) $presignedUrl = $this->basePublicUrlUsingSecondaryS3($key, $filename, $download);
            else $presignedUrl = $this->basePrivateUrlUsingSecondaryS3($key, $filename, $download);
        }
        return $presignedUrl;
    }

    public function base_get_aws_url($key, $filename, $download, $isPublic = true, $duration = null) {
        if ($this->isSecondaryS3Available()) {
            if ($isPublic) $presignedUrl = $this->basePublicUrlUsingSecondaryS3($key, $filename, $download, $duration);
            else $presignedUrl = $this->basePrivateUrlUsingSecondaryS3($key, $filename, $download);
        } else {
            if ($isPublic) {
                if (!$duration) {
                    $presignedUrl = $this->base_get_public_url($key, $filename, $download);
                } else {
                    $presignedUrl = $this->base_get_public_url($key, $filename, $download, $duration);
                }
            }
            else $presignedUrl = $this->base_get_private_url($key, $filename, $download);
        }
        return $presignedUrl;
    }

    public function base_upload_attachment($tempPath, $mime_type, $is_private, $key)
    {
        try {
            $insert = $this->s3->putObject([
                'Bucket' => $is_private ? getenv("MINIO_BUCKET_PRIVATE") : getenv("MINIO_BUCKET_PUBLIC"),
                'Key'    => $this->preprocessKey($key),
                'SourceFile'   => $tempPath,
                'ContentType' => $mime_type
            ]);
        } catch (Exception $e) {
            throw new CustomErrorException(BaseResponse::MINIO_ERROR, "Gagal upload file : " . $e->getMessage());
        }
    }

    public function deletion($key, $attachmentType)
    {
        $bucket = $attachmentType == Attachments::TIPE_PUBLIC ? getenv("MINIO_BUCKET_PUBLIC") : getenv("MINIO_BUCKET_PRIVATE");
        $isFound = false;
        try {
            $isFound = $this->s3->headObject([
                'Bucket' => $bucket,
                'Key'    => $this->preprocessKey($key),
            ]);
        } catch (\Exception $e) {}

        if ($isFound) {
            try {
                $this->s3->deleteObject([
                    'Bucket' => $bucket,
                    'Key' => $this->preprocessKey($key)
                ]);
            } catch (\Exception $e) {}
        } elseif ($this->isSecondaryS3Available()) {
            try {
                $isFound = $this->secondaryS3->headObject([
                    'Bucket' => $bucket,
                    'Key'    => $this->preprocessKey($key),
                ]);
            } catch (\Exception $e) {}
            if ($isFound) {
                try {
                    $this->secondaryS3->deleteObject([
                        'Bucket' => $bucket,
                        'Key' => $this->preprocessKey($key)
                    ]);
                } catch (\Exception $e) {}
            }
        }
    }

    public function delete($uuid, $is_private, $folder = "")
    {
        try {
            $result = $this->s3->deleteObject([
                'Bucket' => $is_private ? getenv("MINIO_BUCKET_PRIVATE") : getenv("MINIO_BUCKET_PUBLIC"),
                'Key'    => $folder . DIRECTORY_SEPARATOR . $uuid
            ]);
        } catch (Exception $e) {
            throw new CustomErrorException(BaseResponse::MINIO_ERROR, "Gagal delete file : " . $e->getMessage());
        }
    }

    public function deleteUsingSecondaryS3($uuid, $is_private, $folder = "")
    {
        try {
            $result = $this->secondaryS3->deleteObject([
                'Bucket' => $is_private ? getenv("MINIO_BUCKET_PRIVATE") : getenv("MINIO_BUCKET_PUBLIC"),
                'Key'    => $folder . DIRECTORY_SEPARATOR . $uuid
            ]);
        } catch (Exception $e) {
            throw new CustomErrorException(BaseResponse::MINIO_ERROR, "Gagal delete file : " . $e->getMessage());
        }
    }

    public function baseUploadUsingSecondaryS3($tempPath, $mime_type, $is_private, $key) {
        try {
            $this->secondaryS3->putObject([
                'Bucket' => $is_private ? getenv("MINIO2_BUCKET_PRIVATE") : getenv("MINIO2_BUCKET_PUBLIC"),
                'Key'    => $this->preprocessKey($key),
                'SourceFile'   => $tempPath,
                'ContentType' => $mime_type
            ]);
        } catch (Exception $e) {
            throw new CustomErrorException(BaseResponse::MINIO_ERROR, "Gagal upload file : " . $e->getMessage());
        }
    }

    public function basePrivateUrlUsingSecondaryS3($key, $filename, $download) {
        $duration = getenv("PRIVATE_URL_DURATION");

        $option = [
            'Bucket' => getenv("MINIO2_BUCKET_PRIVATE"),
            'Key'    => $this->preprocessKey($key),
        ];

        if ($download) {
            $option['ResponseContentDisposition'] = "attachment; filename=\"" . $filename . "\"";
        }

        $command = $this->secondaryS3->getCommand('GetObject', $option);

        $presignedRequest = $this->secondaryS3->createPresignedRequest($command, "+$duration minutes");
        $presignedUrl =  (string)  $presignedRequest->getUri();

        return $presignedUrl;
    }

    public function basePublicUrlUsingSecondaryS3($key, $filename, $download, $duration = null) {
        if (!$duration)
            $duration = getenv("PRIVATE_URL_DURATION") . " minutes";

        $option = [
            'Bucket' => getenv("MINIO2_BUCKET_PUBLIC"),
            'Key'    => $this->preprocessKey($key),
        ];

        if ($download) {
            $option['ResponseContentDisposition'] = "attachment; filename=\"" . $filename . "\"";
        }

        $command = $this->secondaryS3->getCommand('GetObject', $option);

        $presignedRequest = $this->secondaryS3->createPresignedRequest($command, "+$duration");
        $presignedUrl =  (string)  $presignedRequest->getUri();

        return $presignedUrl;
    }

    public static function checkFileExist($url) {
        $options = [
            'verify' => false
        ];
        $client = new GuzzleHttp\Client($options);
        try {
            $response = $client->request('GET', $url);
            $isFound = $response->getStatusCode() == 200;
        } catch (\Exception $e) {
            $isFound = false;
        }

        return $isFound;
    }

    public function isFileExist($modulName, $key, $isPublic = true, $usePrimaryS3 = true) {
        if ($usePrimaryS3 || !$this->isSecondaryS3Available()) {
            $s3 = $this->s3;
            $source = str_contains(getenv("MINIO_ENDPOINT"), "singgalang") ? "MINIO" : "AWS";
            if ($isPublic) $bucket = getenv("MINIO_BUCKET_PUBLIC");
            else $bucket = getenv("MINIO_BUCKET_PRIVATE");
        } else {
            $s3 = $this->secondaryS3;
            $source = "AWS";
            if ($isPublic) $bucket = getenv("MINIO2_BUCKET_PUBLIC");
            else $bucket = getenv("MINIO2_BUCKET_PRIVATE");
        }

        $isFound = true;
        try {
            $s3->headObject([
                'Bucket' => $bucket,
                'Key'    => $this->preprocessKey($key),
            ]);
        } catch (\Exception $e) {
            $isFound = false;
        }
        LogAttachments::insert($source, $bucket, $key, $modulName, ($isFound ? "FOUND" : "NOT FOUND"));
        return $isFound;
    }

    public function isFileExistNoLog($key, $isPublic = true, $usePrimaryS3 = true) {
        if ($usePrimaryS3 || !$this->isSecondaryS3Available()) {
            $s3 = $this->s3;
            if ($isPublic) $bucket = getenv("MINIO_BUCKET_PUBLIC");
            else $bucket = getenv("MINIO_BUCKET_PRIVATE");
        } else {
            $s3 = $this->secondaryS3;
            if ($isPublic) $bucket = getenv("MINIO2_BUCKET_PUBLIC");
            else $bucket = getenv("MINIO2_BUCKET_PRIVATE");
        }

        $isFound = true;
        try {
            $s3->headObject([
                'Bucket' => $bucket,
                'Key'    => $this->preprocessKey($key),
            ]);
        } catch (\Exception $e) {
            $isFound = false;
        }
        return $isFound;
    }

    public function isSecondaryS3Available() {
        return $this->useDualCloud && isset($this->secondaryS3);
    }

    public function checkAndDownloadPublic($modulName, $createdAt, $filekey, $filename, $isDownload) {
        $isForceAws = DateHelper::compareAwsCheckpoint($createdAt);
        if ($isForceAws) {
            $url = $this->base_get_aws_url($filekey, $filename, $isDownload);
        } else if ($this->isSecondaryS3Available() && $this->isFileExist($modulName, $filekey, true, false)) {
            $url = $this->basePublicUrlUsingSecondaryS3($filekey, $filename, $isDownload);
        } else {
            $this->isFileExist($modulName, $filekey);
            $url = $this->base_get_public_url($filekey, $filename, $isDownload);
        }

        return $url;
    }

    public function checkAndDownloadPrivate($modulName, $createdAt, $filekey, $filename, $isDownload) {
        $isForceAws = DateHelper::compareAwsCheckpoint($createdAt);
        if ($isForceAws) {
            $url = $this->base_get_aws_url($filekey, $filename, $isDownload, false);
        } else if ($this->isSecondaryS3Available() && $this->isFileExist($modulName, $filekey, false, false)) {
            $url = $this->basePrivateUrlUsingSecondaryS3($filekey, $filename, $isDownload);
        } else {
            $this->isFileExist($modulName, $filekey, false);
            $url = $this->base_get_private_url($filekey, $filename, $isDownload);
        }

        return $url;
    }

    public function checkAndDownloadPublicWithLongExpDuration($modulName, $createdAt, $filekey, $filename, $isDownload) {
        $url = null;
        $isForceAws = DateHelper::compareAwsCheckpoint($createdAt);
        if ($isForceAws && $this->isFileExist($modulName, $filekey, true, false)) {
            $url = $this->base_get_aws_url($filekey, $filename, $isDownload, true, self::LONG_EXPIRED_DURATION);
        } else if ($this->isSecondaryS3Available() && $this->isFileExist($modulName, $filekey, true, false)) {
            $url = $this->basePublicUrlUsingSecondaryS3($filekey, $filename, $isDownload, self::LONG_EXPIRED_DURATION);
        } else if ($this->isFileExist($modulName, $filekey)) {
            $url = $this->base_get_public_url($filekey, $filename, $isDownload, self::LONG_EXPIRED_DURATION);
        }

        return $url;
    }

    public function getUploadPresignedUrl($key, $isPublic) {
        if ($this->isSecondaryS3Available()) {
            $s3 = $this->secondaryS3;
            $bucket = $isPublic ? getenv("MINIO2_BUCKET_PUBLIC") : getenv("MINIO2_BUCKET_PRIVATE");
        } else {
            $s3 = $this->s3;
            $bucket = $isPublic ? getenv("MINIO_BUCKET_PUBLIC") : getenv("MINIO_BUCKET_PRIVATE");
        }

        $option = [
            'Bucket' => $bucket,
            'Key'    => $this->preprocessKey($key)
        ];

        $command = $s3->getCommand('PutObject', $option);
        $presignedRequest = $s3->createPresignedRequest($command, "+5 minutes")->withMethod('PUT');
        return (string) $presignedRequest->getUri();
    }
}
