<?php

namespace Lcmigrator\LcMigrator\Migrator;

use Exception;
use Lcmigrator\LcMigrator\Constants\Constants;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Lcmigrator\LcMigrator\Database\DatabaseConnection;
use Lcmigrator\LcMigrator\Migrator\Database\DatabaseDump;
use ZipArchive;
use Lcmigrator\LcMigrator\Logger\Logger;
use Lcmigrator\LcMigrator\Migrator\BackupInProgress\BackupInProgress;
use Lcmigrator\LcMigrator\Migrator\SiteInfo\SiteInfo;
use Lcmigrator\LcMigrator\OptionManager\OptionManager;
use Lcmigrator\LcMigrator\Event\EventUpdater;
use Lcmigrator\LcMigrator\StageInfo\StageInfo;

class Migrator
{
    private $backup_dir;
    private $batch_size = 500;
    private $max_execution_time = 0;
    private $memory_limit = '20G';

    private ?Logger $logger = null;
    private string $backupId;
    private string $backupOptionName = 'lc_migrator_backup_options';
    private OptionManager $backupOptions;
    private BackupInProgress $backupInProgress;

    public function __construct($backupId, $backupInProgress)
    {
        $this->backup_dir = wp_upload_dir()['basedir'] . '/wp-site-backups/';
        $this->init();
        $this->backupId = $backupId;
        $this->backupOptions = OptionManager::getInstance($backupId);
        $this->backupInProgress = $backupInProgress;
        $logger = Logger::getInstance();
        $logger->info("Backup ID: " . $backupId);
        $logger->info("Backup In Progress: ");
    }

    private function init()
    {
        // Create backup directory
        $this->create_backup_directory();
        // Increase limits
        $this->set_system_limits();
    }

    private function set_system_limits()
    {
        set_time_limit($this->max_execution_time);
        ini_set('memory_limit', $this->memory_limit);
        ini_set('max_execution_time', $this->max_execution_time);
    }

    private function create_backup_directory()
    {
        if (!file_exists($this->backup_dir)) {
            wp_mkdir_p($this->backup_dir);
            if (!is_writable($this->backup_dir)) {
                error_log("Not Writable, Setting Permissions");
                chmod($this->backup_dir, 0755);
            }
        }

        // Protect backup directory
        $htaccess_content = "Order deny,allow\nDeny from all";
        file_put_contents($this->backup_dir . '.htaccess', $htaccess_content);
    }


    public function export_complete_site($backupId)
    {
        $logger = Logger::getInstance();
        $logger->info("Exporting complete site" . $backupId);
        // Pendo events removed

        // Initialize progress tracking
        $total_steps = Constants::total_steps;
        $current_step = 1;

        // Backup configuration
        $include_uploads = true;
        $compress = true;

        try {
            // Check for force closed before each step
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'initialization',
                    'stage' => $current_step
                );
            }

            // Initialize backup status
            $this->backupInProgress->setMetadata('message', 'Verifying credentials and connecting to your source website...');
            $this->backupInProgress->setMetadata('stage', 1);
            $this->backupInProgress->setMetadata('status', 'running');
            $this->backupInProgress->setMetadata('action', 'Checking Connection');

            // Step 1: Initialize backup
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'backup initialization',
                    'stage' => $current_step
                );
            }



            $timestamp = date('Y-m-d_H-i-s');
            $backup_filename = "lc_migrator_{$timestamp}.zip";
            $backup_path = $this->backup_dir . $backup_filename;

            $this->logger = Logger::getInstance();
            $this->logger->info("Starting backup process with ID: " . $backupId);
            $this->logger->info("Backup directory: " . $this->backup_dir);
            $this->logger->info("Is backup directory writable: " . (is_writable($this->backup_dir) ? 'yes' : 'no'));

            // Step 2: Create ZIP archive
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'Creating ZIP archive',
                    'stage' => $current_step
                );
            }



            $zip = new ZipArchive();
            $result = $zip->open($backup_path, ZipArchive::CREATE | ZipArchive::OVERWRITE);

            if ($result !== true) {
                $this->logger->error("ZipArchive open failed. Error code: " . $result);
                $this->logger->error("Backup path: " . $backup_path);
                throw new Exception("Failed to create ZIP archive");
            }

            // Step 3: Export database
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'Database Export',
                    'stage' => $current_step
                );
            }

            $this->backupInProgress->setMetadata('message', 'Migrating your WordPress database...');
            $this->backupInProgress->setMetadata('stage', 2);
            $this->backupInProgress->setMetadata('status', 'starting');
            $this->backupInProgress->setMetadata('action', 'Exporting Database');


            $this->logger->info("Starting database export");
            $databaseConnection = new DatabaseConnection(
                array(
                    'host' => DB_HOST,
                    'user' => DB_USER,
                    'password' => DB_PASSWORD,
                    'database' => DB_NAME,
                    'charset' => defined('DB_CHARSET') ? DB_CHARSET : 'utf8mb4'
                ),
                $this->logger
            );

            $dump = new DatabaseDump([
                'dumpfile' => $this->backup_dir . 'database_backup.sql',
                'dbcharset' => defined('DB_CHARSET') ? DB_CHARSET : 'utf8mb4',
                'compression' => true
            ], $databaseConnection, $this->logger);

            $dump->execute();
            $zip->addFile($dump->dumpfile, 'database_backup.sql');
            $this->logger->info("Database export complete");

            // Step 4: Export site info
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'site info export',
                    'stage' => $current_step
                );
            }

            $this->backupInProgress->setMetadata('message', 'Exporting site info...');
            $this->backupInProgress->setMetadata('stage', 2);
            $this->backupInProgress->setMetadata('status', 'running');
            $this->backupInProgress->setMetadata('action', 'Exporting Site Info');

            $this->logger->info("Exporting site info");
            $site_info = new SiteInfo();
            $site_info->export_data($this->backup_dir . 'site_info.json');
            $zip->addFile($this->backup_dir . 'site_info.json', 'site_info.json');
            $this->logger->info("Site info export complete");

            // Step 5: Add WordPress configuration
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'WordPress configuration',
                    'stage' => $current_step
                );
            }

            $this->backupInProgress->setMetadata('message', 'Adding WordPress configuration files...');
            $this->backupInProgress->setMetadata('stage', 2);
            $this->backupInProgress->setMetadata('status', 'running');
            $this->backupInProgress->setMetadata('action', 'Adding WordPress Configuration');

            $this->logger->info("Adding WordPress configuration files");
            $zip->addFile(ABSPATH . 'wp-config.php', 'wp-config.php');

            // Step 6: Add content directory
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'content directory backup',
                    'stage' => 6
                );
            }


            $this->backupInProgress->setMetadata('message', 'Adding wp-content directory...');
            $this->backupInProgress->setMetadata('stage', 2);
            $this->backupInProgress->setMetadata('status', 'running');
            $this->backupInProgress->setMetadata('action', 'Adding Content Directory');

            if ($include_uploads) {
                $this->logger->info("Adding wp-content directory");
                $this->add_directory_to_zip($zip, WP_CONTENT_DIR, 'wp-content');
                $this->logger->info("wp-content directory added");
            }

            // Step 7: Finalize ZIP archive
            if ($this->backupInProgress->getMetadata('force_closed') === true) {
                return array(
                    'success' => false,
                    'status' => 'backup_stopped',
                    'backupId' => $backupId,
                    'closed_at_stage' => 'ZIP finalization',
                    'stage' => 7
                );
            }

            $this->backupInProgress->setMetadata('message', 'Creating a secure backup of your current website...');
            $this->backupInProgress->setMetadata('stage', 3);
            $this->backupInProgress->setMetadata('status', 'running');
            $this->backupInProgress->setMetadata('action', 'Finalizing ZIP Archive');

            if ($zip->numFiles > 0) {
                $this->logger->info("Finalizing ZIP archive with " . $zip->numFiles . " files");
                // Close the ZIP with timeouts reset
                set_time_limit(0); // Remove time limit for closing operation
                ini_set('max_execution_time', 0);
                
                // Memory management before heavy operation
                gc_collect_cycles();
                $zip->close();
                $this->logger->info("Backup complete: " . $backup_path);
                $this->backupInProgress->setMetadata('status', 'completed');
                $this->backupInProgress->setMetadata('move_to_next', true);
            } else {
                $this->logger->error("No files added to ZIP archive");
                throw new Exception("No files were added to the backup archive");
            }

            return array(
                'backup_path' => $backup_path,
            );
        } catch (Exception $e) {
            // Update status to failed
            $this->backupInProgress->setMetadata('status', 'failed');
            $this->backupInProgress->setMetadata('error', $e->getMessage());

            // Pendo events removed

            $this->logger->error("Backup failed: " . $e->getMessage());
            return array(
                'success' => false,
                'backupId' => $backupId,
                'error' => $e->getMessage(),
                'stage' => $current_step
            );
        }
    }


    private function add_directory_to_zip(&$zip, $source, $relative_path = '')
    {
        // Normalize source path
        $source = realpath($source);

        // Configuration for memory-efficient processing
        $batch_size = 980; // Adjust based on file sizes in your WordPress sites

        // File types that are already compressed - don't compress these again
        $compressed_extensions = [
            'jpg',
            'jpeg',
            'png',
            'gif',
            'zip',
            'gz',
            'mp3',
            'mp4',
            'avi',
            'mov',
            'pdf',
            'docx',
            'xlsx',
            'pptx',
            'webp'
        ];

        // Text files that benefit from maximum compression
        $text_extensions = [
            'php',
            'css',
            'js',
            'html',
            'htm',
            'txt',
            'md',
            'xml',
            'json',
            'svg',
            'log',
            'sql',
            'yml',
            'yaml',
            'ini',
            'conf'
        ];

        try {
            // PHASE 1: Add directory structures first (memory-efficient)
            $dir_paths = [];
            $dir_iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            );

            foreach ($dir_iterator as $file) {
                if ($file->isDir()) {
                    $dir_path = $file->getRealPath();
                    $relative_dir_path = str_replace($source . '/', '', $dir_path);
                    $dir_paths[] = $relative_dir_path;
                }
            }

            // Free memory
            unset($dir_iterator);
            gc_collect_cycles();

            // Add directories to zip
            foreach ($dir_paths as $dir) {
                $zip->addEmptyDir($relative_path . '/' . $dir);
            }

            // Free memory
            unset($dir_paths);
            gc_collect_cycles();

            // PHASE 2: Collect file paths only to minimize memory usage
            $file_paths = [];
            $file_iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::LEAVES_ONLY // Only process files, not directories
            );

            foreach ($file_iterator as $file) {
                if ($file->isFile()) {
                    $file_paths[] = $file->getRealPath();
                }
            }

            // Free memory
            unset($file_iterator);
            gc_collect_cycles();

            // PHASE 3: Process files in batches with optimized compression
            $total_files = count($file_paths);
            $files_processed = 0;

            for ($i = 0; $i < $total_files; $i += $batch_size) {
                $batch = array_slice($file_paths, $i, $batch_size);

                foreach ($batch as $file_path) {
                    // Skip non-existent files
                    if (!file_exists($file_path)) {
                        continue;
                    }

                    $relative_file_path = str_replace($source . '/', '', $file_path);

                    // Add file to ZIP
                    $zip->addFile($file_path, $relative_path . '/' . $relative_file_path);

                    // Optimize compression based on file type
                    $ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));

                    if (in_array($ext, $compressed_extensions)) {
                        // Store already compressed files without recompression
                        $zip->setCompressionName($relative_path . '/' . $relative_file_path, ZipArchive::CM_STORE);
                    } else if (in_array($ext, $text_extensions)) {
                        // Use maximum compression for text files
                        $zip->setCompressionName($relative_path . '/' . $relative_file_path, ZipArchive::CM_STORE);
                    } else {
                        // Use moderate compression for other files
                        $zip->setCompressionName($relative_path . '/' . $relative_file_path, ZipArchive::CM_STORE);
                    }

                    $files_processed++;
                    $percentage = round(($files_processed / $total_files) * 100);
                    $this->backupInProgress->setMetadata('message', "Adding wp-content directory($percentage%)...");
                }

                // Free memory after processing batch
                unset($batch);
                gc_collect_cycles();

                // Prevent timeout
                if (function_exists('set_time_limit')) {
                    set_time_limit(30);
                }
            }

            // Final cleanup
            unset($file_paths);
            gc_collect_cycles();

            return true;
        } catch (Exception $e) {
            // Log error if logger is available
            if (property_exists($this, 'logger') && $this->logger) {
                $this->logger->error("ZIP error: " . $e->getMessage());
            } else {
                error_log("ZIP error: " . $e->getMessage());
            }
            return false;
        }
    }


    public function perform_cleanup()
    {
        // Optional: Remove backup file after download
        $logger = Logger::getInstance();
        $logger->info("Performing cleanup");
        $logger->info("Backup directory: " . $this->backup_dir);
        lc_migrator_recursive_directory_delete($this->backup_dir);
    }

    /**
     * Summary of handle_backup_download
     * Function not in use but keeping it here for future reference
     * @param mixed $backup_path
     * @param mixed $backup_filename
     * @return never
     */
    private function handle_backup_download($backup_path, $backup_filename)
    {
        // Validate file existence
        if (!file_exists($backup_path)) {
            wp_die('Backup file not found.');
        }

        // Validate file size
        $filesize = filesize($backup_path);
        if ($filesize <= 0) {
            wp_die('Backup file is empty.');
        }

        // Security: Prevent any output before download
        if (ob_get_level()) {
            ob_end_clean();
        }

        // Disable error reporting
        @error_reporting(0);

        // Download Headers
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $backup_filename . '"');
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . $filesize);
        header('Cache-Control: no-cache, no-store, must-revalidate');
        header('Pragma: no-cache');
        header('Expires: 0');

        // Flush system output buffers
        flush();

        // Stream file download
        $download_handle = fopen($backup_path, 'rb');

        // Check file handle
        if ($download_handle === false) {
            wp_die('Unable to read backup file.');
        }

        // Stream file contents
        while (!feof($download_handle)) {
            echo esc_html(fread($download_handle, 4096));
            flush();
        }

        // Close file handle
        fclose($download_handle);

        // Terminate script
        exit;
    }
}
