<?php

namespace Lcmigrator\LcMigrator\Cli;

use Exception;
use WP_CLI;
use Lcmigrator\LcMigrator\Logger\Logger;

/**
 * Restore a WordPress site from a .lc-migrator backup file
 *
 * ## EXAMPLES
 *
 *     # Restore from GCS URL file
 *     wp lcm restore --snapshot-id=XXX --location-id=YYY --wp-id=ZZZ
 *
 *     # Restore from direct URL
 *     wp lcm restore --url="https://storage.googleapis.com/bucket/backup.lc-migrator"
 *
 * @package Lcmigrator\LcMigrator\Cli
 */
class RestoreCli
{
    /**
     * Logger instance
     *
     * @var Logger
     */
    private $logger;

    /**
     * Content type for .lc-migrator files
     *
     * @var string
     */
    private $contentType = 'application/octet-stream';

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->logger = Logger::getInstance();
    }

    /**
     * Restore a WordPress site from a .lc-migrator backup
     *
     * ## OPTIONS
     *
     * [--snapshot-id=<snapshot-id>]
     * : Snapshot ID for constructing the GCS URL file path
     *
     * [--location-id=<location-id>]
     * : Location ID for constructing the GCS URL file path
     *
     * [--wp-id=<wp-id>]
     * : WordPress ID for constructing the GCS URL file path
     *
     * [--gcs-url=<gcs-url>]
     * : Direct GCS public URL to the .lc-migrator file (alternative to snapshot/location/wp-id)
     *
     * ## EXAMPLES
     *
     *     wp lcm restore --snapshot-id=XXX --location-id=YYY --wp-id=ZZZ
     *     wp lcm restore --gcs-url="https://storage.googleapis.com/bucket/backup.lc-migrator"
     *
     * Note: Confirmation is automatically skipped (--yes is default) for CLI usage.
     *
     * @param array $args       Positional arguments
     * @param array $assoc_args Associative arguments
     * @return void
     */
    public function __invoke($args, $assoc_args)
    {
        $startTime = microtime(true);

        try {
            WP_CLI::log('');
            WP_CLI::log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            WP_CLI::log('  LC Migrator - Site Restore');
            WP_CLI::log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            WP_CLI::log('');

            // 1. Get GCS URL
            WP_CLI::log('Initializing restore process...');
            $gcsUrl = $this->_getGcsUrl($assoc_args);

            if (empty($gcsUrl)) {
                WP_CLI::error('No GCS URL provided. Use --gcs-url or --snapshot-id/--location-id/--wp-id');
            }

            // Validate URL
            $this->_validateGcsUrl($gcsUrl);

            // Extract restore ID from parameters
            $restoreId = $this->_getRestoreId($assoc_args);
            WP_CLI::log("Restore ID: {$restoreId}");
            WP_CLI::log("GCS URL: " . substr($gcsUrl, 0, 80) . "...");

            // 2. Setup directories
            WP_CLI::log('Setting up restore directories...');
            $dirs = $this->_setupDirectories();
            if (!$dirs['success']) {
                WP_CLI::error('Failed to setup directories: ' . $dirs['error']);
            }

            // 3. Verify system requirements
            WP_CLI::log('Verifying system requirements...');
            $requirements = $this->_verifySystemRequirements();
            if (!$requirements['success']) {
                WP_CLI::error('System requirements check failed: ' . $requirements['error']);
            }

            // 4. Download the backup file
            WP_CLI::log('');
            WP_CLI::log('Downloading backup from GCS...');
            WP_CLI::log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

            $downloadResult = $this->_downloadFromGcs($gcsUrl, $restoreId);
            if (!$downloadResult['success']) {
                WP_CLI::error('Download failed: ' . $downloadResult['error']);
            }

            $archivePath = $downloadResult['archive_path'];
            $storagePath = $downloadResult['storage_path'];
            $archiveSize = filesize($archivePath);
            $archiveSizeFormatted = $this->_formatFileSize($archiveSize);

            WP_CLI::log('');
            WP_CLI::success("✓ Backup downloaded: {$archivePath} ({$archiveSizeFormatted})");

            // 5. Run import pipeline
            WP_CLI::log('');
            WP_CLI::log('Starting site restoration...');
            WP_CLI::log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

            $importResult = $this->_runImportPipeline($archivePath, $storagePath, $restoreId, $assoc_args);

            if (!$importResult['success']) {
                WP_CLI::error('Restore failed: ' . $importResult['error']);
            }

            // 6. Post-restore tasks
            WP_CLI::log('');
            WP_CLI::log('Running post-restore tasks...');
            $this->_postRestoreTasks();

            // 7. Summary
            $totalTime = microtime(true) - $startTime;
            WP_CLI::log('');
            WP_CLI::log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            WP_CLI::success('Site restored successfully!');
            WP_CLI::log("Total time: " . $this->_formatTime($totalTime));
            WP_CLI::log("Backup size: {$archiveSizeFormatted}");
            WP_CLI::log("Site URL: " . get_site_url());

        } catch (Exception $e) {
            WP_CLI::error('Restore failed: ' . $e->getMessage());
        }
    }

    /**
     * Get GCS URL from arguments or file
     *
     * @param array $assoc_args Associative arguments
     * @return string|null GCS URL
     */
    private function _getGcsUrl($assoc_args)
    {
        // Option 1: Direct URL provided via --gcs-url
        if (!empty($assoc_args['gcs-url'])) {
            WP_CLI::log("Using direct GCS URL from --gcs-url parameter");
            return trim($assoc_args['gcs-url']);
        }

        // Option 2: Construct from snapshot-id, location-id, wp-id
        if (!empty($assoc_args['snapshot-id']) && !empty($assoc_args['location-id']) && !empty($assoc_args['wp-id'])) {
            $snapshotId = $assoc_args['snapshot-id'];
            $locationId = $assoc_args['location-id'];
            $wpId = $assoc_args['wp-id'];

            // Construct file path: /uploads/lc-migrator/download/{snapshotId}-{locationId}-{wpId}.txt
            $uploadDir = wp_upload_dir();
            $urlFilePath = $uploadDir['basedir'] . '/lc-migrator/download/' . $snapshotId . '-' . $locationId . '-' . $wpId . '.txt';

            WP_CLI::log("Looking for GCS URL file: {$urlFilePath}");

            if (file_exists($urlFilePath)) {
                $gcsUrl = trim(file_get_contents($urlFilePath));
                if (!empty($gcsUrl)) {
                    WP_CLI::log("Using GCS URL from file: {$urlFilePath}");
                    return $gcsUrl;
                }
            } else {
                WP_CLI::warning("GCS URL file not found: {$urlFilePath}");
            }
        }

        return null;
    }

    /**
     * Get restore ID from arguments
     *
     * @param array $assoc_args Associative arguments
     * @return string Restore ID
     */
    private function _getRestoreId($assoc_args)
    {
        if (!empty($assoc_args['snapshot-id']) && !empty($assoc_args['location-id']) && !empty($assoc_args['wp-id'])) {
            return $assoc_args['snapshot-id'] . '-' . $assoc_args['location-id'] . '-' . $assoc_args['wp-id'];
        }

        // Generate a unique restore ID
        return 'restore-' . date('Ymd-His') . '-' . substr(md5(uniqid()), 0, 8);
    }

    /**
     * Validate GCS URL
     *
     * @param string $url The GCS URL to validate
     * @return void
     * @throws Exception If URL is invalid
     */
    private function _validateGcsUrl($url)
    {
        if (empty($url)) {
            throw new Exception('GCS URL cannot be empty');
        }

        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            throw new Exception('Invalid URL format');
        }

        // Check if it's a GCS URL
        if (strpos($url, 'storage.googleapis.com') === false && strpos($url, 'storage.cloud.google.com') === false) {
            WP_CLI::warning('URL does not appear to be a Google Cloud Storage URL');
        }

        // Check file extension
        $urlPath = parse_url($url, PHP_URL_PATH);
        if (strpos($urlPath, '.lc-migrator') === false) {
            WP_CLI::warning('URL does not point to a .lc-migrator file');
        }

        $this->logger->info("Validated GCS URL: " . substr($url, 0, 100) . "...");
    }

    /**
     * Setup restore directories
     *
     * @return array Success status and directory paths or error
     */
    private function _setupDirectories()
    {
        try {
            // Check if storage path constant is defined
            if (!defined('LC_MIGRATOR_STORAGE_PATH')) {
                return [
                    'success' => false,
                    'error' => 'LC_MIGRATOR_STORAGE_PATH constant is not defined'
                ];
            }

            $storagePath = LC_MIGRATOR_STORAGE_PATH;

            // Create storage directory if it doesn't exist
            if (!file_exists($storagePath)) {
                if (!wp_mkdir_p($storagePath)) {
                    return [
                        'success' => false,
                        'error' => "Failed to create storage directory: {$storagePath}"
                    ];
                }
            }

            // Check if writable
            if (!is_writable($storagePath)) {
                return [
                    'success' => false,
                    'error' => "Storage directory is not writable: {$storagePath}"
                ];
            }

            return [
                'success' => true,
                'storage_path' => $storagePath
            ];

        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Verify system requirements
     *
     * @return array Success status or error message
     */
    private function _verifySystemRequirements()
    {
        $errors = [];

        // Check PHP version
        if (version_compare(PHP_VERSION, '7.4', '<')) {
            $errors[] = 'PHP 7.4 or higher is required';
        }

        // Check required extensions
        $requiredExtensions = ['curl', 'json', 'mbstring'];
        foreach ($requiredExtensions as $ext) {
            if (!extension_loaded($ext)) {
                $errors[] = "Required PHP extension not loaded: {$ext}";
            }
        }

        // Check available disk space (need at least 1GB for restore operations)
        $storagePath = defined('LC_MIGRATOR_STORAGE_PATH') ? LC_MIGRATOR_STORAGE_PATH : sys_get_temp_dir();
        $freeSpace = @disk_free_space($storagePath);
        if ($freeSpace !== false && $freeSpace < 1073741824) { // 1GB
            WP_CLI::warning('Less than 1GB of free disk space available');
        }

        // Check memory limit
        $memoryLimit = wp_convert_hr_to_bytes(ini_get('memory_limit'));
        if ($memoryLimit > 0 && $memoryLimit < 268435456) { // 256MB
            WP_CLI::warning('Memory limit is less than 256MB, this may cause issues with large imports');
        }

        if (!empty($errors)) {
            return [
                'success' => false,
                'error' => implode('; ', $errors)
            ];
        }

        return ['success' => true];
    }

    /**
     * Download backup file from GCS
     *
     * @param string $gcsUrl    The GCS URL
     * @param string $restoreId The restore ID
     * @return array Result with archive_path and storage_path
     */
    private function _downloadFromGcs($gcsUrl, $restoreId)
    {
        try {
            // Setup environment
            $this->_setupRestoreEnvironment();

            // Create storage folder
            $storageId = lc_migrator_storage_folder();
            $storagePath = LC_MIGRATOR_STORAGE_PATH . DIRECTORY_SEPARATOR . $storageId;

            if (!file_exists($storagePath)) {
                if (!wp_mkdir_p($storagePath)) {
                    throw new Exception("Failed to create storage folder: {$storagePath}");
                }
            }

            WP_CLI::log("Storage folder: {$storagePath}");

            // Generate archive filename from URL or create new one
            $urlPath = parse_url($gcsUrl, PHP_URL_PATH);
            $archiveFilename = basename($urlPath);

            // If filename doesn't have .lc-migrator extension, generate one
            if (strpos($archiveFilename, '.lc-migrator') === false) {
                $archiveFilename = lc_migrator_archive_file();
            }

            $archivePath = $storagePath . DIRECTORY_SEPARATOR . $archiveFilename;

            WP_CLI::log("Downloading to: {$archivePath}");

            // Get file size first
            $fileSize = $this->_getRemoteFileSize($gcsUrl);
            if ($fileSize > 0) {
                WP_CLI::log("Remote file size: " . $this->_formatFileSize($fileSize));
            }

            // Download with progress
            $result = $this->_downloadWithProgress($gcsUrl, $archivePath, $fileSize);

            if (!$result['success']) {
                throw new Exception($result['error']);
            }

            // Verify downloaded file
            if (!file_exists($archivePath)) {
                throw new Exception("Downloaded file not found: {$archivePath}");
            }

            $downloadedSize = filesize($archivePath);
            if ($downloadedSize < 1000) {
                throw new Exception("Downloaded file is too small ({$downloadedSize} bytes), may be corrupted");
            }

            return [
                'success' => true,
                'archive_path' => $archivePath,
                'storage_path' => $storagePath,
                'storage_id' => $storageId,
                'archive_filename' => $archiveFilename
            ];

        } catch (Exception $e) {
            $this->logger->error("Download failed: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Get remote file size using HEAD request
     *
     * @param string $url The URL
     * @return int File size in bytes, or 0 if unknown
     */
    private function _getRemoteFileSize($url)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        curl_exec($ch);
        $fileSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
        curl_close($ch);

        return (int) $fileSize;
    }

    /**
     * Download file with progress display
     *
     * @param string $url         Source URL
     * @param string $destination Destination path
     * @param int    $totalSize   Expected file size
     * @return array Result
     */
    private function _downloadWithProgress($url, $destination, $totalSize)
    {
        try {
            $fp = fopen($destination, 'wb');
            if (!$fp) {
                throw new Exception("Cannot open file for writing: {$destination}");
            }

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_FILE, $fp);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, 0); // No timeout for large files
            curl_setopt($ch, CURLOPT_BUFFERSIZE, 1048576); // 1MB buffer

            // Progress callback
            $startTime = microtime(true);
            $lastUpdate = 0;
            curl_setopt($ch, CURLOPT_NOPROGRESS, false);
            curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function ($resource, $downloadSize, $downloaded, $uploadSize, $uploaded) use ($totalSize, $startTime, &$lastUpdate) {
                $now = microtime(true);
                
                // Update every 0.5 seconds
                if ($now - $lastUpdate < 0.5) {
                    return 0;
                }
                $lastUpdate = $now;

                $size = $downloadSize > 0 ? $downloadSize : $totalSize;
                if ($size > 0 && $downloaded > 0) {
                    $percent = min(100, round(($downloaded / $size) * 100));
                    $elapsed = $now - $startTime;
                    $speed = $elapsed > 0 ? $downloaded / $elapsed : 0;
                    $speedMB = round($speed / 1048576, 2);
                    $remaining = $speed > 0 ? ($size - $downloaded) / $speed : 0;
                    $eta = $this->_formatTime($remaining);

                    // Use carriage return to update same line
                    fwrite(STDERR, "\rProgress: {$percent}% | Speed: {$speedMB} MB/s | ETA: {$eta}    ");
                }
                return 0;
            });

            $result = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);

            curl_close($ch);
            fclose($fp);

            // New line after progress
            fwrite(STDERR, "\n");

            if ($result === false) {
                throw new Exception("cURL error: {$error}");
            }

            if ($httpCode !== 200) {
                throw new Exception("HTTP error: {$httpCode}");
            }

            return ['success' => true];

        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Run the import pipeline
     *
     * @param string $archivePath Full path to archive
     * @param string $storagePath Full path to storage folder
     * @param string $restoreId   Restore ID
     * @param array  $assoc_args  CLI arguments (for --yes flag)
     * @return array Result
     */
    private function _runImportPipeline($archivePath, $storagePath, $restoreId, $assoc_args = [])
    {
        try {
            // Setup environment
            $this->_setupRestoreEnvironment();

            // Initialize params for import
            $storageId = basename($storagePath);
            $archiveFilename = basename($archivePath);

            // Auto-confirm by default for CLI (add --yes to assoc_args)
            $assoc_args['yes'] = true;
            
            $params = [
                'archive'   => $archiveFilename,
                'storage'   => $storageId,
                'site_id'   => $restoreId,
                'priority'  => 10,
                'cli_mode'  => true,
                'cli_args'  => $assoc_args,  // Pass CLI args with --yes for auto-confirm
                'options'   => [],
            ];

            WP_CLI::log("Archive: {$archiveFilename}");
            WP_CLI::log("Storage ID: {$storageId}");

            // Get all import filters
            $filters = $this->_getImportFilters('lcm_import');

            if (empty($filters)) {
                throw new Exception('No import filters registered. Make sure the plugin is properly initialized.');
            }

            // Define filters to skip for CLI (we handle download ourselves)
            $skipFilters = [
                'Lcm_URL_Import_Create::execute',    // We already have the archive
                'Lcm_URL_Import_Download::execute',  // We already downloaded it
            ];

            // Execute filters in priority order
            foreach ($filters as $priority => $hooks) {
                foreach ($hooks as $hook) {
                    $functionName = $this->_getFunctionName($hook['function']);

                    // Skip download-related filters (we already have the file)
                    if (in_array($functionName, $skipFilters)) {
                        WP_CLI::log("Skipping filter: {$functionName} (already handled)");
                        continue;
                    }

                    WP_CLI::log("Executing: {$functionName} (priority: {$priority})");

                    try {
                        // Execute the filter
                        $params = call_user_func_array($hook['function'], [$params]);

                        // Handle chunked operations (completed flag)
                        $completed = isset($params['completed']) ? (bool) $params['completed'] : true;

                        while (!$completed) {
                            // Show progress for long-running operations
                            if (isset($params['processed_files_size']) && isset($params['total_files_size'])) {
                                $progress = min(100, round(($params['processed_files_size'] / $params['total_files_size']) * 100));
                                WP_CLI::log("  → Progress: {$progress}%");
                            }

                            $params = call_user_func_array($hook['function'], [$params]);
                            $completed = isset($params['completed']) ? (bool) $params['completed'] : true;
                        }

                        WP_CLI::log("  ✓ Completed");

                    } catch (\Lcm_Database_Exception $e) {
                        throw new Exception("Database error in {$functionName}: " . $e->getMessage());
                    } catch (\Lcm_Import_Exception $e) {
                        throw new Exception("Import error in {$functionName}: " . $e->getMessage());
                    }
                }
            }

            return ['success' => true];

        } catch (Exception $e) {
            $this->logger->error("Import failed: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Setup environment for restore process
     *
     * @return void
     */
    private function _setupRestoreEnvironment()
    {
        @ignore_user_abort(true);
        @set_time_limit(0);
        @ini_set('max_input_time', '-1');
        @ini_set('pcre.backtrack_limit', PHP_INT_MAX);

        if (@function_exists('mb_internal_encoding') && (@ini_get('mbstring.func_overload') & 2)) {
            @mb_internal_encoding('ISO-8859-1');
        }

        // Call plugin's setup functions if available
        if (function_exists('lcm_setup_environment')) {
            lcm_setup_environment();
        }
        if (function_exists('lcm_setup_errors')) {
            lcm_setup_errors();
        }
    }

    /**
     * Get import filters from WordPress
     *
     * @param string $tag Filter tag
     * @return array Filters sorted by priority
     */
    private function _getImportFilters($tag)
    {
        if (function_exists('lcm_get_filters')) {
            return lcm_get_filters($tag);
        }

        global $wp_filter;

        if (!isset($wp_filter[$tag])) {
            return [];
        }

        $filters = $wp_filter[$tag];

        if (is_object($filters)) {
            $filters = $filters->callbacks;
        }

        ksort($filters);

        return $filters;
    }

    /**
     * Get function name from hook callback
     *
     * @param mixed $function Hook callback
     * @return string Function name
     */
    private function _getFunctionName($function)
    {
        if (is_string($function)) {
            return $function;
        }

        if (is_array($function)) {
            $class = is_object($function[0]) ? get_class($function[0]) : $function[0];
            return $class . '::' . $function[1];
        }

        if (is_object($function) && $function instanceof \Closure) {
            return 'Closure';
        }

        return 'Unknown';
    }

    /**
     * Run post-restore tasks
     *
     * @return void
     */
    private function _postRestoreTasks()
    {
        // Flush rewrite rules
        WP_CLI::log('Flushing rewrite rules...');
        flush_rewrite_rules(true);

        // Clear object cache
        WP_CLI::log('Clearing cache...');
        wp_cache_flush();

        // Clear any transients
        if (function_exists('delete_expired_transients')) {
            delete_expired_transients(true);
        }

        WP_CLI::log('✓ Post-restore tasks completed');
    }

    /**
     * Format file size in human readable format
     *
     * @param int $bytes File size in bytes
     * @return string Formatted size
     */
    private function _formatFileSize($bytes)
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        $bytes /= pow(1024, $pow);

        return round($bytes, 2) . ' ' . $units[$pow];
    }

    /**
     * Format time in human readable format
     *
     * @param float $seconds Time in seconds
     * @return string Formatted time
     */
    private function _formatTime($seconds)
    {
        if ($seconds < 60) {
            return round($seconds) . 's';
        }

        $minutes = floor($seconds / 60);
        $secs = round($seconds % 60);

        if ($minutes < 60) {
            return sprintf('%d:%02d', $minutes, $secs);
        }

        $hours = floor($minutes / 60);
        $mins = $minutes % 60;

        return sprintf('%d:%02d:%02d', $hours, $mins, $secs);
    }
}

