<?php

namespace classes;
require_once WD . '/modules/backups/backups_c.php';

use backups\backups_c;
use Exception;
use interfaces\minio_backups_i;
use traits\minio_t;

class backup_store implements minio_backups_i
{
    use minio_t;

    /**
     * The prefix for the backup files in the S3 bucket
     * @var string
     */
    const backup_s3_prefix = 'backups/';
    /**
     * The prefix for the metadata files in the S3 bucket
     * @var string
     */
    const metadata_s3_prefix = 'metadata/'; // The prefix for the backup files in the S3 bucket
    /**
     * The path to store the backups locally
     * @var string
     */
    const local_backup_path = '/tmp/backups/'; // The prefix for the metadata files in the S3 bucket
    /**
     * The configuration for the backup store
     * @var backups_c
     */
    public backups_c $config;

    /**
     * @throws Exception If the bucket does not exist, is not accessible, or is not configured.
     */
    public function __construct()
    {
        $this->config = new backups_c();
        self::setBucket('backups'); // Change the bucket to backups
        self::validateBucket();
    }

    /**
     * @inheritDoc
     */
    public function validateBucket(): void
    {
        // Check if the bucket exists
        if (!self::getS3Client()->doesBucketExist(self::getBucket())) {
            // throw an exception
            throw new Exception('The bucket does not exist in the S3 service! Missing: ' . self::getBucket());
        }

    }

    /**
     * @inheritDoc
     */
    public function backup_exists(string $backup_uuid): bool
    {
        // Check if the file exists
        $objects = self::getS3Client()->listObjects([
            'Bucket' => self::getBucket(),
            'Prefix' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip'
        ]);
        return count($objects['Contents'] ?? []) > 0;
    }

    /**
     * @inheritDoc
     */
    public function getBackupDownloadUrl(string $backup_uuid): string
    {
        // Get the presigned URL for the backup
        return self::getPresignedUrl('backup_' . $backup_uuid . '.zip');
    }

    /**
     * @inheritDoc
     */
    public function download(string $backup_uuid): string
    {
        // Download the backup
        $path = self::local_backup_path . 'backup_' . $backup_uuid . '.zip';
        $result = self::getS3Client()->getObject([
            'Bucket' => self::getBucket(),
            'Key' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip',
            'SaveAs' => $path
        ]);
        return $path;
    }

    /**
     * @inheritDoc
     * @throws Exception
     */
    public function createBackup($backup_name = null, $backup_description = null): string
    {
        // Check if the backup name is set
        if (!$backup_name) {
            $backup_name = 'Backup ' . date('Y-m-d H:i:s');
        }
        // Check if the backup description is set
        if (!$backup_description) {
            $backup_description = 'Backup created on ' . date('Y-m-d H:i:s');
        }
        // Generate the backup file
        $backup_uuid = self::generateBackupFile();
        // Upload the backup file
        self::getS3Client()->putObject([
            'Bucket' => self::getBucket(),
            'Key' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip',
            'SourceFile' => self::local_backup_path . 'backup_' . $backup_uuid . '.zip'
        ]);
        // Update the metadata
        self::createBackupMetadata($backup_uuid, $backup_name, $backup_description);
        // Clean up the backup file
        self::cleanUpBackupFile($backup_uuid);
        // Return the backup UUID
        return $backup_uuid;
    }

    /**
     * @inheritDoc
     */
    public function generateBackupFile(): string
    {
        // Generate a UUID for the backup
        $backup_uuid = uniqid();

        // Prepare the backup directories
        if (!is_dir(self::local_backup_path)) {
            mkdir(self::local_backup_path);
            if (!is_dir(self::local_backup_path)) {
                throw new Exception('Failed to create local backup path: ' . self::local_backup_path);
            }
        }
        if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
            mkdir(self::local_backup_path . 'backup_' . $backup_uuid);
            if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
                throw new Exception('Failed to create local backup directory: ' . self::local_backup_path . 'backup_' . $backup_uuid);
            }
        }

        // Backup the database
        $database_path = self::backupDatabase($backup_uuid);

        // Web server files
        $webserver_files_path = self::backupServerFiles($backup_uuid);

        // Environment variables
        $env_path = self::backupEnvironmentVariables($backup_uuid);

        // Create the backup path
        $backup_path = self::local_backup_path . 'backup_' . $backup_uuid . '.zip';

        // Create the backups directory
        if (!is_dir(self::local_backup_path)) {
            mkdir(self::local_backup_path);
        }

        // Create the backup directory
        if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
            mkdir(self::local_backup_path . 'backup_' . $backup_uuid);
        }

        // Add the database backup to the backup directory
        copy($database_path, self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql');

        // Add the web server files to the backup directory
        copy($webserver_files_path, self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip');

        // Add the environment variables to the backup directory
        copy($env_path, self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json');

        // Zip the backup directory
        exec('zip -r ' . $backup_path . ' ' . self::local_backup_path . 'backup_' . $backup_uuid);

        // Return the backup UUID
        return $backup_uuid;
    }

    /**
     * @inheritDoc
     * @throws Exception
     */
    public function backupDatabase(string $backup_uuid): string
    {
        global /** @var db $db */
        $db;
        // Backup the database
        $path = self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql';
        $result = $db->backupDatabase($path);
        if (!$result) {
            throw new Exception('Failed to backup the database!');
        }
        return $path;
    }

    /**
     * @inheritDoc
     */
    public function backupServerFiles(string $backup_uuid): string
    {
        // Backup the server files from the /var/www/html directory
        $path = self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip';
        exec('zip -r ' . $path . ' /var/www/html');
        return $path;
    }

    /**
     * @inheritDoc
     */
    public function backupEnvironmentVariables(string $backup_uuid): string
    {
        // Backup the config variables
        $path = self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json';
        file_put_contents($path, json_encode($_ENV));
        return $path;
    }

    /**
     * @inheritDoc
     */
    public function createBackupMetadata(string $backup_uuid, string $backup_name, string $backup_description): void
    {
        // Create the metadata file
        $metadata = [
            'backup_name' => $backup_name,
            'backup_description' => $backup_description,
            'backup_date' => date('Y-m-d H:i:s'),
            'backup_size' => filesize(self::local_backup_path . 'backup_' . $backup_uuid . '.zip'),
            'hash' => md5_file(self::local_backup_path . 'backup_' . $backup_uuid . '.zip')
        ];
        file_put_contents(
            self::local_backup_path . 'backup_' . $backup_uuid . '.json',
            json_encode($metadata)
        );
        // Upload the metadata file
        self::getS3Client()->putObject([
            'Bucket' => self::getBucket(),
            'Key' => self::metadata_s3_prefix . 'backup_' . $backup_uuid . '.json',
            'SourceFile' => self::local_backup_path . 'backup_' . $backup_uuid . '.json'
        ]);
    }

    /**
     * @inheritDoc
     */
    public function cleanupBackupFile(string $backup_uuid): void
    {
        /** Clean up the backup files, and remove the temporary directories and files */

        // Remove the backup directory
        unlink(self::local_backup_path . 'backup_' . $backup_uuid . '.zip');

        // Remove the metadata file
        unlink(self::local_backup_path . 'backup_' . $backup_uuid . '.json');

        // Remove the database backup
        unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql');

        // Remove the web server files backup
        unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip');

        // Remove the environment variables backup
        unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json');

        // Remove the backup directory
        rmdir(self::local_backup_path . 'backup_' . $backup_uuid);
    }

    /**
     * @inheritDoc
     */
    public function listBackups(): array
    {
        // List the backups
        $backups = [];
        $objects = self::getS3Client()->listObjects([
            'Bucket' => self::getBucket(),
            'Prefix' => self::metadata_s3_prefix
        ]);
        // Check if there are any backups
        if (!isset($objects['Contents'])) {
            return [];
        }
        foreach ( $objects['Contents'] as $object ) {
            $backup_uuid = str_replace(
                self::metadata_s3_prefix . 'backup_',
                '',
                str_replace('.json', '', $object['Key'])
            );
            $backups[] = $this->getBackupMetadata($backup_uuid);
        }
        return $backups;
    }

    /**
     * @inheritDoc
     */
    public function getBackupMetadata(string $backup_uuid): array
    {
        // Get the metadata file
        $metadata = self::getS3Client()->getObject([
            'Bucket' => self::getBucket(),
            'Key' => self::metadata_s3_prefix . 'backup_' . $backup_uuid . '.json'
        ]);
        return json_decode($metadata['Body'], true);
    }
}