<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Setting;
use App\Models\BankAccount;
use App\Models\PaymentMethod;
use App\Models\PaymentType;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Config;
use ZipArchive;

class SettingsController extends Controller
{
    /**
     * Display settings dashboard with tabs.
     */
    public function index()
    {
        $bankAccounts = BankAccount::all();
        $paymentMethods = PaymentMethod::all();
        
        $paymentTypesIn = PaymentType::where('direction', 'in')->get();
        $paymentTypesOut = PaymentType::where('direction', 'out')->get();

        // Read all settings
        $settings = Setting::pluck('value', 'key')->all();

        // Gather list of backup zips from storage
        $backups = [];
        $backupDiskPath = storage_path('app');
        $files = $this->rglob($backupDiskPath . '/*.zip');

        foreach ($files as $file) {
            $backups[] = [
                'filename' => basename($file),
                'size' => filesize($file),
                'date' => filemtime($file),
                'path' => str_replace(storage_path('app/'), '', $file),
            ];
        }

        // Sort backups by date descending
        usort($backups, fn($a, $b) => $b['date'] - $a['date']);

        return view('settings.index', compact(
            'bankAccounts', 'paymentMethods', 
            'paymentTypesIn', 'paymentTypesOut', 'settings', 'backups'
        ));
    }

    /**
     * Save business details settings.
     */
    public function saveBusiness(Request $request)
    {
        $request->validate([
            'business_name' => 'required|string|max:255',
            'business_email' => 'nullable|email|max:255',
            'business_phone' => 'nullable|string|max:100',
            'business_address' => 'nullable|string',
            'business_tax_ref' => 'nullable|string|max:100',
            'business_logo' => 'nullable|image|mimes:png,jpg,jpeg,svg,webp|max:2048',
            'business_favicon' => 'nullable|file|mimes:png,jpg,jpeg,svg,ico|max:2048',
            'invoice_logo' => 'nullable|image|mimes:png,jpg,jpeg,svg,webp|max:2048',
        ]);

        foreach ($request->only(['business_name', 'business_email', 'business_phone', 'business_address', 'business_tax_ref']) as $key => $val) {
            Setting::updateOrCreate(['key' => $key], ['value' => $val ?: '']);
        }

        // Handle Business Logo Upload
        if ($request->hasFile('business_logo')) {
            $logoFile = $request->file('business_logo');
            $logoFilename = time() . '_' . uniqid() . '.' . $logoFile->guessExtension();
            
            // Store directly in public/uploads/logo to avoid symlink issues
            $logoFile->move(public_path('uploads/logo'), $logoFilename);
            $newLogoPath = 'uploads/logo/' . $logoFilename;

            // Delete old logo file if it exists
            $oldLogo = Setting::where('key', 'business_logo')->value('value');
            if ($oldLogo && file_exists(public_path($oldLogo))) {
                @unlink(public_path($oldLogo));
            }

            Setting::updateOrCreate(['key' => 'business_logo'], ['value' => $newLogoPath]);
        }

        // Handle Business Favicon Upload
        if ($request->hasFile('business_favicon')) {
            $faviconFile = $request->file('business_favicon');
            $ext = $faviconFile->guessExtension();
            
            $faviconFilename = time() . '_' . uniqid() . '.' . $ext;
            
            // Store directly in public/uploads/favicon to avoid symlink issues
            $faviconFile->move(public_path('uploads/favicon'), $faviconFilename);
            $newFaviconPath = 'uploads/favicon/' . $faviconFilename;

            // Delete old favicon file if it exists
            $oldFavicon = Setting::where('key', 'business_favicon')->value('value');
            if ($oldFavicon && file_exists(public_path($oldFavicon))) {
                @unlink(public_path($oldFavicon));
            }

            Setting::updateOrCreate(['key' => 'business_favicon'], ['value' => $newFaviconPath]);
        }

        // Handle Invoice Logo Upload
        if ($request->hasFile('invoice_logo')) {
            $logoFile = $request->file('invoice_logo');
            $logoFilename = time() . '_' . uniqid() . '.' . $logoFile->guessExtension();
            $logoFile->move(public_path('uploads/logo'), $logoFilename);
            $newLogoPath = 'uploads/logo/' . $logoFilename;

            $oldLogo = Setting::where('key', 'invoice_logo')->value('value');
            if ($oldLogo && file_exists(public_path($oldLogo))) {
                @unlink(public_path($oldLogo));
            }

            Setting::updateOrCreate(['key' => 'invoice_logo'], ['value' => $newLogoPath]);
        }

        try {
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
            Artisan::call('view:clear');
        } catch (\Exception $e) {}

        return back()->with('success', 'Business profile settings saved.')->with('active_tab', 'business');
    }

    /**
     * Save global currency settings.
     */
    public function saveCurrency(Request $request)
    {
        $request->validate([
            'currency_symbol' => 'required|string|max:10',
            'base_currency_code' => 'required|string|size:3',
            'exchange_api_provider' => 'required|in:frankfurter,exchangerate_api,fawaz,exchangerate_api_free',
            'exchange_api_key' => 'nullable|string',
        ]);

        Setting::updateOrCreate(['key' => 'currency_symbol'], ['value' => $request->currency_symbol]);
        Setting::updateOrCreate(['key' => 'base_currency_code'], ['value' => strtoupper($request->base_currency_code)]);
        Setting::updateOrCreate(['key' => 'exchange_api_provider'], ['value' => $request->exchange_api_provider]);
        Setting::updateOrCreate(['key' => 'exchange_api_key'], ['value' => $request->exchange_api_key ?? '']);

        try {
            \Illuminate\Support\Facades\Artisan::call('config:clear');
            \Illuminate\Support\Facades\Artisan::call('cache:clear');
            \Illuminate\Support\Facades\Artisan::call('view:clear');
        } catch (\Exception $e) {}

        return back()->with('success', 'Global currency settings updated.')->with('active_tab', 'business');
    }

    /**
     * Save VAT settings.
     */
    public function saveVat(Request $request)
    {
        $request->validate([
            'vat_default_rate' => 'nullable|numeric|min:0|max:100',
            'vat_registration_no' => 'nullable|string|max:50',
            'vat_mtd_vrn' => 'nullable|string|max:50',
        ]);

        foreach ($request->only(['vat_default_rate', 'vat_registration_no', 'vat_mtd_vrn']) as $key => $val) {
            Setting::updateOrCreate(['key' => $key], ['value' => $val ?: '']);
        }

        try {
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
        } catch (\Exception $e) {}

        return back()->with('success', 'VAT configuration saved.')->with('active_tab', 'vat');
    }

    /**
     * Store Bank Account details.
     */
    public function storeBankAccount(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'bank_name' => 'required|string|max:255',
            'account_number' => 'required|string|max:100',
            'sort_code' => 'nullable|string|max:50',
            'type' => 'required|in:business,personal',
        ]);

        BankAccount::create($request->only(['name', 'bank_name', 'account_number', 'sort_code', 'type']));

        return back()->with('success', 'Bank Account registered.')->with('active_tab', 'banks');
    }

    /**
     * Update Bank Account details.
     */
    public function updateBankAccount(Request $request, BankAccount $bankAccount)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'bank_name' => 'required|string|max:255',
            'account_number' => 'required|string|max:100',
            'sort_code' => 'nullable|string|max:50',
            'type' => 'required|in:business,personal',
        ]);

        $bankAccount->update($request->only(['name', 'bank_name', 'account_number', 'sort_code', 'type']));

        return back()->with('success', 'Bank Account details updated.')->with('active_tab', 'banks');
    }

    /**
     * Delete Bank Account.
     */
    public function deleteBankAccount(BankAccount $bankAccount)
    {
        $bankAccount->delete();
        return back()->with('success', 'Bank Account removed.')->with('active_tab', 'banks');
    }

    /**
     * Support settings: Payment Methods.
     */
    public function storePaymentMethod(Request $request)
    {
        $request->validate(['name' => 'required|string|max:255']);
        PaymentMethod::create($request->only('name'));
        return back()->with('success', 'Payment Method added.')->with('active_tab', 'support');
    }

    public function deletePaymentMethod(PaymentMethod $paymentMethod)
    {
        $paymentMethod->delete();
        return back()->with('success', 'Payment Method deleted.')->with('active_tab', 'support');
    }



    /**
     * Support settings: Payment Types.
     */
    public function storePaymentType(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'direction' => 'required|in:in,out',
        ]);
        PaymentType::create($request->only(['name', 'direction']));
        return back()->with('success', 'Payment Type registered.')->with('active_tab', 'support');
    }

    public function deletePaymentType(PaymentType $paymentType)
    {
        $paymentType->delete();
        return back()->with('success', 'Payment Type deleted.')->with('active_tab', 'support');
    }

    /**
     * Save SMTP settings.
     */
    public function saveSmtp(Request $request)
    {
        $request->validate([
            'smtp_host' => 'required|string|max:255',
            'smtp_port' => 'required|numeric',
            'smtp_username' => 'nullable|string|max:255',
            'smtp_password' => 'nullable|string|max:255',
            'smtp_encryption' => 'nullable|string|max:50',
            'smtp_from_address' => 'required|email|max:255',
        ]);

        foreach ($request->only(['smtp_host', 'smtp_port', 'smtp_username', 'smtp_password', 'smtp_encryption', 'smtp_from_address']) as $key => $val) {
            if ($key === 'smtp_password' && empty($val)) {
                continue;
            }
            Setting::updateOrCreate(['key' => $key], ['value' => $val ?: '']);
        }

        try {
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
        } catch (\Exception $e) {}

        return back()->with('success', 'SMTP server configuration saved.')->with('active_tab', 'smtp');
    }

    /**
     * Test SMTP configuration.
     */
    public function testSmtp(Request $request)
    {
        $request->validate([
            'test_email' => 'required|email',
        ]);

        // Load SMTP config dynamically from settings
        $settings = Setting::pluck('value', 'key')->all();

        if (empty($settings['smtp_host']) || empty($settings['smtp_port'])) {
            return response()->json(['success' => false, 'message' => 'Please save SMTP configurations first before testing.']);
        }

        // Apply config dynamically
        Config::set('mail.default', 'smtp');
        Config::set('mail.mailers.smtp.host', $settings['smtp_host']);
        Config::set('mail.mailers.smtp.port', $settings['smtp_port']);
        Config::set('mail.mailers.smtp.username', $settings['smtp_username'] ?? null);
        Config::set('mail.mailers.smtp.password', !empty($settings['smtp_password']) ? $settings['smtp_password'] : null);
        Config::set('mail.mailers.smtp.encryption', $settings['smtp_encryption'] ?? null);
        Config::set('mail.mailers.smtp.stream', ['ssl' => ['verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => true]]);
        Config::set('mail.from.address', $settings['smtp_from_address']);
        Config::set('mail.from.name', $settings['business_name'] ?? 'RentTrack Pro');

        try {
            Mail::raw('This is a test email from RentTrack Pro verifying SMTP connectivity.', function ($message) use ($request) {
                $message->to($request->test_email)->subject('RentTrack Pro - SMTP Test');
            });

            return response()->json(['success' => true, 'message' => 'Test email sent successfully to ' . $request->test_email]);
        } catch (\Exception $e) {
            return response()->json(['success' => false, 'message' => 'Failed to send mail: ' . $e->getMessage()]);
        }
    }

    /**
     * Helper: Core backup generation logic.
     */
    private function performBackup($backupName)
    {
        $timestamp = now()->format('Y-m-d-His');
        $tempDir = storage_path("app/backup-temp/{$timestamp}");

        try {
            $this->ensureDirExists($tempDir);

            // 1. Dump database
            $sqlPath = "{$tempDir}/database.sql";
            $this->dumpDatabase($sqlPath);

            // 2. Copy storage/app/public (receipts, documents, photos)
            $publicStorage = storage_path('app/public');
            if (is_dir($publicStorage)) {
                $this->copyDir($publicStorage, "{$tempDir}/storage/app/public");
            }

            // 3. Copy public/uploads (logo, favicon, dashboard icons)
            $uploads = public_path('uploads');
            if (is_dir($uploads)) {
                $this->copyDir($uploads, "{$tempDir}/public/uploads");
            }

            // 4. Zip everything
            $zipPath = storage_path("app/{$backupName}");
            $zip = new \ZipArchive();
            if ($zip->open($zipPath, \ZipArchive::CREATE) !== TRUE) {
                throw new \Exception('Could not create backup zip file.');
            }
            $this->addDirToZip($zip, $tempDir, strlen(dirname($tempDir)) + 1);
            $zip->close();

            // 5. Cleanup temp
            $this->deleteDir($tempDir);

            // 6. Rotate backups (keep only the latest 2)
            $this->rotateBackups();

            return true;
        } catch (\Exception $e) {
            $this->deleteDir($tempDir);
            throw $e;
        }
    }

    /**
     * Create full system backup (database + uploaded files).
     */
    public function createBackup()
    {
        $timestamp = now()->format('Y-m-d-His');
        $backupName = "backup-{$timestamp}.zip";

        try {
            $this->performBackup($backupName);
            return back()->with('success', "Full system backup created: {$backupName}")->with('active_tab', 'backups');
        } catch (\Exception $e) {
            return back()->with('error', 'Backup failed: ' . $e->getMessage())->with('active_tab', 'backups');
        }
    }

    /**
     * Spatie Backups: Download backup file.
     */
    public function downloadBackup($filename)
    {
        $safeName = basename($filename);
        $files = $this->rglob(storage_path('app/' . $safeName));

        if (!empty($files) && file_exists($files[0])) {
            return response()->download($files[0]);
        }

        // Search folders
        $folders = glob(storage_path('app/*/' . $safeName));
        if (!empty($folders) && file_exists($folders[0])) {
            return response()->download($folders[0]);
        }

        return back()->withErrors(['error' => 'Backup file not found.'])->with('active_tab', 'backups');
    }

    /**
     * Full system restore: database + uploaded files.
     */
    public function restoreBackup($filename)
    {
        $safeName = basename($filename);
        $files = $this->rglob(storage_path('app/' . $safeName));
        
        if (empty($files) || !file_exists($files[0])) {
            $folders = glob(storage_path('app/*/' . $safeName));
            if (!empty($folders) && file_exists($folders[0])) {
                $files = [$folders[0]];
            } else {
                return back()->withErrors(['error' => 'Backup zip file not found.'])->with('active_tab', 'backups');
            }
        }

        $zipPath = $files[0];
        $extractDir = storage_path('app/backup-temp/restore-' . uniqid());
        $zip = new ZipArchive();

        if ($zip->open($zipPath) !== TRUE) {
            return back()->withErrors(['error' => 'Could not open backup zip archive.'])->with('active_tab', 'backups');
        }

        // 1. Extract SQL dump from zip (read without extracting)
        $sqlContent = null;
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $stat = $zip->statIndex($i);
            if (pathinfo($stat['name'], PATHINFO_EXTENSION) === 'sql') {
                $sqlContent = $zip->getFromIndex($i);
                break;
            }
        }

        if (!$sqlContent) {
            $zip->close();
            return back()->withErrors(['error' => 'No SQL dump file (.sql) found inside the backup zip.'])->with('active_tab', 'backups');
        }

        // Validate SQL safety
        if (!str_contains($sqlContent, 'CREATE TABLE') && !str_contains($sqlContent, 'INSERT INTO')) {
            $zip->close();
            return back()->withErrors(['error' => 'Invalid backup file: No SQL structure found.'])->with('active_tab', 'backups');
        }
        if (preg_match('/\b(DROP\s+DATABASE|ALTER\s+USER|GRANT|REVOKE|CREATE\s+USER)\b/i', $sqlContent)) {
            $zip->close();
            return back()->withErrors(['error' => 'Invalid backup file: Contains prohibited SQL commands.'])->with('active_tab', 'backups');
        }

        try {
            // 2. Restore database
            DB::statement('SET FOREIGN_KEY_CHECKS=0;');
            DB::unprepared($sqlContent);
            DB::statement('SET FOREIGN_KEY_CHECKS=1;');

            // 3. Extract entire zip to temp directory for file restoration
            $this->ensureDirExists($extractDir);
            $zip->extractTo($extractDir);
            $zip->close();

            // 4. Restore storage/app/public (receipts, documents, photos)
            $storageSource = $extractDir . '/storage/app/public';
            if (is_dir($storageSource)) {
                $this->ensureDirExists(storage_path('app/public'));
                $this->copyDir($storageSource, storage_path('app/public'));
            }

            // 5. Restore public/uploads (logo, favicon, dashboard icons)
            $uploadsSource = $extractDir . '/public/uploads';
            if (is_dir($uploadsSource)) {
                $this->ensureDirExists(public_path('uploads'));
                $this->copyDir($uploadsSource, public_path('uploads'));
            }

            // 6. Cleanup temp extraction
            $this->deleteDir($extractDir);

            // 7. Re-create public/storage symlink
            $this->ensureStorageLink();

            // 8. Clear application cache
            try {
                Artisan::call('cache:clear');
                Artisan::call('config:clear');
                Artisan::call('view:clear');
            } catch (\Exception $e) {}

            return back()->with('success', 'Full system restored successfully (database + files): ' . $safeName)->with('active_tab', 'backups');
        } catch (\Exception $e) {
            DB::statement('SET FOREIGN_KEY_CHECKS=1;');
            $this->deleteDir($extractDir);
            return back()->withErrors(['error' => 'Restore failed: ' . $e->getMessage()])->with('active_tab', 'backups');
        }
    }

    /**
     * Spatie Backups: Delete backup file.
     */
    public function deleteBackup($filename)
    {
        $safeName = basename($filename);
        $files = $this->rglob(storage_path('app/' . $safeName));

        if (!empty($files) && file_exists($files[0])) {
            unlink($files[0]);
            return back()->with('success', 'Backup file deleted.')->with('active_tab', 'backups');
        }

        $folders = glob(storage_path('app/*/' . $safeName));
        if (!empty($folders) && file_exists($folders[0])) {
            unlink($folders[0]);
            return back()->with('success', 'Backup file deleted.')->with('active_tab', 'backups');
        }

        return back()->with('error', 'Could not find backup file to delete.')->with('active_tab', 'backups');
    }

    /**
     * Upload a backup ZIP file and restore the system from it.
     */
    public function uploadRestoreBackup(Request $request)
    {
        $request->validate([
            'backup_file' => 'required|file|mimes:zip|max:51200', // max 50MB
        ]);

        $file = $request->file('backup_file');
        $filename = 'uploaded-backup-' . time() . '.zip';
        
        // Move the file to storage/app
        $file->move(storage_path('app'), $filename);

        // Perform restore using existing method
        $response = $this->restoreBackup($filename);

        // Cleanup the uploaded zip file after restore attempt
        $filePath = storage_path('app/' . $filename);
        if (file_exists($filePath)) {
            unlink($filePath);
        }

        return $response;
    }

    /**
     * Toggle 6-hour automatic backup setting.
     */
    public function toggleAutoBackup(Request $request)
    {
        Setting::updateOrCreate(['key' => 'auto_backup'], ['value' => $request->has('auto_backup') ? '1' : '0']);
        return back()->with('success', 'Auto-backup settings updated.')->with('active_tab', 'backups');
    }

    /**
     * Helper: Execute automatic 6-hour backup.
     */
    public function runAutoBackup()
    {
        $timestamp = now()->format('Y-m-d-His');
        $backupName = "auto-backup-{$timestamp}.zip";

        try {
            $this->performBackup($backupName);
            return true;
        } catch (\Exception $e) {
            \Illuminate\Support\Facades\Log::error('Auto-backup run failed: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Helper: Execute automatic logout backup.
     */
    public function runLogoutBackup()
    {
        $timestamp = now()->format('Y-m-d-His');
        $backupName = "logout-backup-{$timestamp}.zip";

        try {
            $this->performBackup($backupName);
            return true;
        } catch (\Exception $e) {
            \Illuminate\Support\Facades\Log::error('Logout backup run failed: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Helper: Keep only the latest 2 backup zip files direct in storage/app
     */
    public function rotateBackups()
    {
        $backupDiskPath = storage_path('app');
        $files = glob($backupDiskPath . '/*.zip');

        if (count($files) > 2) {
            // Sort files by modified time ascending (oldest first)
            usort($files, function ($a, $b) {
                return filemtime($a) - filemtime($b);
            });

            // Delete oldest files until we have exactly 2 left
            while (count($files) > 2) {
                $oldest = array_shift($files);
                if (file_exists($oldest)) {
                    @unlink($oldest);
                }
            }
        }
    }

    /**
     * Programmatic "Link Storage" button.
     */
    public function generateStorageLink()
    {
        $link = public_path('storage');

        if (file_exists($link)) {
            return back()->with('success', 'Storage link already exists.')->with('active_tab', 'maintenance');
        }

        try {
            Artisan::call('storage:link');
            return back()->with('success', 'Public storage link created successfully.')->with('active_tab', 'maintenance');
        } catch (\Exception $e) {
            $this->ensureStorageLink();

            if (file_exists($link)) {
                return back()->with('success', 'Public storage symlink mapped programmatically.')->with('active_tab', 'maintenance');
            }

            return back()->with('error', 'Failed to link public storage: ' . $e->getMessage())->with('active_tab', 'maintenance');
        }
    }

    /**
     * Helper: Recursive glob to find backups nested in subdirectories
     */
    protected function rglob($pattern, $flags = 0)
    {
        $files = glob($pattern, $flags);
        foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
            $files = array_merge($files, $this->rglob($dir . '/' . basename($pattern), $flags));
        }
        return $files;
    }

    /**
     * Dump MySQL database to a .sql file using PHP.
     */
    private function dumpDatabase($sqlPath)
    {
        $pdo = DB::connection()->getPdo();
        $db = config('database.connections.mysql.database');
        $tables = DB::select("SHOW TABLES");
        $key = "Tables_in_{$db}";

        $sql = "-- RentTrack Pro Full System Backup\n";
        $sql .= "-- Generated: " . now() . "\n\n";
        $sql .= "SET FOREIGN_KEY_CHECKS=0;\n\n";

        foreach ($tables as $tableObj) {
            $table = $tableObj->$key;

            // Skip volatile Laravel cache/jobs tables
            if (in_array($table, ['cache', 'cache_locks', 'jobs', 'job_batches', 'sessions'])) {
                continue;
            }

            // CREATE TABLE
            $create = DB::select("SHOW CREATE TABLE `{$table}`");
            $createStmt = $create[0]->{'Create Table'};
            $sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
            $sql .= "{$createStmt};\n\n";

            // INSERT data
            $first = true;
            $rowCount = 0;
            foreach (DB::table($table)->cursor() as $row) {
                if ($first) {
                    $columns = implode('`, `', array_keys((array) $row));
                    $sql .= "INSERT INTO `{$table}` (`{$columns}`) VALUES\n";
                    $first = false;
                } else {
                    $sql .= ",\n";
                }

                $vals = array_map(function ($val) use ($pdo) {
                    if (is_null($val)) return 'NULL';
                    return $pdo->quote($val);
                }, (array) $row);
                $sql .= '(' . implode(', ', $vals) . ')';
                $rowCount++;
            }

            if ($rowCount > 0) {
                $sql .= ";\n\n";
            }
        }

        $sql .= "SET FOREIGN_KEY_CHECKS=1;\n";

        file_put_contents($sqlPath, $sql);
    }

    /**
     * Create a directory if it doesn't exist.
     */
    private function ensureDirExists($path)
    {
        if (!is_dir($path)) {
            mkdir($path, 0755, true);
        }
    }

    /**
     * Recursively copy a directory.
     */
    private function copyDir($source, $dest)
    {
        $this->ensureDirExists($dest);
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $item) {
            $target = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathname();
            if ($item->isDir()) {
                $this->ensureDirExists($target);
            } else {
                copy($item, $target);
            }
        }
    }

    /**
     * Recursively delete a directory.
     */
    private function deleteDir($path)
    {
        if (!is_dir($path)) return;
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach ($iterator as $item) {
            if ($item->isDir()) {
                rmdir($item);
            } else {
                unlink($item);
            }
        }
        rmdir($path);
    }

    /**
     * Recursively add a directory to a ZipArchive.
     */
    private function addDirToZip($zip, $path, $prefixLen)
    {
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $item) {
            $localName = substr($item->getPathname(), $prefixLen);
            if ($item->isDir()) {
                $zip->addEmptyDir($localName);
            } else {
                $zip->addFile($item->getPathname(), $localName);
            }
        }
    }

    /**
     * Clear all application caches (config, route, view, cache).
     */
    public function clearCache()
    {
        try {
            Artisan::call('config:clear');
            Artisan::call('cache:clear');
            Artisan::call('view:clear');
            Artisan::call('route:clear');
            $this->clearCompiledViews();

            return back()->with('success', 'All application caches cleared successfully.')->with('active_tab', 'maintenance');
        } catch (\Exception $e) {
            return back()->with('error', 'Failed to clear cache: ' . $e->getMessage())->with('active_tab', 'maintenance');
        }
    }

    /**
     * Manually clear compiled view files (fallback if Artisan fails).
     */
    private function clearCompiledViews(): void
    {
        $viewPath = storage_path('framework/views');
        if (is_dir($viewPath)) {
            foreach (glob($viewPath . '/*.php') as $file) {
                @unlink($file);
            }
        }
    }

    /**
     * Ensure public/storage symlink exists.
     */
    private function ensureStorageLink()
    {
        $target = storage_path('app/public');
        $link = public_path('storage');

        if (file_exists($link)) return;

        try {
            if (symlink($target, $link)) {
                return;
            }
        } catch (\Exception $e) {}
    }
}
