<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;

class ConfigMerge extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'config:merge
                            {file? : Config file to merge (themes, timebank-default, timebank_cc)}
                            {--all : Merge all config files}
                            {--dry-run : Show what would change without applying}
                            {--force : Skip confirmation prompts}
                            {--restore : Restore config from backup}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Merge new configuration keys from .example files into active configs';

    /**
     * Config files that can be merged
     *
     * @var array
     */
    protected $mergeableConfigs = [
        'themes' => 'config/themes.php',
        'timebank-default' => 'config/timebank-default.php',
        'timebank_cc' => 'config/timebank_cc.php',
    ];

    /**
     * Execute the console command.
     */
    public function handle()
    {
        // Handle restore option
        if ($this->option('restore')) {
            return $this->restoreFromBackup();
        }

        // Determine which files to merge
        $filesToMerge = $this->getFilesToMerge();

        if (empty($filesToMerge)) {
            $this->error('No valid config files specified.');
            return 1;
        }

        $anyChanges = false;
        $results = [];

        foreach ($filesToMerge as $name => $path) {
            $result = $this->mergeConfigFile($name, $path);
            $results[$name] = $result;

            if ($result['hasChanges']) {
                $anyChanges = true;
            }
        }

        // Summary
        if (!$this->option('dry-run')) {
            $this->newLine();
            $this->line('═══════════════════════════════════════════════════════');
            $this->info('Config Merge Summary');
            $this->line('═══════════════════════════════════════════════════════');

            foreach ($results as $name => $result) {
                if ($result['hasChanges'] && $result['applied']) {
                    $this->info("✓ {$name}: {$result['newKeyCount']} new keys merged");
                } elseif ($result['hasChanges'] && !$result['applied']) {
                    $this->warn("⊘ {$name}: {$result['newKeyCount']} new keys available (not applied)");
                } else {
                    $this->comment("  {$name}: Up to date");
                }
            }
        }

        return $anyChanges ? 0 : 0;
    }

    /**
     * Get the list of files to merge
     */
    protected function getFilesToMerge(): array
    {
        if ($this->option('all')) {
            return $this->mergeableConfigs;
        }

        $file = $this->argument('file');
        if (!$file) {
            $this->error('Please specify a config file or use --all');
            return [];
        }

        if (!isset($this->mergeableConfigs[$file])) {
            $this->error("Unknown config file: {$file}");
            $this->line('Available files: ' . implode(', ', array_keys($this->mergeableConfigs)));
            return [];
        }

        return [$file => $this->mergeableConfigs[$file]];
    }

    /**
     * Merge a single config file
     */
    protected function mergeConfigFile(string $name, string $path): array
    {
        $examplePath = $path . '.example';

        // Check if files exist
        if (!File::exists($examplePath)) {
            $this->warn("⊘ {$name}: Example file not found ({$examplePath})");
            return ['hasChanges' => false, 'applied' => false, 'newKeyCount' => 0];
        }

        if (!File::exists($path)) {
            $this->warn("⊘ {$name}: Active config not found ({$path}) - run deployment first");
            return ['hasChanges' => false, 'applied' => false, 'newKeyCount' => 0];
        }

        // Load configs
        try {
            $currentConfig = include $path;
            $exampleConfig = include $examplePath;
        } catch (\Throwable $e) {
            $this->error("✗ {$name}: Failed to load config - {$e->getMessage()}");
            return ['hasChanges' => false, 'applied' => false, 'newKeyCount' => 0];
        }

        if (!is_array($currentConfig) || !is_array($exampleConfig)) {
            $this->error("✗ {$name}: Invalid config format");
            return ['hasChanges' => false, 'applied' => false, 'newKeyCount' => 0];
        }

        // Find new keys
        $newKeys = $this->findNewKeys($currentConfig, $exampleConfig);

        if (empty($newKeys)) {
            $this->comment("  {$name}: No new keys found");
            return ['hasChanges' => false, 'applied' => false, 'newKeyCount' => 0];
        }

        // Display changes
        $this->newLine();
        $this->line("─────────────────────────────────────────────────────");
        $this->info("Config: {$name}");
        $this->line("─────────────────────────────────────────────────────");
        $this->warn("Found " . count($newKeys) . " new configuration key(s):");
        $this->newLine();

        foreach ($newKeys as $keyPath => $value) {
            $this->line("  <fg=green>+</> {$keyPath}");
            $this->line("    <fg=gray>" . $this->formatValue($value) . "</>");
        }

        // Dry run - stop here
        if ($this->option('dry-run')) {
            return ['hasChanges' => true, 'applied' => false, 'newKeyCount' => count($newKeys)];
        }

        // Confirm merge
        if (!$this->option('force')) {
            if (!$this->confirm("Merge these keys into {$name}?", false)) {
                $this->comment("Skipped {$name}");
                return ['hasChanges' => true, 'applied' => false, 'newKeyCount' => count($newKeys)];
            }
        }

        // Create backup
        $backupPath = $this->createBackup($path);
        if (!$backupPath) {
            $this->error("✗ {$name}: Failed to create backup");
            return ['hasChanges' => true, 'applied' => false, 'newKeyCount' => count($newKeys)];
        }

        $this->line("  Backup created: {$backupPath}");

        // Perform merge
        $mergedConfig = $this->deepMergeNewKeys($currentConfig, $exampleConfig);

        // Write merged config
        if (!$this->writeConfig($path, $mergedConfig)) {
            $this->error("✗ {$name}: Failed to write merged config");
            // Restore from backup
            File::copy($backupPath, $path);
            return ['hasChanges' => true, 'applied' => false, 'newKeyCount' => count($newKeys)];
        }

        // Validate merged config can be loaded
        try {
            $testLoad = include $path;
            if (!is_array($testLoad)) {
                throw new \Exception('Config does not return an array');
            }
        } catch (\Throwable $e) {
            $this->error("✗ {$name}: Merged config is invalid - {$e->getMessage()}");
            // Restore from backup
            File::copy($backupPath, $path);
            return ['hasChanges' => true, 'applied' => false, 'newKeyCount' => count($newKeys)];
        }

        // Log the merge
        Log::channel('single')->info('Config merged', [
            'file' => $name,
            'path' => $path,
            'new_keys' => array_keys($newKeys),
            'new_key_count' => count($newKeys),
            'backup' => $backupPath,
        ]);

        $this->info("✓ {$name}: Successfully merged " . count($newKeys) . " new keys");

        return ['hasChanges' => true, 'applied' => true, 'newKeyCount' => count($newKeys)];
    }

    /**
     * Find new keys in example config that don't exist in current config
     */
    protected function findNewKeys(array $current, array $example, string $prefix = ''): array
    {
        $newKeys = [];

        foreach ($example as $key => $value) {
            $keyPath = $prefix ? "{$prefix}.{$key}" : $key;

            if (!array_key_exists($key, $current)) {
                // This is a new key
                $newKeys[$keyPath] = $value;
            } elseif (is_array($value) && is_array($current[$key])) {
                // Both are arrays - recurse to find nested new keys
                $nestedKeys = $this->findNewKeys($current[$key], $value, $keyPath);
                $newKeys = array_merge($newKeys, $nestedKeys);
            }
            // Otherwise key exists and is not both arrays - skip (preserve current value)
        }

        return $newKeys;
    }

    /**
     * Perform deep merge of new keys only
     */
    protected function deepMergeNewKeys(array $current, array $example): array
    {
        $result = $current;

        foreach ($example as $key => $value) {
            if (!array_key_exists($key, $current)) {
                // New key - add it
                $result[$key] = $value;
            } elseif (is_array($value) && is_array($current[$key])) {
                // Both are arrays - recurse
                $result[$key] = $this->deepMergeNewKeys($current[$key], $value);
            }
            // Otherwise preserve current value
        }

        return $result;
    }

    /**
     * Create a backup of the config file
     */
    protected function createBackup(string $path): ?string
    {
        $backupDir = storage_path('config-backups');

        if (!File::isDirectory($backupDir)) {
            File::makeDirectory($backupDir, 0755, true);
        }

        $filename = basename($path);
        $timestamp = date('Y-m-d_His');
        $backupPath = "{$backupDir}/{$filename}.backup.{$timestamp}";

        if (File::copy($path, $backupPath)) {
            // Cleanup old backups (keep last 5)
            $this->cleanupOldBackups($backupDir, $filename);
            return $backupPath;
        }

        return null;
    }

    /**
     * Cleanup old backup files
     */
    protected function cleanupOldBackups(string $backupDir, string $filename): void
    {
        $pattern = "{$backupDir}/{$filename}.backup.*";
        $backups = glob($pattern);

        if (count($backups) > 5) {
            // Sort by modification time (oldest first)
            usort($backups, function ($a, $b) {
                return filemtime($a) - filemtime($b);
            });

            // Delete oldest backups, keeping last 5
            $toDelete = array_slice($backups, 0, count($backups) - 5);
            foreach ($toDelete as $backup) {
                File::delete($backup);
            }
        }
    }

    /**
     * Write config array to file
     */
    protected function writeConfig(string $path, array $config): bool
    {
        $content = "<?php\n\n";
        $content .= "use Illuminate\\Validation\\Rule;\n\n";
        $content .= "return " . $this->varExportPretty($config, 0) . ";\n";

        return File::put($path, $content) !== false;
    }

    /**
     * Pretty print PHP array
     */
    protected function varExportPretty($var, int $indent = 0): string
    {
        $indentStr = str_repeat(' ', $indent * 4);
        $nextIndentStr = str_repeat(' ', ($indent + 1) * 4);

        if (!is_array($var)) {
            if (is_string($var)) {
                return "'" . addcslashes($var, "'\\") . "'";
            }
            if (is_bool($var)) {
                return $var ? 'true' : 'false';
            }
            if (is_null($var)) {
                return 'null';
            }
            return var_export($var, true);
        }

        if (empty($var)) {
            return '[]';
        }

        $output = "[\n";
        foreach ($var as $key => $value) {
            $output .= $nextIndentStr;

            // Format key
            if (is_int($key)) {
                $output .= $key;
            } else {
                $output .= "'" . addcslashes($key, "'\\") . "'";
            }

            $output .= ' => ';

            // Format value
            if (is_array($value)) {
                $output .= $this->varExportPretty($value, $indent + 1);
            } elseif (is_string($value)) {
                $output .= "'" . addcslashes($value, "'\\") . "'";
            } elseif (is_bool($value)) {
                $output .= $value ? 'true' : 'false';
            } elseif (is_null($value)) {
                $output .= 'null';
            } elseif ($value instanceof \Closure) {
                // Handle closures - just export as string representation
                $output .= var_export($value, true);
            } else {
                $output .= var_export($value, true);
            }

            $output .= ",\n";
        }
        $output .= $indentStr . ']';

        return $output;
    }

    /**
     * Format a value for display
     */
    protected function formatValue($value): string
    {
        if (is_array($value)) {
            if (empty($value)) {
                return '[]';
            }
            $preview = array_slice($value, 0, 3, true);
            $formatted = [];
            foreach ($preview as $k => $v) {
                $formatted[] = is_int($k) ? $this->formatValue($v) : "{$k}: " . $this->formatValue($v);
            }
            $result = '[' . implode(', ', $formatted);
            if (count($value) > 3) {
                $result .= ', ... +' . (count($value) - 3) . ' more';
            }
            return $result . ']';
        }
        if (is_string($value)) {
            return strlen($value) > 50 ? '"' . substr($value, 0, 47) . '..."' : "\"{$value}\"";
        }
        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }
        if (is_null($value)) {
            return 'null';
        }
        return (string) $value;
    }

    /**
     * Restore config from backup
     */
    protected function restoreFromBackup(): int
    {
        $backupDir = storage_path('config-backups');

        if (!File::isDirectory($backupDir)) {
            $this->error('No backups found');
            return 1;
        }

        // List available backups
        $backups = File::glob("{$backupDir}/*.backup.*");

        if (empty($backups)) {
            $this->error('No backups found');
            return 1;
        }

        // Group by config file
        $grouped = [];
        foreach ($backups as $backup) {
            $basename = basename($backup);
            preg_match('/^(.+?)\.backup\.(.+)$/', $basename, $matches);
            if ($matches) {
                $configFile = $matches[1];
                $timestamp = $matches[2];
                $grouped[$configFile][] = [
                    'path' => $backup,
                    'timestamp' => $timestamp,
                    'time' => filemtime($backup),
                ];
            }
        }

        // Sort by time (newest first)
        foreach ($grouped as &$backupList) {
            usort($backupList, function ($a, $b) {
                return $b['time'] - $a['time'];
            });
        }

        // Display available backups
        $this->info('Available backups:');
        $this->newLine();

        $options = [];
        $i = 1;
        foreach ($grouped as $configFile => $backupList) {
            $this->line("<fg=yellow>{$configFile}</>");
            foreach ($backupList as $backup) {
                $date = date('Y-m-d H:i:s', $backup['time']);
                $this->line("  {$i}. {$date}");
                $options[$i] = [
                    'file' => $configFile,
                    'path' => $backup['path'],
                ];
                $i++;
            }
            $this->newLine();
        }

        // Get user choice
        $choice = $this->ask('Enter backup number to restore (or 0 to cancel)');

        if ($choice === '0' || !isset($options[(int)$choice])) {
            $this->comment('Restore cancelled');
            return 0;
        }

        $selected = $options[(int)$choice];
        $configPath = "config/{$selected['file']}";

        if (!File::exists($configPath)) {
            $this->error("Config file not found: {$configPath}");
            return 1;
        }

        if (!$this->confirm("Restore {$selected['file']} from backup?", false)) {
            $this->comment('Restore cancelled');
            return 0;
        }

        // Create backup of current config before restoring
        $currentBackupPath = $this->createBackup($configPath);
        if ($currentBackupPath) {
            $this->line("Current config backed up to: {$currentBackupPath}");
        }

        // Restore
        if (File::copy($selected['path'], $configPath)) {
            $this->info("✓ Successfully restored {$selected['file']}");
            return 0;
        } else {
            $this->error("✗ Failed to restore config");
            return 1;
        }
    }
}
