<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use ZipArchive;
use Exception;

class UpgradeController extends Controller
{
    private $upgradeDir;
    private $backupDir;
    private $tempDir;
    
    public function __construct()
    {
        $this->upgradeDir = storage_path('app/upgrades');
        $this->backupDir = storage_path('app/backups');
        $this->tempDir = storage_path('app/temp/upgrade');
        
        // Ensure directories exist
        foreach ([$this->upgradeDir, $this->backupDir, $this->tempDir] as $dir) {
            if (!File::exists($dir)) {
                File::makeDirectory($dir, 0755, true);
            }
        }
    }
    
    public function index()
    {
        if (!File::exists(storage_path('app/installed.txt'))) {
            return redirect('/')->with('error', 'Application is not installed yet!');
        }
        
        // Check if upgrade is in progress
        $upgradeInProgress = File::exists(storage_path('app/upgrade_in_progress.txt'));
        
        // Get current version
        $currentVersion = $this->getCurrentVersion();
        
        // Get available backups
        $backups = $this->getAvailableBackups();
        
        return view('upgrade.index', compact('currentVersion', 'upgradeInProgress', 'backups'));
    }
    
    public function upload(Request $request)
    {
        $request->validate([
            'upgrade_package' => 'required|file|mimes:zip|max:512000', // 500MB max
        ]);
        
        try {
            $file = $request->file('upgrade_package');
            $filename = 'upgrade_' . time() . '.zip';
            $path = $file->storeAs('upgrades', $filename);
            
            // Verify ZIP file
            $fullPath = storage_path('app/' . $path);
            if (!$this->verifyZipFile($fullPath)) {
                File::delete($fullPath);
                return back()->with('error', 'Invalid upgrade package. Please check the file and try again.');
            }
            
            session(['upgrade_package' => $filename]);
            
            return redirect()->route('upgrade.verify')->with('success', 'Upgrade package uploaded successfully!');
            
        } catch (Exception $e) {
            return back()->with('error', 'Failed to upload upgrade package: ' . $e->getMessage());
        }
    }
    
    public function verify()
    {
        $packageName = session('upgrade_package');
        if (!$packageName) {
            return redirect()->route('upgrade.index')->with('error', 'No upgrade package found.');
        }
        
        $packagePath = $this->upgradeDir . '/' . $packageName;
        
        if (!File::exists($packagePath)) {
            return redirect()->route('upgrade.index')->with('error', 'Upgrade package not found.');
        }
        
        // Extract and analyze package
        try {
            $packageInfo = $this->analyzePackage($packagePath);
            
            return view('upgrade.verify', compact('packageInfo'));
            
        } catch (Exception $e) {
            return back()->with('error', 'Failed to analyze package: ' . $e->getMessage());
        }
    }
    
    public function backup(Request $request)
    {
        try {
            File::put(storage_path('app/upgrade_in_progress.txt'), 'Upgrade started at ' . date('Y-m-d H:i:s'));
            
            $backupName = 'backup_' . date('Y-m-d_His');
            
            // Create backup
            $backupResult = $this->createBackup($backupName);
            
            if (!$backupResult['success']) {
                File::delete(storage_path('app/upgrade_in_progress.txt'));
                return back()->with('error', 'Backup failed: ' . $backupResult['message']);
            }
            
            session(['backup_name' => $backupName]);
            
            return redirect()->route('upgrade.process')->with('success', 'Backup created successfully!');
            
        } catch (Exception $e) {
            File::delete(storage_path('app/upgrade_in_progress.txt'));
            return back()->with('error', 'Backup failed: ' . $e->getMessage());
        }
    }
    
    public function process()
    {
        return view('upgrade.process');
    }
    
    public function doUpgrade(Request $request)
    {
        $packageName = session('upgrade_package');
        $backupName = session('backup_name');
        
        if (!$packageName || !$backupName) {
            return response()->json([
                'success' => false,
                'message' => 'Missing upgrade package or backup information.'
            ], 400);
        }
        
        try {
            $packagePath = $this->upgradeDir . '/' . $packageName;
            
            // Step 1: Extract package
            $this->updateProgress(10, 'Extracting upgrade package...');
            $this->extractPackage($packagePath);
            
            // Step 2: Verify compatibility
            $this->updateProgress(20, 'Verifying compatibility...');
            if (!$this->checkCompatibility()) {
                throw new Exception('Compatibility check failed. This upgrade is not compatible with your current version.');
            }
            
            // Step 3: Copy new files
            $this->updateProgress(35, 'Updating application files...');
            $this->updateFiles();
            
            // Step 4: Install npm dependencies (if package.json changed)
            $this->updateProgress(50, 'Installing dependencies...');
            $this->installDependencies();
            
            // Step 5: Compile assets
            $this->updateProgress(60, 'Compiling assets (CSS, JS)...');
            $this->compileAssets();
            
            // Step 6: Run migrations
            $this->updateProgress(75, 'Running database migrations...');
            Artisan::call('migrate', ['--force' => true]);
            
            // Step 7: Clear caches
            $this->updateProgress(90, 'Clearing caches...');
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
            Artisan::call('view:clear');
            Artisan::call('route:clear');
            
            // Step 8: Update version
            $this->updateProgress(95, 'Finalizing upgrade...');
            $this->updateVersion();
            
            // Cleanup
            $this->updateProgress(100, 'Upgrade completed successfully!');
            $this->cleanup();
            
            File::delete(storage_path('app/upgrade_in_progress.txt'));
            
            return response()->json([
                'success' => true,
                'message' => 'Upgrade completed successfully!'
            ]);
            
        } catch (Exception $e) {
            \Log::error('Upgrade failed: ' . $e->getMessage());
            \Log::error('Stack trace: ' . $e->getTraceAsString());
            
            return response()->json([
                'success' => false,
                'message' => 'Upgrade failed: ' . $e->getMessage(),
                'can_rollback' => true
            ], 500);
        }
    }
    
    public function rollback()
    {
        $backupName = session('backup_name');
        
        if (!$backupName) {
            return redirect()->route('upgrade.index')->with('error', 'No backup found for rollback.');
        }
        
        return view('upgrade.rollback', compact('backupName'));
    }
    
    public function doRollback(Request $request)
    {
        $backupName = session('backup_name');
        
        if (!$backupName) {
            return response()->json([
                'success' => false,
                'message' => 'No backup found for rollback.'
            ], 400);
        }
        
        try {
            $this->updateProgress(10, 'Starting rollback...');
            
            $backupPath = $this->backupDir . '/' . $backupName;
            
            if (!File::exists($backupPath)) {
                throw new Exception('Backup not found.');
            }
            
            // Restore files
            $this->updateProgress(30, 'Restoring files...');
            $this->restoreBackup($backupPath);
            
            // Restore database
            $this->updateProgress(50, 'Restoring database...');
            $this->restoreDatabase($backupPath);
            
            // Reinstall dependencies
            $this->updateProgress(70, 'Restoring dependencies...');
            $this->installDependencies();
            
            // Recompile assets
            $this->updateProgress(80, 'Recompiling assets...');
            $this->compileAssets();
            
            // Clear caches
            $this->updateProgress(90, 'Clearing caches...');
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
            Artisan::call('view:clear');
            
            $this->updateProgress(100, 'Rollback completed successfully!');
            
            File::delete(storage_path('app/upgrade_in_progress.txt'));
            
            return response()->json([
                'success' => true,
                'message' => 'Rollback completed successfully!'
            ]);
            
        } catch (Exception $e) {
            \Log::error('Rollback failed: ' . $e->getMessage());
            
            return response()->json([
                'success' => false,
                'message' => 'Rollback failed: ' . $e->getMessage()
            ], 500);
        }
    }
    
    public function complete()
    {
        File::delete(storage_path('app/upgrade_in_progress.txt'));
        session()->forget(['upgrade_package', 'backup_name']);
        
        $newVersion = $this->getCurrentVersion();
        
        return view('upgrade.complete', compact('newVersion'));
    }
    
    // Helper Methods
    
    private function verifyZipFile($path)
    {
        $zip = new ZipArchive;
        $res = $zip->open($path);
        
        if ($res !== TRUE) {
            return false;
        }
        
        // Check for required files/structure
        $requiredFiles = ['version.json', 'upgrade.json'];
        
        foreach ($requiredFiles as $file) {
            if ($zip->locateName($file) === false) {
                $zip->close();
                return false;
            }
        }
        
        $zip->close();
        return true;
    }
    
    private function analyzePackage($packagePath)
    {
        $zip = new ZipArchive;
        $zip->open($packagePath);
        
        // Read version info
        $versionJson = $zip->getFromName('version.json');
        $versionInfo = json_decode($versionJson, true);
        
        // Read upgrade info
        $upgradeJson = $zip->getFromName('upgrade.json');
        $upgradeInfo = json_decode($upgradeJson, true);
        
        $zip->close();
        
        return [
            'current_version' => $this->getCurrentVersion(),
            'new_version' => $versionInfo['version'] ?? 'Unknown',
            'release_date' => $versionInfo['release_date'] ?? 'Unknown',
            'changes' => $upgradeInfo['changes'] ?? [],
            'files_count' => $upgradeInfo['files_count'] ?? 0,
            'requires_database_migration' => $upgradeInfo['requires_migration'] ?? false,
            'minimum_version' => $upgradeInfo['minimum_version'] ?? null,
        ];
    }
    
    private function createBackup($backupName)
    {
        try {
            $backupPath = $this->backupDir . '/' . $backupName;
            File::makeDirectory($backupPath, 0755, true);
            
            // Backup critical files
            $filesToBackup = [
                '.env',
                'composer.json',
                'composer.lock',
                'package.json',
                'package-lock.json',
                'app',
                'config',
                'database',
                'routes',
                'resources',
            ];
            
            foreach ($filesToBackup as $item) {
                $source = base_path($item);
                $destination = $backupPath . '/files/' . $item;
                
                if (File::exists($source)) {
                    if (File::isDirectory($source)) {
                        File::copyDirectory($source, $destination);
                    } else {
                        File::ensureDirectoryExists(dirname($destination));
                        File::copy($source, $destination);
                    }
                }
            }
            
            // Backup database
            $this->backupDatabase($backupPath);
            
            // Create backup info file
            File::put($backupPath . '/backup_info.json', json_encode([
                'created_at' => date('Y-m-d H:i:s'),
                'version' => $this->getCurrentVersion(),
                'php_version' => PHP_VERSION,
                'laravel_version' => app()->version(),
                'node_version' => $this->getNodeVersion(),
                'npm_version' => $this->getNpmVersion(),
            ]));
            
            return ['success' => true];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }
    
    private function backupDatabase($backupPath)
    {
        try {
            $dbPath = $backupPath . '/database';
            File::makeDirectory($dbPath, 0755, true);
            
            $database = env('DB_DATABASE');
            $username = env('DB_USERNAME');
            $password = env('DB_PASSWORD');
            $host = env('DB_HOST');
            
            $filename = $dbPath . '/database_backup.sql';
            
            // Use mysqldump if available
            $command = sprintf(
                'mysqldump -h %s -u %s %s %s > %s',
                escapeshellarg($host),
                escapeshellarg($username),
                $password ? '-p' . escapeshellarg($password) : '',
                escapeshellarg($database),
                escapeshellarg($filename)
            );
            
            exec($command, $output, $returnVar);
            
            // If mysqldump fails, use PHP method
            if ($returnVar !== 0 || !File::exists($filename)) {
                $this->backupDatabasePHP($filename);
            }
            
            return true;
            
        } catch (Exception $e) {
            \Log::error('Database backup failed: ' . $e->getMessage());
            return false;
        }
    }
    
    private function backupDatabasePHP($filename)
    {
        $tables = DB::select('SHOW TABLES');
        $database = env('DB_DATABASE');
        
        $dump = "-- Database Backup\n";
        $dump .= "-- Created: " . date('Y-m-d H:i:s') . "\n\n";
        
        foreach ($tables as $table) {
            $tableName = array_values((array)$table)[0];
            
            $dump .= "-- Table: {$tableName}\n";
            $dump .= "DROP TABLE IF EXISTS `{$tableName}`;\n";
            
            $createTable = DB::select("SHOW CREATE TABLE `{$tableName}`");
            $dump .= $createTable[0]->{'Create Table'} . ";\n\n";
            
            $rows = DB::table($tableName)->get();
            
            foreach ($rows as $row) {
                $values = array_map(function($value) {
                    return is_null($value) ? 'NULL' : "'" . addslashes($value) . "'";
                }, (array)$row);
                
                $dump .= "INSERT INTO `{$tableName}` VALUES (" . implode(', ', $values) . ");\n";
            }
            
            $dump .= "\n";
        }
        
        File::put($filename, $dump);
    }
    
    private function extractPackage($packagePath)
    {
        $zip = new ZipArchive;
        
        if ($zip->open($packagePath) !== TRUE) {
            throw new Exception('Failed to open upgrade package.');
        }
        
        // Clean temp directory
        if (File::exists($this->tempDir)) {
            File::deleteDirectory($this->tempDir);
        }
        File::makeDirectory($this->tempDir, 0755, true);
        
        $zip->extractTo($this->tempDir);
        $zip->close();
        
        return true;
    }
    
    private function checkCompatibility()
    {
        $upgradeInfoPath = $this->tempDir . '/upgrade.json';
        
        if (!File::exists($upgradeInfoPath)) {
            return true; // No compatibility requirements specified
        }
        
        $upgradeInfo = json_decode(File::get($upgradeInfoPath), true);
        
        if (isset($upgradeInfo['minimum_version'])) {
            $currentVersion = $this->getCurrentVersion();
            
            if (version_compare($currentVersion, $upgradeInfo['minimum_version'], '<')) {
                return false;
            }
        }
        
        return true;
    }
    
    private function updateFiles()
    {
        $sourcePath = $this->tempDir . '/files';
        
        if (!File::exists($sourcePath)) {
            throw new Exception('Upgrade files not found in package.');
        }
        
        // Files to skip (user-specific)
        $skipFiles = [
            '.env',
            'storage/app',
            'storage/logs',
            'public/uploads',
            'node_modules',
        ];
        
        // Copy files
        $files = File::allFiles($sourcePath);
        
        foreach ($files as $file) {
            $relativePath = str_replace($sourcePath . '/', '', $file->getPathname());
            
            // Check if file should be skipped
            $skip = false;
            foreach ($skipFiles as $skipPattern) {
                if (str_starts_with($relativePath, $skipPattern)) {
                    $skip = true;
                    break;
                }
            }
            
            if ($skip) {
                continue;
            }
            
            $destination = base_path($relativePath);
            File::ensureDirectoryExists(dirname($destination));
            File::copy($file->getPathname(), $destination);
        }
        
        return true;
    }
    
    private function installDependencies()
    {
        try {
            // Check if npm is available
            if (!$this->isNpmAvailable()) {
                \Log::warning('npm is not available. Skipping dependency installation.');
                return false;
            }
            
            // Check if package.json exists
            if (!File::exists(base_path('package.json'))) {
                \Log::info('package.json not found. Skipping npm install.');
                return false;
            }
            
            // Run npm install
            $command = 'cd ' . escapeshellarg(base_path()) . ' && npm install 2>&1';
            
            exec($command, $output, $returnVar);
            
            if ($returnVar !== 0) {
                $errorMessage = implode("\n", $output);
                \Log::error('npm install failed: ' . $errorMessage);
                throw new Exception('Failed to install dependencies. Check logs for details.');
            }
            
            \Log::info('npm install completed successfully.');
            return true;
            
        } catch (Exception $e) {
            \Log::error('Error during dependency installation: ' . $e->getMessage());
            throw $e;
        }
    }
    
    private function compileAssets()
    {
        try {
            // Check if npm is available
            if (!$this->isNpmAvailable()) {
                \Log::warning('npm is not available. Skipping asset compilation.');
                return false;
            }
            
            // Check if package.json exists
            if (!File::exists(base_path('package.json'))) {
                \Log::info('package.json not found. Skipping asset compilation.');
                return false;
            }
            
            // Check if build script exists in package.json
            $packageJson = json_decode(File::get(base_path('package.json')), true);
            if (!isset($packageJson['scripts']['build'])) {
                \Log::warning('Build script not found in package.json. Skipping asset compilation.');
                return false;
            }
            
            // Run npm run build
            $command = 'cd ' . escapeshellarg(base_path()) . ' && npm run build 2>&1';
            
            exec($command, $output, $returnVar);
            
            if ($returnVar !== 0) {
                $errorMessage = implode("\n", $output);
                \Log::error('npm run build failed: ' . $errorMessage);
                throw new Exception('Failed to compile assets. Check logs for details.');
            }
            
            \Log::info('Asset compilation completed successfully.');
            return true;
            
        } catch (Exception $e) {
            \Log::error('Error during asset compilation: ' . $e->getMessage());
            throw $e;
        }
    }
    
    private function isNpmAvailable()
    {
        exec('npm --version 2>&1', $output, $returnVar);
        return $returnVar === 0;
    }
    
    private function getNodeVersion()
    {
        exec('node --version 2>&1', $output, $returnVar);
        return $returnVar === 0 ? trim(implode('', $output)) : 'Not installed';
    }
    
    private function getNpmVersion()
    {
        exec('npm --version 2>&1', $output, $returnVar);
        return $returnVar === 0 ? trim(implode('', $output)) : 'Not installed';
    }
    
    private function updateVersion()
    {
        $versionPath = $this->tempDir . '/version.json';
        
        if (File::exists($versionPath)) {
            File::copy($versionPath, base_path('version.json'));
        }
    }
    
    private function cleanup()
    {
        // Clean temp directory
        if (File::exists($this->tempDir)) {
            File::deleteDirectory($this->tempDir);
        }
        
        // Delete upgrade package
        $packageName = session('upgrade_package');
        if ($packageName) {
            $packagePath = $this->upgradeDir . '/' . $packageName;
            if (File::exists($packagePath)) {
                File::delete($packagePath);
            }
        }
    }
    
    private function restoreBackup($backupPath)
    {
        $filesPath = $backupPath . '/files';
        
        if (!File::exists($filesPath)) {
            throw new Exception('Backup files not found.');
        }
        
        $files = File::allFiles($filesPath);
        
        foreach ($files as $file) {
            $relativePath = str_replace($filesPath . '/', '', $file->getPathname());
            $destination = base_path($relativePath);
            
            File::ensureDirectoryExists(dirname($destination));
            File::copy($file->getPathname(), $destination);
        }
    }
    
    private function restoreDatabase($backupPath)
    {
        $sqlFile = $backupPath . '/database/database_backup.sql';
        
        if (!File::exists($sqlFile)) {
            throw new Exception('Database backup file not found.');
        }
        
        $database = env('DB_DATABASE');
        $username = env('DB_USERNAME');
        $password = env('DB_PASSWORD');
        $host = env('DB_HOST');
        
        $command = sprintf(
            'mysql -h %s -u %s %s %s < %s',
            escapeshellarg($host),
            escapeshellarg($username),
            $password ? '-p' . escapeshellarg($password) : '',
            escapeshellarg($database),
            escapeshellarg($sqlFile)
        );
        
        exec($command, $output, $returnVar);
        
        if ($returnVar !== 0) {
            // Try PHP method
            $sql = File::get($sqlFile);
            DB::unprepared($sql);
        }
    }
    
    private function getCurrentVersion()
    {
        $versionFile = base_path('version.json');
        
        if (File::exists($versionFile)) {
            $versionData = json_decode(File::get($versionFile), true);
            return $versionData['version'] ?? '1.0.0';
        }
        
        return '1.0.0';
    }
    
    private function getAvailableBackups()
    {
        if (!File::exists($this->backupDir)) {
            return [];
        }
        
        $backups = [];
        $directories = File::directories($this->backupDir);
        
        foreach ($directories as $dir) {
            $infoFile = $dir . '/backup_info.json';
            
            if (File::exists($infoFile)) {
                $info = json_decode(File::get($infoFile), true);
                $backups[] = [
                    'name' => basename($dir),
                    'created_at' => $info['created_at'] ?? 'Unknown',
                    'version' => $info['version'] ?? 'Unknown',
                    'size' => $this->getDirectorySize($dir),
                ];
            }
        }
        
        return $backups;
    }
    
    private function getDirectorySize($path)
    {
        $size = 0;
        
        foreach (File::allFiles($path) as $file) {
            $size += $file->getSize();
        }
        
        return $this->formatBytes($size);
    }
    
    private function formatBytes($bytes)
    {
        if ($bytes >= 1073741824) {
            return number_format($bytes / 1073741824, 2) . ' GB';
        } elseif ($bytes >= 1048576) {
            return number_format($bytes / 1048576, 2) . ' MB';
        } elseif ($bytes >= 1024) {
            return number_format($bytes / 1024, 2) . ' KB';
        } else {
            return $bytes . ' bytes';
        }
    }
    
    private function updateProgress($percent, $message)
    {
        File::put(storage_path('app/upgrade_progress.json'), json_encode([
            'percent' => $percent,
            'message' => $message,
            'timestamp' => time()
        ]));
    }
    
    public function getProgress()
    {
        $progressFile = storage_path('app/upgrade_progress.json');
        
        if (File::exists($progressFile)) {
            return response()->json(json_decode(File::get($progressFile), true));
        }
        
        return response()->json([
            'percent' => 0,
            'message' => 'Preparing...',
            'timestamp' => time()
        ]);
    }
}