<?php

namespace Modules\WebPilotAI\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Modules\WebPilotAI\Models\Website;
use Throwable;
use ZipArchive; // For unzipping
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Crypt; // For password encryption/decryption
use League\Flysystem\Filesystem;
use League\Flysystem\Ftp\FtpAdapter as FlysystemFtpAdapter; // Renamed to avoid conflict
use Illuminate\Support\Facades\Http; // For cPanel API calls
use League\Flysystem\PhpseclibV3\SftpAdapter;
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;

use Modules\WebPilotAI\Events\WebsiteDeploymentSucceeded; // Import Success Event
use Modules\WebPilotAI\Events\WebsiteDeploymentFailed; // Import Failure Event

class DeployWebsiteToHosting implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public Website $website;
    public array $deploymentConfig;

    /**
     * Create a new job instance.
     */
    public function __construct(Website $website, array $deploymentConfig)
    {
        $this->website = $website;
        // Password is now encrypted by the controller before dispatching.
        $this->deploymentConfig = $deploymentConfig;
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        $hostDisplay = $this->deploymentConfig['host'] ?? 'N/A';
        Log::info("Starting deployment for website ID: {$this->website->id} to host: {$hostDisplay}");
        $this->website->update(['status' => Website::STATUS_DEPLOYING, 'generation_error' => null]); // Clear previous errors

        $password = null;
        if (isset($this->deploymentConfig['password'])) {
            $password = Crypt::decryptString($this->deploymentConfig['password']);
        }
        $tempDir = null;
        $tempPackagePath = null; // For cPanel temporary zip

        try {
            $zipStoragePath = str_replace('storage/', 'public/', $this->website->generated_content_path);
            if (!$this->website->generated_content_path || !Storage::disk('local')->exists($zipStoragePath)) {
                throw new \Exception("Generated website ZIP file not found at '{$zipStoragePath}' for website ID: {$this->website->id}.");
            }

            $absoluteZipPath = Storage::disk('local')->path($zipStoragePath);

            // 1. Create a temporary directory to extract ZIP contents
            $tempDir = storage_path('app/temp_deploy_' . $this->website->id . '_' . time());
            File::ensureDirectoryExists($tempDir);

            // 2. Unzip the contents
            $zip = new ZipArchive;
            if ($zip->open($absoluteZipPath) === TRUE) {
                $zip->extractTo($tempDir);
                $zip->close();
                Log::info("Successfully unzipped {$absoluteZipPath} to {$tempDir} for website ID: {$this->website->id}");
            } else {
                throw new \Exception("Failed to unzip archive for website ID: {$this->website->id}");
            }

            // 3. Implement deployment logic based on type
            $deploymentType = $this->deploymentConfig['deployment_type'];

            if ($deploymentType === 'sftp') {
                Log::info("Attempting SFTP deployment for website ID: {$this->website->id}");

                // Check for password or private key (assuming you might add private_key_path to deploymentConfig)
                if (empty($password) && empty($this->deploymentConfig['private_key_path'])) {
                    throw new \InvalidArgumentException("SFTP deployment requires either a password or a private key. Please check your deployment settings.");
                }

                $sftpConfig = [
                    'host' => $this->deploymentConfig['host'],
                    'username' => $this->deploymentConfig['username'],
                    'password' => $password, // Use the (potentially decrypted) password
                    'port' => (int) ($this->deploymentConfig['port'] ?? 22),
                    'root' => $this->deploymentConfig['remote_path'] ?? '/', // Remote base path
                    // 'privateKey' => $this->deploymentConfig['private_key_path'] ?? null, // Path to private key
                    // 'passphrase' => $this->deploymentConfig['private_key_passphrase'] ?? null, // Passphrase for private key
                    'timeout' => 30, // Connection timeout
                ];

                $provider = new SftpConnectionProvider(
                    $sftpConfig['host'],
                    $sftpConfig['username'],
                    $sftpConfig['password'],
                    $this->deploymentConfig['private_key_path'] ?? null, // private key path
                    $this->deploymentConfig['private_key_passphrase'] ?? null, // passphrase for private key
                    $sftpConfig['port'],
                    false, // use agent
                    $sftpConfig['timeout']
                );

                $adapter = new SftpAdapter($provider, $sftpConfig['root']);
                $filesystem = new Filesystem($adapter);

                // Upload files from $tempDir
                $localFiles = File::allFiles($tempDir);

                foreach ($localFiles as $localFile) {
                    $relativePath = $localFile->getRelativePathname();
                    $remoteFilePath = $relativePath; // Path relative to the SFTP root

                    Log::debug("Uploading {$localFile->getPathname()} to SFTP path: {$remoteFilePath}");
                    $stream = fopen($localFile->getPathname(), 'r+');
                    $filesystem->writeStream($remoteFilePath, $stream);
                    if (is_resource($stream)) {
                        if (!fclose($stream)) {
                            Log::warning("Failed to close file stream for SFTP upload: {$localFile->getPathname()}");
                        }
                    }
                    Log::info("Uploaded {$relativePath} to SFTP for website ID: {$this->website->id}");
                }
                Log::info("SFTP deployment completed for website ID: {$this->website->id}");

            } elseif ($deploymentType === 'ftp') {
                Log::info("Attempting FTP deployment for website ID: {$this->website->id}");

                if (empty($password)) {
                    throw new \InvalidArgumentException("FTP deployment requires a password. Please check your deployment settings.");
                }

                $ftpConfig = [
                    'host' => $this->deploymentConfig['host'],
                    'username' => $this->deploymentConfig['username'],
                    'password' => $password,
                    'port' => (int) ($this->deploymentConfig['port'] ?? 21),
                    'root' => $this->deploymentConfig['remote_path'] ?? '/',
                    'passive' => true, // Often required
                    'ssl' => false, // Set to true for FTPS
                    'timeout' => 30,
                ];

                $adapter = new FlysystemFtpAdapter($ftpConfig);
                $filesystem = new Filesystem($adapter);

                $localFiles = File::allFiles($tempDir);
                foreach ($localFiles as $localFile) {
                    $relativePath = $localFile->getRelativePathname();
                    $remoteFilePath = $relativePath;

                    Log::debug("Uploading {$localFile->getPathname()} to FTP path: {$remoteFilePath}");
                    $stream = fopen($localFile->getPathname(), 'r+');
                    $filesystem->writeStream($remoteFilePath, $stream);
                    if (is_resource($stream)) {
                        if (!fclose($stream)) {
                            Log::warning("Failed to close file stream for FTP upload: {$localFile->getPathname()}");
                        }
                    }
                    Log::info("Uploaded {$relativePath} to FTP for website ID: {$this->website->id}");
                }
                Log::info("FTP deployment completed for website ID: {$this->website->id}");

            } elseif ($deploymentType === 'cpanel') {
                Log::info("Attempting cPanel API deployment for website ID: {$this->website->id}");

                $cpanelHost = $this->deploymentConfig['host'];
                $cpanelUser = $this->deploymentConfig['username'];
                $remotePath = $this->deploymentConfig['remote_path'] ?? 'public_html'; // Default to public_html
                $cpanelPort = (int) ($this->deploymentConfig['port'] ?? 2083); // Default cPanel SSL port

                if (empty($password)) {
                    throw new \InvalidArgumentException("cPanel deployment requires a password. Please check your deployment settings.");
                }

                // 1. Create a new temporary ZIP of the extracted contents
                $tempPackageName = 'webpilotai_deploy_package_' . $this->website->id . '_' . time() . '.zip';
                $tempPackagePath = storage_path('app/' . $tempPackageName);
                $packageZip = new ZipArchive();

                if ($packageZip->open($tempPackagePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
                    throw new \Exception("Could not create temporary package for cPanel deployment.");
                }
                $filesToZip = new \RecursiveIteratorIterator(
                    new \RecursiveDirectoryIterator($tempDir),
                    \RecursiveIteratorIterator::LEAVES_ONLY
                );
                foreach ($filesToZip as $name => $file) {
                    if (!$file->isDir()) {
                        $filePath = $file->getRealPath();
                        $relativePath = substr($filePath, strlen($tempDir) + 1);
                        $packageZip->addFile($filePath, $relativePath);
                    }
                }
                $packageZip->close();
                Log::info("Created temporary cPanel deployment package: {$tempPackagePath}");

                // 2. Upload the temporary package
                $uploadApiUrl = "https://{$cpanelHost}:{$cpanelPort}/execute/Fileman/upload_files";
                $responseUpload = Http::withBasicAuth($cpanelUser, $password)
                    ->attach('file-1', file_get_contents($tempPackagePath), $tempPackageName)
                    ->post($uploadApiUrl, ['dir' => $remotePath]);

                if (!$responseUpload->successful() || !empty($responseUpload->json()['errors'])) {
                    Log::error("cPanel API upload_files failed: " . $responseUpload->body());
                    throw new \Exception("cPanel API Error: Failed to upload website package. Server said: " . ($responseUpload->json()['errors'][0] ?? $responseUpload->status() . ' - ' . $responseUpload->reason()));
                }
                Log::info("cPanel API: Successfully uploaded {$tempPackageName} to {$remotePath}");

                // 3. Extract the uploaded package
                $extractApiUrl = "https://{$cpanelHost}:{$cpanelPort}/execute/Fileman/extract_files";
                $responseExtract = Http::withBasicAuth($cpanelUser, $password)
                    ->post($extractApiUrl, ['dir' => $remotePath, 'file' => $tempPackageName]);

                if (!$responseExtract->successful() || !empty($responseExtract->json()['errors'])) {
                    Log::error("cPanel API extract_files failed: " . $responseExtract->body());
                    Http::withBasicAuth($cpanelUser, $password)->post("https://{$cpanelHost}:{$cpanelPort}/execute/Fileman/unlink_files", ['dir' => $remotePath, 'files' => $tempPackageName]);
                    throw new \Exception("cPanel API Error: Failed to extract website package on server. Server said: " . ($responseExtract->json()['errors'][0] ?? $responseExtract->status() . ' - ' . $responseExtract->reason()));
                }
                Log::info("cPanel API: Successfully extracted {$tempPackageName} in {$remotePath}");

                // 4. Delete the uploaded package from the server
                $deleteApiUrl = "https://{$cpanelHost}:{$cpanelPort}/execute/Fileman/unlink_files";
                $responseDelete = Http::withBasicAuth($cpanelUser, $password)
                    ->post($deleteApiUrl, ['dir' => $remotePath, 'files' => $tempPackageName]);

                if (!$responseDelete->successful() || !empty($responseDelete->json()['errors'])) {
                    Log::warning("cPanel API: Failed to delete uploaded package {$tempPackageName} from server. Manual cleanup may be required. Server said: " . ($responseDelete->json()['errors'][0] ?? $responseDelete->status() . ' - ' . $responseDelete->reason()));
                } else {
                    Log::info("cPanel API: Successfully deleted {$tempPackageName} from server path {$remotePath}");
                }
                Log::info("cPanel API deployment completed for website ID: {$this->website->id}");

            } else {
                Log::warning("Unsupported deployment type '{$deploymentType}' for website ID: {$this->website->id}.");
                throw new \Exception("Unsupported deployment type: {$deploymentType}");
            }

            // If successful, update status and store deployment details
            $this->website->update([
                'status' => Website::STATUS_DEPLOYED,
                'deployment_details' => [
                    'type' => $deploymentType,
                    'host' => $this->deploymentConfig['host'],
                    'username' => $this->deploymentConfig['username'], // Store username for reference
                    'remote_path' => $this->deploymentConfig['remote_path'] ?? ($deploymentType === 'cpanel' ? 'public_html' : '/'),
                    'port' => $this->deploymentConfig['port'] ?? ($deploymentType === 'sftp' ? 22 : ($deploymentType === 'ftp' ? 21 : ($deploymentType === 'cpanel' ? 2083 : null))),
                    'cpanel_domain' => $this->deploymentConfig['cpanel_domain'] ?? null,
                    'deployed_at' => now()->toDateTimeString(),
                ],
                'generation_error' => null, // Clear any previous errors
            ]);
            Log::info("Website ID: {$this->website->id} marked as deployed.");
            WebsiteDeploymentSucceeded::dispatch($this->website);

        } catch (Throwable $e) {
            Log::error("Error deploying website ID {$this->website->id}: {$e->getMessage()} \n" . $e->getTraceAsString());
            $this->website->update([
                'status' => Website::STATUS_DEPLOYMENT_FAILED,
                'generation_error' => "Deployment Error: " . Str::limit($e->getMessage(), 450), // Limit length for DB
                'deployment_details' => null // Clear deployment details on failure
            ]);
            WebsiteDeploymentFailed::dispatch($this->website, "Deployment Error: " . $e->getMessage());
        }
        finally {
            // Clean up the temporary directory
            if ($tempDir && File::isDirectory($tempDir)) {
                File::deleteDirectory($tempDir);
                Log::info("Cleaned up temporary directory: {$tempDir}");
            }
            // Clean up cPanel temporary package if it was created
            if ($tempPackagePath && File::exists($tempPackagePath)) {
                File::delete($tempPackagePath);
                Log::info("Cleaned up temporary cPanel package: {$tempPackagePath}");
            }
        }
    }

    /**
     * Handle a job failure.
     */
    public function failed(Throwable $exception): void
    {
        Log::critical("Deployment job permanently failed for website ID {$this->website->id}: {$exception->getMessage()}");
        // Ensure status is updated if not already set by the handle() method's catch block,
        // which can happen for unhandled exceptions or job timeouts.
        if ($this->website->status !== Website::STATUS_DEPLOYMENT_FAILED) {
            $this->website->update([
                'status' => Website::STATUS_DEPLOYMENT_FAILED,
                'generation_error' => "Deployment Job Failed: " . Str::limit($exception->getMessage(), 450), // Limit length for DB
                'deployment_details' => null // Clear deployment details on permanent job failure
            ]);
        }
        WebsiteDeploymentFailed::dispatch($this->website, "Deployment Job Failed: " . $exception->getMessage(), true);
    }
}
