<?php

namespace App\Commands;

use App\Services\Storage\FileStorageException;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\Storage;

class StorageUploadTest extends BaseCommand
{
    protected $group = 'Testing';

    protected $name = 'storage:upload-test';

    protected $description = 'Run a live file upload/download/delete check against configured storage (local or S3).';

    protected $usage = 'storage:upload-test [options]';

    protected $options = [
        '--keep' => 'Keep uploaded test files in storage (default: delete after success).',
        '--pdf'  => 'Use this PDF file path for policy_pdf upload instead of a generated sample.',
    ];

    /** @var list<array{module: string, subFolder: string, fileName: string}> */
    private array $uploadedObjects = [];

    private string $tempDir = '';

    public function run(array $params)
    {
        helper('storage_helper');

        $config = config('Storage');
        $this->printEnvironment($config);

        $this->tempDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'nhance_storage_upload_test_' . uniqid('', true);
        if (! mkdir($this->tempDir, 0777, true) && ! is_dir($this->tempDir)) {
            CLI::error('Unable to create temp directory: ' . $this->tempDir);

            return EXIT_ERROR;
        }

        $keepFiles = (bool) CLI::getOption('keep');
        $failed    = 0;
        $passed    = 0;

        CLI::newLine();
        CLI::write('Starting live storage upload scenarios...', 'yellow');
        CLI::newLine();

        $scenarios = [
            'policy_pdf' => fn () => $this->scenarioPolicyPdf(),
            'policy_payment_receipt' => fn () => $this->scenarioPolicyReceipt(),
            'policy_md' => fn () => $this->scenarioPolicyMarkdown(),
            'endorsement_original' => fn () => $this->scenarioEndorsementOriginal(),
            'endorsement_completion' => fn () => $this->scenarioEndorsementCompletion(),
            'agent_certificate' => fn () => $this->scenarioGeneric('agent', 'certificate_file', 'certificate.pdf'),
            'agent_incentive' => fn () => $this->scenarioGeneric('agent', 'incentive_file', 'incentive.xlsx'),
            'enquiry_id_proof' => fn () => $this->scenarioGeneric('enquiry', 'id_proof', 'id_proof.pdf'),
            'enquiry_rc' => fn () => $this->scenarioGeneric('enquiry', 'rc', 'rc.pdf'),
            'enquiry_previous_policy' => fn () => $this->scenarioGeneric('enquiry', 'previous_policy', 'previous_policy.pdf'),
            'enquiry_root' => fn () => $this->scenarioGeneric('enquiry', '', 'enquiry_extra.pdf'),
            'quotation' => fn () => $this->scenarioGeneric('quotation', '', 'quotation_extra.pdf'),
            'claims' => fn () => $this->scenarioGeneric('claims', '', 'claim_file.pdf'),
        ];

        foreach ($scenarios as $label => $callback) {
            try {
                $callback();
                CLI::write('[PASS] ' . $label, 'green');
                $passed++;
            } catch (\Throwable $e) {
                CLI::error('[FAIL] ' . $label . ' -> ' . $e->getMessage());
                $failed++;
            }

            CLI::newLine();
        }

        if (! $keepFiles && $failed === 0) {
            $this->cleanupUploadedObjects();
            CLI::write('Cleaned up uploaded test files from storage.', 'dark_gray');
        } elseif ($keepFiles) {
            CLI::write('Uploaded test files were kept (--keep).', 'yellow');
            foreach ($this->uploadedObjects as $object) {
                CLI::write(
                    '  - ' . storage()->resolveKey($object['module'], $object['subFolder'], $object['fileName']),
                    'white'
                );
            }
        }

        $this->cleanupTempDir();

        CLI::newLine();
        CLI::write("Summary: {$passed} passed, {$failed} failed", $failed === 0 ? 'green' : 'red');

        return $failed === 0 ? EXIT_SUCCESS : EXIT_ERROR;
    }

    private function printEnvironment(Storage $config): void
    {
        CLI::write('Storage upload test', 'green');
        CLI::write('Driver: ' . ($config->driver === 's3' ? 's3' : 'local'), 'cyan');

        if ($config->driver === 's3') {
            CLI::write('Bucket: ' . ($config->s3Bucket ?: '(empty)'), 'cyan');
            CLI::write('Region: ' . $config->s3Region, 'cyan');
            CLI::write('Prefix: ' . ($config->s3Prefix !== '' ? $config->s3Prefix : '(none)'), 'cyan');
        } else {
            CLI::write('Local root: ' . $config->localRoot, 'cyan');
        }
    }

    private function scenarioPolicyPdf(): void
    {
        $customPdf = CLI::getOption('pdf');
        $source    = is_string($customPdf) && $customPdf !== '' && is_file($customPdf)
            ? $customPdf
            : $this->createSamplePdf();

        $fileName = time() . '_spark_test_policy.pdf';
        $content  = file_get_contents($source);

        if ($content === false || $content === '') {
            throw new FileStorageException('Sample PDF is empty or unreadable');
        }

        $this->uploadFile('policy', 'policy_pdf', $fileName, $source, $content);
        $this->assertReadable('policy', 'policy_pdf', $fileName, $content);
        $this->assertLocalProcessingPath('policy', 'policy_pdf', $fileName);
        $this->assertTemporaryUrl('policy', 'policy_pdf', $fileName);
    }

    private function scenarioPolicyReceipt(): void
    {
        $source   = $this->createTempFile('sample_receipt.jpg', 'JPEG receipt test content for spark upload-test');
        $fileName = time() . '_spark_test_receipt.jpg';
        $content  = file_get_contents($source);

        $this->uploadFile('policy', 'policy_payment_receipt', $fileName, $source, $content);
        $this->assertReadable('policy', 'policy_payment_receipt', $fileName, $content);
        $this->assertTemporaryUrl('policy', 'policy_payment_receipt', $fileName);
    }

    private function scenarioPolicyMarkdown(): void
    {
        $fileName = time() . '_spark_test_policy.md';
        $content  = "# Spark Storage Test\n\n" . str_repeat('Policy markdown body. ', 40);

        storage_put('policy', 'policy_md', $fileName, $content, ['content_type' => 'text/markdown']);
        $this->trackUploaded('policy', 'policy_md', $fileName);

        if (! policy_md_exists($fileName)) {
            throw new FileStorageException('policy_md file not found after upload');
        }

        if (policy_read_md($fileName) !== $content) {
            throw new FileStorageException('policy_md content mismatch after read');
        }
    }

    private function scenarioEndorsementOriginal(): void
    {
        $source   = $this->createTempFile('endorsement_original.pdf', '%PDF-1.4 endorsement original test');
        $fileName = time() . '_spark_test_endorsement.pdf';
        $content  = file_get_contents($source);

        $this->uploadFile('endorsement', '', $fileName, $source, $content);
        $this->assertReadable('endorsement', '', $fileName, $content);
        $this->assertTemporaryUrl('endorsement', '', $fileName);
    }

    private function scenarioEndorsementCompletion(): void
    {
        $source   = $this->createTempFile('endorsement_completion.pdf', '%PDF-1.4 endorsement completion test');
        $fileName = time() . '_spark_test_completion.pdf';
        $content  = file_get_contents($source);

        $this->uploadFile('endorsement', 'endorsement_pdf', $fileName, $source, $content);
        $this->assertReadable('endorsement', 'endorsement_pdf', $fileName, $content);
        $this->assertTemporaryUrl('endorsement', 'endorsement_pdf', $fileName);
    }

    private function scenarioGeneric(string $module, string $subFolder, string $sampleName): void
    {
        $source   = $this->createTempFile($sampleName, '%PDF-1.4 storage module test for ' . $module);
        $fileName = time() . '_spark_test_' . preg_replace('/[^a-z0-9]+/i', '_', $sampleName);
        $content  = file_get_contents($source);

        $this->uploadFile($module, $subFolder, $fileName, $source, $content);
        $this->assertReadable($module, $subFolder, $fileName, $content);
        $this->assertTemporaryUrl($module, $subFolder, $fileName);
    }

    /**
     * @param string|false $content
     */
    private function uploadFile(
        string $module,
        string $subFolder,
        string $fileName,
        string $sourcePath,
        $content
    ): void {
        storage()->upload($sourcePath, storage()->resolveKey($module, $subFolder, $fileName));
        $this->trackUploaded($module, $subFolder, $fileName);

        if (! storage_exists($module, $subFolder, $fileName)) {
            throw new FileStorageException('File not found immediately after upload');
        }

        if ($content !== false && storage_read($module, $subFolder, $fileName) !== $content) {
            throw new FileStorageException('Uploaded file content mismatch on read-back');
        }

        CLI::write('  uploaded: ' . storage()->resolveKey($module, $subFolder, $fileName), 'white');
    }

    /**
     * @param string|false $expectedContent
     */
    private function assertReadable(string $module, string $subFolder, string $fileName, $expectedContent): void
    {
        if (! storage_exists($module, $subFolder, $fileName)) {
            throw new FileStorageException('exists() returned false');
        }

        $readBack = storage_read($module, $subFolder, $fileName);

        if ($expectedContent !== false && $readBack !== $expectedContent) {
            throw new FileStorageException('read() content mismatch');
        }

        CLI::write('  read-back: OK (' . strlen($readBack) . ' bytes)', 'white');
    }

    private function assertLocalProcessingPath(string $module, string $subFolder, string $fileName): void
    {
        $path = storage_local_path($module, $subFolder, $fileName);

        try {
            if (! is_file($path)) {
                throw new FileStorageException('Local processing path not readable: ' . $path);
            }

            CLI::write('  local path: ' . $path, 'white');
        } finally {
            storage_cleanup_temp($path);
        }
    }

    private function assertTemporaryUrl(string $module, string $subFolder, string $fileName): void
    {
        $url = storage_temporary_url($module, $subFolder, $fileName);

        if ($url === '') {
            throw new FileStorageException('temporary URL is empty');
        }

        CLI::write('  url: ' . $url, 'white');
    }

    private function trackUploaded(string $module, string $subFolder, string $fileName): void
    {
        $this->uploadedObjects[] = [
            'module'    => $module,
            'subFolder' => $subFolder,
            'fileName'  => $fileName,
        ];
    }

    private function cleanupUploadedObjects(): void
    {
        foreach ($this->uploadedObjects as $object) {
            try {
                storage_delete($object['module'], $object['subFolder'], $object['fileName']);
            } catch (\Throwable $e) {
                CLI::write(
                    '  cleanup warning: ' . $object['fileName'] . ' -> ' . $e->getMessage(),
                    'yellow'
                );
            }
        }
    }

    private function cleanupTempDir(): void
    {
        if ($this->tempDir === '' || ! is_dir($this->tempDir)) {
            return;
        }

        foreach (scandir($this->tempDir) ?: [] as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            @unlink($this->tempDir . DIRECTORY_SEPARATOR . $item);
        }

        @rmdir($this->tempDir);
    }

    private function createSamplePdf(): string
    {
        return $this->createTempFile(
            'sample_policy.pdf',
            "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\nxref\n0 3\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \ntrailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n110\n%%EOF\n"
        );
    }

    private function createTempFile(string $name, string $contents): string
    {
        $path = $this->tempDir . DIRECTORY_SEPARATOR . $name;

        if (file_put_contents($path, $contents) === false) {
            throw new FileStorageException('Failed to create temp file: ' . $path);
        }

        return $path;
    }
}
