<?php

namespace App\Http\Controllers\Admin\Backup;

use App\Http\Controllers\Controller;
use App\Traits\RefreshesSettingsCache;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
use Inertia\Response;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

class BackupController extends Controller
{
    use RefreshesSettingsCache;

    /**
     * Get current backup progress
     */
    public function getProgress(Request $request)
    {
        $userId = auth()->id();
        $progress = Cache::get("backup_progress_{$userId}", [
            'status' => 'idle',
            'percentage' => 0,
            'message' => '',
            'current_file' => '',
            'total_files' => 0,
            'processed_files' => 0,
        ]);

        return response()->json($progress);
    }

    /**
     * Update backup progress
     */
    protected function updateProgress(array $data)
    {
        $userId = auth()->id();
        $defaultData = [
            'status' => 'idle',
            'percentage' => 0,
            'message' => '',
            'current_file' => '',
            'total_files' => 0,
            'processed_files' => 0,
        ];

        $progress = array_merge($defaultData, $data);

        Cache::put("backup_progress_{$userId}", $progress, now()->addMinutes(30));
    }

    /**
     * Clear backup progress
     */
    protected function clearProgress()
    {
        $userId = auth()->id();
        Cache::forget("backup_progress_{$userId}");
    }

    /**
     * Display the backup management page
     */
    public function index(): Response
    {
        $backupSettings = $this->getBackupSettings();
        $backups = $this->listBackups();
        $diskUsage = $this->getBackupDiskUsage();

        return Inertia::render('admin/backup/index', [
            'settings' => $backupSettings,
            'backups' => $backups,
            'diskUsage' => $diskUsage,
        ]);
    }

    /**
     * Get backup settings from database
     */
    protected function getBackupSettings(): array
    {
        $mysqlHost = DB::table('settings')->where('key', 'backup_mysql_host')->value('value') ?? env('DB_HOST', 'localhost');
        $mysqlPort = DB::table('settings')->where('key', 'backup_mysql_port')->value('value') ?? env('DB_PORT', '3306');
        $mysqlDatabase = DB::table('settings')->where('key', 'backup_mysql_database')->value('value') ?? env('DB_DATABASE', 'forge');
        $mysqlUsername = DB::table('settings')->where('key', 'backup_mysql_username')->value('value') ?? env('DB_USERNAME', 'forge');
        $mysqlPassword = DB::table('settings')->where('key', 'backup_mysql_password')->value('value') ?? env('DB_PASSWORD', '');

        $customPaths = DB::table('settings')
            ->where('key', 'backup_custom_paths')
            ->value('value');

        $paths = $customPaths ? json_decode($customPaths, true) : [
            '/home/gamedbd/dbhomewdb',
            '/home/uniquenamed/uname',
        ];

        $backupDir = DB::table('settings')->where('key', 'backup_directory')->value('value') ?? storage_path('app/backups');

        return [
            'mysql' => [
                'host' => $mysqlHost,
                'port' => $mysqlPort,
                'database' => $mysqlDatabase,
                'username' => $mysqlUsername,
                'password' => $mysqlPassword,
            ],
            'customPaths' => $paths,
            'backupDirectory' => $backupDir,
        ];
    }

    /**
     * Update backup settings
     */
    public function updateSettings(Request $request)
    {
        $request->validate([
            'mysql_host' => 'nullable|string',
            'mysql_port' => 'nullable|string',
            'mysql_database' => 'nullable|string',
            'mysql_username' => 'nullable|string',
            'mysql_password' => 'nullable|string',
            'backup_directory' => 'nullable|string',
            'custom_paths' => 'nullable|array',
            'custom_paths.*' => 'nullable|string',
        ]);

        // Update MySQL settings
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_mysql_host'],
            ['value' => $request->input('mysql_host')]
        );
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_mysql_port'],
            ['value' => $request->input('mysql_port')]
        );
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_mysql_database'],
            ['value' => $request->input('mysql_database')]
        );
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_mysql_username'],
            ['value' => $request->input('mysql_username')]
        );
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_mysql_password'],
            ['value' => $request->input('mysql_password')]
        );

        // Update backup directory
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_directory'],
            ['value' => $request->input('backup_directory')]
        );

        // Update custom paths
        $paths = array_filter($request->input('custom_paths', []), function ($path) {
            return ! empty($path) && is_string($path);
        });
        DB::table('settings')->updateOrInsert(
            ['key' => 'backup_custom_paths'],
            ['value' => json_encode(array_values($paths))]
        );

        $this->refreshSettingsCache();

        return back()->with('success', 'Backup settings updated successfully.');
    }

    /**
     * Perform MySQL backup
     */
    public function backupMySQL(Request $request)
    {
        $settings = $this->getBackupSettings();
        $mysql = $settings['mysql'];
        $backupDir = $settings['backupDirectory'];

        // Ensure backup directory exists
        if (! is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }

        $timestamp = now()->format('Y-m-d_H-i-s');
        $filename = "mysql_backup_{$timestamp}.sql.gz";
        $backupPath = "{$backupDir}/{$filename}";

        // Initialize progress
        $this->updateProgress([
            'status' => 'running',
            'percentage' => 0,
            'message' => 'Starting MySQL backup...',
        ]);

        try {
            // Update progress
            $this->updateProgress([
                'percentage' => 20,
                'message' => 'Dumping database...',
            ]);

            // Build mysqldump command
            $command = [
                'mysqldump',
                '-h', $mysql['host'],
                '-P', $mysql['port'],
                '-u', $mysql['username'],
            ];

            // Only add password flag if password is not empty
            if ($mysql['password'] !== '') {
                $command[] = '-p'.$mysql['password'];
            }

            $command[] = '--routines';
            $command[] = '--triggers';
            $command[] = '--events';
            $command[] = '--single-transaction';
            $command[] = '--add-drop-table';
            $command[] = '--default-character-set=utf8mb4';
            $command[] = '--complete-insert';
            $command[] = '--skip-lock-tables';

            $command[] = $mysql['database'];

            $process = new Process($command);

            $process->setTimeout(3600);
            $process->run();

            if (! $process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

            // Update progress
            $this->updateProgress([
                'percentage' => 70,
                'message' => 'Compressing backup...',
            ]);

            // Compress the backup
            file_put_contents($backupPath, gzencode($process->getOutput()));

            // Update progress
            $this->updateProgress([
                'percentage' => 90,
                'message' => 'Saving backup to database...',
            ]);

            // Log backup
            DB::table('backups')->insert([
                'type' => 'mysql',
                'filename' => $filename,
                'path' => $backupPath,
                'size' => filesize($backupPath),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            // Update progress to complete
            $this->updateProgress([
                'status' => 'completed',
                'percentage' => 100,
                'message' => 'MySQL backup completed successfully!',
            ]);

            return back()->with('success', 'MySQL backup created successfully.');
        } catch (\Exception $e) {
            Log::error('MySQL backup failed: '.$e->getMessage());

            // Update progress to failed
            $this->updateProgress([
                'status' => 'failed',
                'percentage' => 0,
                'message' => 'MySQL backup failed: '.$e->getMessage(),
            ]);

            return back()->with('error', 'MySQL backup failed: '.$e->getMessage());
        }
    }

    /**
     * Perform directory backup
     */
    public function backupDirectory(Request $request)
    {
        $request->validate([
            'path' => 'required|string',
        ]);

        $path = $request->input('path');
        $settings = $this->getBackupSettings();
        $backupDir = $settings['backupDirectory'];

        // Validate path exists
        if (! is_dir($path)) {
            return back()->with('error', "Directory does not exist: {$path}");
        }

        // Ensure backup directory exists
        if (! is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }

        $timestamp = now()->format('Y-m-d_H-i-s');
        $pathName = str_replace(['/', '\\'], '_', trim($path, '/'));
        $filename = "dir_backup_{$pathName}_{$timestamp}.tar.gz";
        $backupPath = "{$backupDir}/{$filename}";

        // Initialize progress
        $this->updateProgress([
            'status' => 'running',
            'percentage' => 0,
            'message' => 'Starting directory backup...',
            'current_file' => basename($path),
        ]);

        try {
            // Update progress
            $this->updateProgress([
                'percentage' => 30,
                'message' => 'Creating tar archive...',
                'current_file' => basename($path),
            ]);

            $process = new Process([
                'sudo',
                'tar',
                '-czf',
                $backupPath,
                '-C',
                dirname($path),
                basename($path),
            ]);

            $process->setTimeout(3600);
            $process->run();

            if (! $process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

            // Update progress
            $this->updateProgress([
                'percentage' => 80,
                'message' => 'Saving backup to database...',
            ]);

            // Log backup
            DB::table('backups')->insert([
                'type' => 'directory',
                'source_path' => $path,
                'filename' => $filename,
                'path' => $backupPath,
                'size' => filesize($backupPath),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            // Update progress to complete
            $this->updateProgress([
                'status' => 'completed',
                'percentage' => 100,
                'message' => 'Directory backup completed successfully!',
            ]);

            return back()->with('success', 'Directory backup created successfully.');
        } catch (\Exception $e) {
            Log::error('Directory backup failed: '.$e->getMessage());

            // Update progress to failed
            $this->updateProgress([
                'status' => 'failed',
                'percentage' => 0,
                'message' => 'Directory backup failed: '.$e->getMessage(),
            ]);

            return back()->with('error', 'Directory backup failed: '.$e->getMessage());
        }
    }

    /**
     * Perform full backup (MySQL + all custom paths in 1 file)
     */
    public function backupAll(Request $request)
    {
        $settings = $this->getBackupSettings();
        $backupDir = $settings['backupDirectory'];
        $paths = $settings['customPaths'];
        $mysql = $settings['mysql'];

        // Ensure backup directory exists
        if (! is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }

        $timestamp = now()->format('Y-m-d_H-i-s');
        $filename = "full_backup_{$timestamp}.tar.gz";
        $backupPath = "{$backupDir}/{$filename}";

        // Create temporary directory only for MySQL dump
        $tempDir = sys_get_temp_dir().'/backup_'.$timestamp;
        mkdir($tempDir, 0755, true);

        // Initialize progress
        $this->updateProgress([
            'status' => 'running',
            'percentage' => 0,
            'message' => 'Starting full backup...',
            'total_files' => count($paths) + 1,
            'processed_files' => 0,
        ]);

        try {
            // 1. Backup MySQL to temp directory
            $this->updateProgress([
                'percentage' => 10,
                'message' => 'Dumping MySQL database...',
                'current_file' => 'MySQL database',
            ]);

            // Build mysqldump command
            $mysqlCommand = [
                'mysqldump',
                '-h', $mysql['host'],
                '-P', $mysql['port'],
                '-u', $mysql['username'],
            ];

            if ($mysql['password'] !== '') {
                $mysqlCommand[] = '-p'.$mysql['password'];
            }

            $mysqlCommand[] = '--routines';
            $mysqlCommand[] = '--triggers';
            $mysqlCommand[] = '--events';
            $mysqlCommand[] = '--single-transaction';
            $mysqlCommand[] = '--add-drop-table';
            $mysqlCommand[] = '--default-character-set=utf8mb4';
            $mysqlCommand[] = '--complete-insert';
            $mysqlCommand[] = '--skip-lock-tables';

            $mysqlCommand[] = $mysql['database'];

            Log::info('Starting MySQL backup...');
            $mysqlProcess = new Process($mysqlCommand);
            $mysqlProcess->setTimeout(3600);
            $mysqlProcess->run();

            if (! $mysqlProcess->isSuccessful()) {
                throw new ProcessFailedException($mysqlProcess);
            }

            // Save MySQL dump to temp directory
            $mysqlBackupPath = $tempDir.'/mysql_backup.sql';
            file_put_contents($mysqlBackupPath, $mysqlProcess->getOutput());
            Log::info('MySQL backup completed.');

            $this->updateProgress([
                'percentage' => 30,
                'message' => 'MySQL backup completed',
                'processed_files' => 1,
            ]);

            // 2. Create tar archive with MySQL dump and all directories in one command
            $this->updateProgress([
                'percentage' => 40,
                'message' => 'Creating archive with all files...',
                'current_file' => $filename,
            ]);

            Log::info('Creating tar archive with all files...');

            // Build tar command with MySQL dump and all custom paths
            $tarCommand = [
                'sudo',
                'tar',
                '-czf',
                $backupPath,
                '-C',
                $tempDir,
                'mysql_backup.sql',
            ];

            // Add each custom directory to the tar command
            foreach ($paths as $path) {
                if (! is_dir($path)) {
                    Log::warning("Skipping non-existent directory: {$path}");

                    continue;
                }

                // Add -C and directory to tar command
                $tarCommand[] = '-C';
                $tarCommand[] = dirname($path);
                $tarCommand[] = basename($path);
            }

            $process = new Process($tarCommand);
            $process->setTimeout(3600);
            $process->run();

            if (! $process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

            Log::info('Archive created successfully.');

            $this->updateProgress([
                'percentage' => 95,
                'message' => 'Cleaning up temporary files...',
            ]);

            // 3. Clean up temporary directory
            $this->deleteDirectory($tempDir);

            $this->updateProgress([
                'percentage' => 98,
                'message' => 'Saving backup to database...',
            ]);

            // 4. Log backup to database
            $sourcePaths = implode(', ', $paths);
            DB::table('backups')->insert([
                'type' => 'full',
                'source_path' => $sourcePaths,
                'filename' => $filename,
                'path' => $backupPath,
                'size' => filesize($backupPath),
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            Log::info("Full backup created successfully: {$filename}");

            // Update progress to complete
            $this->updateProgress([
                'status' => 'completed',
                'percentage' => 100,
                'message' => 'Full backup completed successfully!',
            ]);

            return back()->with('success', 'Full backup completed successfully.');
        } catch (\Exception $e) {
            Log::error('Full backup failed: '.$e->getMessage());

            // Update progress to failed
            $this->updateProgress([
                'status' => 'failed',
                'percentage' => 0,
                'message' => 'Full backup failed: '.$e->getMessage(),
            ]);

            // Clean up temporary directory on failure
            if (isset($tempDir) && is_dir($tempDir)) {
                $this->deleteDirectory($tempDir);
            }

            // Clean up backup file if it was partially created
            if (file_exists($backupPath)) {
                unlink($backupPath);
            }

            return back()->with('error', 'Full backup failed: '.$e->getMessage());
        }
    }

    /**
     * Restore MySQL from existing backup file
     */
    public function restoreMySQL(Request $request, string $filename)
    {
        $backup = DB::table('backups')->where('filename', $filename)->first();

        if (! $backup) {
            return back()->with('error', 'Backup not found.');
        }

        if (! in_array($backup->type, ['mysql', 'full'])) {
            return back()->with('error', 'Only MySQL or Full backups can be restored.');
        }

        if (! file_exists($backup->path)) {
            return back()->with('error', 'Backup file not found on disk.');
        }

        $settings = $this->getBackupSettings();
        $mysql = $settings['mysql'];

        $this->updateProgress([
            'status' => 'running',
            'percentage' => 0,
            'message' => 'Starting MySQL restore...',
        ]);

        try {
            $this->updateProgress([
                'percentage' => 10,
                'message' => 'Preparing SQL file...',
            ]);

            if ($backup->type === 'mysql') {
                $sqlContent = gzdecode(file_get_contents($backup->path));
            } else {
                $this->updateProgress([
                    'percentage' => 20,
                    'message' => 'Extracting MySQL dump from full backup...',
                ]);

                $tempDir = sys_get_temp_dir().'/restore_'.now()->format('YmdHis');
                mkdir($tempDir, 0755, true);

                $extractProcess = new Process([
                    'sudo', 'tar', '-xzf', $backup->path,
                    '-C', $tempDir, 'mysql_backup.sql',
                ]);
                $extractProcess->setTimeout(600);
                $extractProcess->run();

                if (! $extractProcess->isSuccessful()) {
                    $this->deleteDirectory($tempDir);
                    throw new ProcessFailedException($extractProcess);
                }

                $sqlFilePath = $tempDir.'/mysql_backup.sql';
                if (! file_exists($sqlFilePath)) {
                    $this->deleteDirectory($tempDir);
                    throw new \Exception('mysql_backup.sql not found in full backup archive.');
                }

                $sqlContent = file_get_contents($sqlFilePath);
                $this->deleteDirectory($tempDir);
            }

            $this->updateProgress([
                'percentage' => 40,
                'message' => 'Importing into MySQL database...',
            ]);

            $tempSqlFile = sys_get_temp_dir().'/restore_'.$filename;
            file_put_contents($tempSqlFile, $sqlContent);

            $command = [
                'mysql',
                '-h', $mysql['host'],
                '-P', $mysql['port'],
                '-u', $mysql['username'],
            ];

            if ($mysql['password'] !== '') {
                $command[] = '-p'.$mysql['password'];
            }

            $command[] = '--default-character-set=utf8mb4';
            $command[] = $mysql['database'];

            $process = new Process($command);
            $process->setInput(file_get_contents($tempSqlFile));
            $process->setTimeout(3600);
            $process->run();

            unlink($tempSqlFile);

            if (! $process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

            $this->updateProgress([
                'status' => 'completed',
                'percentage' => 100,
                'message' => 'MySQL restore completed successfully!',
            ]);

            return back()->with('success', "Database restored from {$filename} successfully.");
        } catch (\Exception $e) {
            Log::error('MySQL restore failed: '.$e->getMessage());

            $this->updateProgress([
                'status' => 'failed',
                'percentage' => 0,
                'message' => 'MySQL restore failed: '.$e->getMessage(),
            ]);

            return back()->with('error', 'MySQL restore failed: '.$e->getMessage());
        }
    }

    /**
     * Import SQL from uploaded file
     */
    public function importSQL(Request $request)
    {
        $request->validate([
            'sql_file' => 'required|file|mimes:sql,gz|max:512000',
        ]);

        $file = $request->file('sql_file');
        $extension = $file->getClientOriginalExtension();
        $settings = $this->getBackupSettings();
        $mysql = $settings['mysql'];

        $this->updateProgress([
            'status' => 'running',
            'percentage' => 0,
            'message' => 'Starting SQL import...',
        ]);

        try {
            $tempSqlFile = sys_get_temp_dir().'/import_'.$file->getClientOriginalName();

            $this->updateProgress([
                'percentage' => 10,
                'message' => 'Reading uploaded file...',
            ]);

            if ($extension === 'gz') {
                $sqlContent = gzdecode(file_get_contents($file->getRealPath()));
                file_put_contents($tempSqlFile, $sqlContent);
            } else {
                $file->move(sys_get_temp_dir(), basename($tempSqlFile));
            }

            $this->updateProgress([
                'percentage' => 30,
                'message' => 'Importing into MySQL database...',
            ]);

            $command = [
                'mysql',
                '-h', $mysql['host'],
                '-P', $mysql['port'],
                '-u', $mysql['username'],
            ];

            if ($mysql['password'] !== '') {
                $command[] = '-p'.$mysql['password'];
            }

            $command[] = '--default-character-set=utf8mb4';
            $command[] = $mysql['database'];

            $process = new Process($command);
            $process->setInput(file_get_contents($tempSqlFile));
            $process->setTimeout(3600);
            $process->run();

            if (file_exists($tempSqlFile)) {
                unlink($tempSqlFile);
            }

            if (! $process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

            $this->updateProgress([
                'status' => 'completed',
                'percentage' => 100,
                'message' => 'SQL import completed successfully!',
            ]);

            return back()->with('success', 'SQL file imported successfully.');
        } catch (\Exception $e) {
            Log::error('SQL import failed: '.$e->getMessage());

            if (isset($tempSqlFile) && file_exists($tempSqlFile)) {
                unlink($tempSqlFile);
            }

            $this->updateProgress([
                'status' => 'failed',
                'percentage' => 0,
                'message' => 'SQL import failed: '.$e->getMessage(),
            ]);

            return back()->with('error', 'SQL import failed: '.$e->getMessage());
        }
    }

    /**
     * Recursively delete a directory
     */
    protected function deleteDirectory($dir)
    {
        if (! is_dir($dir)) {
            return;
        }

        $files = array_diff(scandir($dir), ['.', '..']);
        foreach ($files as $file) {
            $path = $dir.'/'.$file;
            is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
        }

        rmdir($dir);
    }

    /**
     * Download backup file
     */
    public function download(Request $request, string $filename)
    {
        $backup = DB::table('backups')->where('filename', $filename)->first();

        if (! $backup) {
            abort(404, 'Backup not found.');
        }

        $filepath = $backup->path;

        if (! file_exists($filepath)) {
            abort(404, 'Backup file not found on disk.');
        }

        // Create a Symfony Response object directly to avoid Inertia middleware
        return new \Symfony\Component\HttpFoundation\BinaryFileResponse(
            $filepath,
            200,
            [
                'Content-Type' => 'application/octet-stream',
                'Content-Disposition' => 'attachment; filename="'.$filename.'"',
            ]
        );
    }

    /**
     * Delete backup
     */
    public function delete(Request $request, string $filename)
    {
        $backup = DB::table('backups')->where('filename', $filename)->first();

        if (! $backup) {
            return back()->with('error', 'Backup not found.');
        }

        // Delete file
        if (file_exists($backup->path)) {
            unlink($backup->path);
        }

        // Delete record
        DB::table('backups')->where('filename', $filename)->delete();

        return back()->with('success', 'Backup deleted successfully.');
    }

    /**
     * List all backups
     */
    protected function listBackups(): array
    {
        $backups = DB::table('backups')
            ->orderBy('created_at', 'desc')
            ->get()
            ->map(function ($backup) {
                return [
                    'id' => $backup->id,
                    'type' => $backup->type,
                    'filename' => $backup->filename,
                    'source_path' => $backup->source_path,
                    'size' => $this->formatSize($backup->size),
                    'size_bytes' => $backup->size,
                    'created_at' => $backup->created_at,
                ];
            })
            ->toArray();

        return $backups;
    }

    /**
     * Get backup disk usage
     */
    protected function getBackupDiskUsage(): array
    {
        $settings = $this->getBackupSettings();
        $backupDir = $settings['backupDirectory'];

        $totalSize = 0;
        $totalFiles = 0;

        if (is_dir($backupDir)) {
            try {
                $iterator = new \RecursiveIteratorIterator(
                    new \RecursiveDirectoryIterator($backupDir, \RecursiveDirectoryIterator::SKIP_DOTS),
                    \RecursiveIteratorIterator::CHILD_FIRST
                );

                foreach ($iterator as $file) {
                    if ($file->isFile()) {
                        $totalSize += $file->getSize();
                        $totalFiles++;
                    }
                }
            } catch (\UnexpectedValueException $e) {
                // Permission denied or directory cannot be read
                Log::warning("Cannot read backup directory: {$backupDir}. Error: ".$e->getMessage());
            } catch (\Exception $e) {
                // Other errors
                Log::error("Error reading backup directory: {$backupDir}. Error: ".$e->getMessage());
            }
        }

        return [
            'total_size' => $this->formatSize($totalSize),
            'total_size_bytes' => $totalSize,
            'total_files' => $totalFiles,
            'directory' => $backupDir,
        ];
    }

    /**
     * Format file size for display
     */
    protected function formatSize(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;

        return number_format($bytes / pow(1024, $power), 2, '.', ',').' '.$units[$power];
    }
}
