<?php

namespace App\Filament\Pages;

use Filament\Pages\Page;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Response;
use Filament\Actions\Contracts\HasActions;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Concerns\InteractsWithForms;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use Exception;

class BackupManager extends Page implements HasForms, HasActions
{
    use InteractsWithForms, InteractsWithActions;

    protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cpu-chip';

    protected string $view = 'filament.pages.backup-manager';

    protected static string|\UnitEnum|null $navigationGroup = 'Settings';

    protected static ?string $title = 'System Backups';

    protected static ?int $navigationSort = 4;

    public array $backups = [];

    public function mount()
    {
        $this->loadBackups();
    }

    /**
     * Load list of existing backup files.
     */
    public function loadBackups()
    {
        $disk = Storage::disk('local');
        $backupName = config('backup.backup.name', 'laravel-backup');
        
        $files = $disk->files($backupName);
        
        $this->backups = [];

        foreach ($files as $file) {
            if (pathinfo($file, PATHINFO_EXTENSION) === 'zip') {
                $this->backups[] = [
                    'path' => $file,
                    'filename' => basename($file),
                    'size' => $this->formatBytes($disk->size($file)),
                    'created_at' => date('Y-m-d H:i:s', $disk->lastModified($file)),
                ];
            }
        }

        // Sort backups by creation date desc
        usort($this->backups, fn($a, $b) => strcmp($b['created_at'], $a['created_at']));
    }

    /**
     * Format file size in bytes to human readable form.
     */
    private function formatBytes($bytes, $precision = 2) {
        $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, $precision) . ' ' . $units[$pow];
    }

    /**
     * Define header actions.
     */
    protected function getHeaderActions(): array
    {
        return [
            $this->createBackupAction(),
        ];
    }

    /**
     * Register page actions.
     */
    protected function getActions(): array
    {
        return [
            $this->restoreBackupAction(),
        ];
    }

    /**
     * Action to create a new backup with options.
     */
    public function createBackupAction(): Action
    {
        return Action::make('createBackup')
            ->label('Create Backup')
            ->modalHeading('Create New Backup')
            ->color('success')
            ->icon('heroicon-m-arrow-path')
            ->form([
                \Filament\Forms\Components\Toggle::make('only_db')
                    ->label('Backup Database Only')
                    ->helperText('Excludes media uploads and vendor files. Database backups are fast and lightweight.')
                    ->default(true),
            ])
            ->action(function (array $data) {
                try {
                    $arguments = ['--only-to-disk' => 'local'];
                    if ($data['only_db']) {
                        $arguments['--only-db'] = true;
                    }

                    Artisan::call('backup:run', $arguments);
                    
                    $this->loadBackups();

                    Notification::make()
                        ->title('Backup Successful')
                        ->body('A new backup archive has been created successfully.')
                        ->success()
                        ->send();
                } catch (Exception $e) {
                    Log::error('Backup creation failure: ' . $e->getMessage());
                    
                    Notification::make()
                        ->title('Backup Failed')
                        ->body('Could not run backup: ' . $e->getMessage())
                        ->danger()
                        ->send();
                }
            });
    }

    /**
     * Action to restore a database backup.
     */
    public function restoreBackupAction(): Action
    {
        return Action::make('restoreBackup')
            ->label('Restore')
            ->requiresConfirmation()
            ->modalHeading('Restore Database Backup')
            ->modalDescription('WARNING: Restoring a backup is a destructive action that will completely overwrite your current database. Make sure you have a safe copy before proceeding.')
            ->form([
                \Filament\Forms\Components\TextInput::make('password')
                    ->label('Confirm Admin Password')
                    ->password()
                    ->required()
                    ->rules([
                        fn () => function (string $attribute, $value, \Closure $fail) {
                            if (!Hash::check($value, auth()->user()->password)) {
                                $fail('The password you entered is incorrect.');
                            }
                        }
                    ]),
            ])
            ->action(function (array $data, array $arguments) {
                $filename = $arguments['filename'];
                try {
                    $this->performRestore($filename);
                    
                    Notification::make()
                        ->title('Restore Successful')
                        ->body('The database has been restored successfully from ' . $filename)
                        ->success()
                        ->send();
                        
                    $this->loadBackups();
                } catch (Exception $e) {
                    Log::error('Database restore failed: ' . $e->getMessage());
                    Notification::make()
                        ->title('Restore Failed')
                        ->body('Failed to restore backup: ' . $e->getMessage())
                        ->danger()
                        ->send();
                }
            });
    }

    /**
     * Extract zip and import SQL dump.
     */
    private function performRestore(string $filename)
    {
        $filename = basename($filename);
        $backupName = config('backup.backup.name', 'laravel-backup');
        $path = $backupName . '/' . $filename;
        $disk = Storage::disk('local');

        if (!$disk->exists($path)) {
            throw new Exception('Backup file not found on disk.');
        }

        $zipPath = $disk->path($path);

        if (!class_exists(\ZipArchive::class)) {
            throw new Exception('PHP ZipArchive extension is not enabled.');
        }

        $zip = new \ZipArchive();
        if ($zip->open($zipPath) !== true) {
            throw new Exception('Failed to open backup ZIP file.');
        }

        $backupPassword = config('backup.backup.password');
        if (!empty($backupPassword)) {
            $zip->setPassword($backupPassword);
        }

        // Create temporary extraction directory
        $tempDir = storage_path('app/private/temp-restore-' . uniqid());
        if (!file_exists($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        // Search for the .sql file in ZIP
        $sqlFileInZip = null;
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = $zip->getNameIndex($i);
            if (pathinfo($name, PATHINFO_EXTENSION) === 'sql') {
                $sqlFileInZip = $name;
                break;
            }
        }

        if (!$sqlFileInZip) {
            $zip->close();
            $this->deleteDirectory($tempDir);
            throw new Exception('No database dump (.sql) found in the backup archive.');
        }

        // Extract ONLY the SQL file
        $zip->extractTo($tempDir, $sqlFileInZip);
        $zip->close();

        $sqlPath = $tempDir . '/' . $sqlFileInZip;

        if (!file_exists($sqlPath)) {
            $this->deleteDirectory($tempDir);
            throw new Exception('Extracted SQL file could not be found.');
        }

        // Run the SQL import
        $connection = config('database.default');
        if ($connection !== 'mysql') {
            $this->deleteDirectory($tempDir);
            throw new Exception('Restore functionality only supports MySQL connections.');
        }

        $config = config("database.connections.{$connection}");
        $host = $config['host'] ?? '127.0.0.1';
        $port = $config['port'] ?? '3306';
        $database = $config['database'];
        $username = $config['username'];
        $password = $config['password'] ?? '';

        $imported = false;
        $errorMessage = '';

        try {
            // First attempt: use mysql CLI client (most robust)
            $args = [
                'mysql',
                '--host=' . $host,
                '--port=' . $port,
                '--user=' . $username,
            ];
            if ($password !== '') {
                $args[] = '--password=' . $password;
            }
            $args[] = $database;

            $sqlContent = file_get_contents($sqlPath);
            $result = \Illuminate\Support\Facades\Process::input($sqlContent)
                ->run($args);

            if ($result->successful()) {
                $imported = true;
            } else {
                $errorMessage = $result->errorOutput();
            }
        } catch (Exception $e) {
            $errorMessage = $e->getMessage();
        }

        // Second attempt: fallback to DB::unprepared (portable)
        if (!$imported) {
            try {
                \Illuminate\Support\Facades\DB::unprepared(file_get_contents($sqlPath));
                $imported = true;
            } catch (Exception $e) {
                $errorMessage .= ' | CLI failed. Fallback error: ' . $e->getMessage();
            }
        }

        // Clean up temp directory
        $this->deleteDirectory($tempDir);

        if (!$imported) {
            throw new Exception('Database import failed: ' . $errorMessage);
        }
    }

    /**
     * Recursively delete directory.
     */
    private function deleteDirectory(string $dir): bool
    {
        if (!file_exists($dir)) {
            return true;
        }

        if (!is_dir($dir)) {
            return unlink($dir);
        }

        foreach (scandir($dir) as $item) {
            if ($item == '.' || $item == '..') {
                continue;
            }

            if (!$this->deleteDirectory($dir . '/' . $item)) {
                return false;
            }
        }

        return rmdir($dir);
    }

    /**
     * Securely download backup ZIP file.
     */
    public function downloadBackup(string $filename)
    {
        $filename = basename($filename);
        $backupName = config('backup.backup.name', 'laravel-backup');
        $path = $backupName . '/' . $filename;

        if (Storage::disk('local')->exists($path)) {
            return Storage::disk('local')->download($path);
        }

        Notification::make()
            ->title('File Not Found')
            ->body('The requested backup file does not exist on disk.')
            ->danger()
            ->send();
    }

    /**
     * Delete backup ZIP file.
     */
    public function deleteBackup(string $filename)
    {
        $filename = basename($filename);
        $backupName = config('backup.backup.name', 'laravel-backup');
        $path = $backupName . '/' . $filename;

        if (Storage::disk('local')->exists($path)) {
            Storage::disk('local')->delete($path);
            $this->loadBackups();

            Notification::make()
                ->title('Backup Deleted')
                ->body('The backup file has been deleted successfully.')
                ->success()
                ->send();
            return;
        }

        Notification::make()
            ->title('Delete Failed')
            ->body('File not found.')
            ->danger()
            ->send();
    }
}
