<?php

namespace App\Commands;

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

class StorageMigrateLocalToS3 extends BaseCommand
{
    protected $group = 'Storage';

    protected $name = 'storage:migrate-local-to-s3';

    protected $description = 'Migrate existing local writable/uploads files to S3 (policy, endorsement, agent, enquiry, quotation, claims).';

    protected $usage = 'storage:migrate-local-to-s3 [options]';

    protected $options = [
        '--dry-run'       => 'List files that would be migrated without uploading.',
        '--force'         => 'Re-upload even if the object already exists on S3.',
        '--delete-local'  => 'Delete local file after successful S3 upload + verify.',
        '--verify'        => 'Compare MD5 of local file vs S3 read-back after upload (default: on).',
        '--no-verify'     => 'Skip read-back verification after upload.',
        '--module'        => 'Migrate one or more modules (comma-separated): policy,endorsement,agent,enquiry,quotation,claims',
    ];

    /** @var list<string> */
    private array $allowedModules = ['policy', 'endorsement', 'agent', 'enquiry', 'quotation', 'claims'];

    /** @var array<string, int> */
    private array $stats = [
        'scanned'  => 0,
        'uploaded' => 0,
        'skipped'  => 0,
        'failed'   => 0,
        'deleted'  => 0,
    ];

    public function run(array $params)
    {
        $config = config('Storage');

        if ($config->s3Bucket === '' || $config->s3AccessKey === '' || $config->s3SecretKey === '') {
            CLI::error('S3 is not configured. Set AWS_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY in .env');

            return EXIT_ERROR;
        }

        $dryRun      = (bool) CLI::getOption('dry-run');
        $force       = (bool) CLI::getOption('force');
        $deleteLocal = (bool) CLI::getOption('delete-local');
        $verify      = ! CLI::getOption('no-verify');
        $moduleFilter = CLI::getOption('module');
        // Support: --module "agent, enquiry, quotation, claims"
        // and bare tokens after a partial --module agent, enquiry quotation claims
        if (is_string($moduleFilter) && $moduleFilter !== '') {
            $extraTokens = [];
            foreach ($params as $param) {
                if (! is_string($param) || $param === '' || str_starts_with($param, '-')) {
                    continue;
                }
                $extraTokens[] = $param;
            }
            if ($extraTokens !== []) {
                $moduleFilter = rtrim($moduleFilter, ',') . ',' . implode(',', $extraTokens);
            }
        }

        $modules = $this->parseModuleFilter($moduleFilter);

        if ($modules === null) {
            return EXIT_ERROR;
        }

        try {
            $s3Driver = new S3StorageDriver($config);
        } catch (FileStorageException $e) {
            CLI::error($e->getMessage());

            return EXIT_ERROR;
        }

        $storage = new FileStorageService($config);

        CLI::write('Local to S3 migration', 'green');
        CLI::write('Local root : ' . $config->localRoot, 'cyan');
        CLI::write('S3 bucket  : ' . $config->s3Bucket, 'cyan');
        CLI::write('S3 region  : ' . $config->s3Region, 'cyan');
        CLI::write('S3 prefix  : ' . ($config->s3Prefix !== '' ? $config->s3Prefix : '(none)'), 'cyan');
        CLI::write('Mode       : ' . ($dryRun ? 'DRY RUN' : 'LIVE'), $dryRun ? 'yellow' : 'green');
        if ($modules !== []) {
            CLI::write('Modules    : ' . implode(', ', $modules), 'cyan');
        }
        CLI::newLine();

        $targets = $this->migrationTargets();

        if ($modules !== []) {
            $targets = array_values(array_filter(
                $targets,
                static fn (array $target) => in_array($target['module'], $modules, true)
            ));
        }

        if ($targets === []) {
            CLI::error('No migration targets matched the given --module filter.');

            return EXIT_ERROR;
        }

        foreach ($targets as $target) {
            $this->migrateTarget($target, $s3Driver, $storage, $dryRun, $force, $deleteLocal, $verify);
        }

        CLI::newLine();
        CLI::write('Migration summary', 'yellow');
        CLI::write('  Scanned : ' . $this->stats['scanned'], 'white');
        CLI::write('  Uploaded: ' . $this->stats['uploaded'], 'green');
        CLI::write('  Skipped : ' . $this->stats['skipped'], 'yellow');
        CLI::write('  Failed  : ' . $this->stats['failed'], $this->stats['failed'] > 0 ? 'red' : 'white');
        CLI::write('  Deleted : ' . $this->stats['deleted'], 'white');

        if ($dryRun) {
            CLI::newLine();
            CLI::write('Dry run complete. Re-run without --dry-run to upload.', 'yellow');
        }

        return $this->stats['failed'] === 0 ? EXIT_SUCCESS : EXIT_ERROR;
    }

    /**
     * Parse --module as a single value or comma-separated list (case-insensitive).
     *
     * @return list<string>|null empty list = all modules; null = invalid input
     */
    private function parseModuleFilter(mixed $moduleFilter): ?array
    {
        if (! is_string($moduleFilter) || trim($moduleFilter) === '') {
            return [];
        }

        $requested = array_values(array_filter(array_map(
            static fn (string $value): string => strtolower(trim($value)),
            explode(',', $moduleFilter)
        ), static fn (string $value): bool => $value !== ''));

        if ($requested === []) {
            return [];
        }

        $invalid = array_values(array_diff($requested, $this->allowedModules));
        if ($invalid !== []) {
            CLI::error(
                'Invalid --module value(s): ' . implode(', ', $invalid)
                . '. Allowed: ' . implode(', ', $this->allowedModules)
                . '. Example: --module agent,enquiry,quotation,claims'
            );

            return null;
        }

        return array_values(array_unique($requested));
    }

    /**
     * @return list<array{label: string, module: string, subFolder: string, localRelative: string, topLevelOnly: bool}>
     */
    private function migrationTargets(): array
    {
        return [
            [
                'label'         => 'policy_pdf',
                'module'        => 'policy',
                'subFolder'     => 'policy_pdf',
                'localRelative' => 'uploads/policy/policy_pdf',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'policy_payment_receipt',
                'module'        => 'policy',
                'subFolder'     => 'policy_payment_receipt',
                'localRelative' => 'uploads/policy/policy_payment_receipt',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'policy_md',
                'module'        => 'policy',
                'subFolder'     => 'policy_md',
                'localRelative' => 'uploads/policy/policy_md',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'endorsement_original',
                'module'        => 'endorsement',
                'subFolder'     => '',
                'localRelative' => 'uploads/endorsement',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'endorsement_completion',
                'module'        => 'endorsement',
                'subFolder'     => 'endorsement_pdf',
                'localRelative' => 'uploads/endorsement/endorsement_pdf',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'agent_certificate',
                'module'        => 'agent',
                'subFolder'     => 'certificate_file',
                'localRelative' => 'uploads/agent/certificate_file',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'agent_incentive',
                'module'        => 'agent',
                'subFolder'     => 'incentive_file',
                'localRelative' => 'uploads/agent/incentive_file',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'enquiry_id_proof',
                'module'        => 'enquiry',
                'subFolder'     => 'id_proof',
                'localRelative' => 'uploads/enquiry/id_proof',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'enquiry_rc',
                'module'        => 'enquiry',
                'subFolder'     => 'rc',
                'localRelative' => 'uploads/enquiry/rc',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'enquiry_previous_policy',
                'module'        => 'enquiry',
                'subFolder'     => 'previous_policy',
                'localRelative' => 'uploads/enquiry/previous_policy',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'enquiry_root',
                'module'        => 'enquiry',
                'subFolder'     => '',
                'localRelative' => 'uploads/enquiry',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'quotation',
                'module'        => 'quotation',
                'subFolder'     => '',
                'localRelative' => 'uploads/quotation',
                'topLevelOnly'  => true,
            ],
            [
                'label'         => 'claims',
                'module'        => 'claims',
                'subFolder'     => '',
                'localRelative' => 'uploads/claims',
                'topLevelOnly'  => true,
            ],
        ];
    }

    /**
     * @param array{label: string, module: string, subFolder: string, localRelative: string, topLevelOnly: bool} $target
     */
    private function migrateTarget(
        array $target,
        S3StorageDriver $s3Driver,
        FileStorageService $storage,
        bool $dryRun,
        bool $force,
        bool $deleteLocal,
        bool $verify
    ): void {
        $localDir = rtrim(config('Storage')->localRoot, '/\\')
            . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $target['localRelative']);

        CLI::write('[' . $target['label'] . '] ' . $target['localRelative'], 'yellow');

        if (! is_dir($localDir)) {
            CLI::write('  Directory not found, skipping.', 'dark_gray');
            CLI::newLine();

            return;
        }

        $files = $this->listLocalFiles($localDir, $target['topLevelOnly']);

        if ($files === []) {
            CLI::write('  No files found.', 'dark_gray');
            CLI::newLine();

            return;
        }

        foreach ($files as $localPath) {
            $this->stats['scanned']++;

            $fileName = basename($localPath);
            $key      = $storage->resolveKey($target['module'], $target['subFolder'], $fileName);
            $size     = filesize($localPath) ?: 0;

            try {
                if (! $force && $s3Driver->exists($key)) {
                    $this->stats['skipped']++;
                    CLI::write("  skip (exists on S3): {$key}", 'dark_gray');
                    continue;
                }

                if ($dryRun) {
                    $this->stats['uploaded']++;
                    CLI::write("  would upload: {$key} ({$size} bytes)", 'cyan');
                    continue;
                }

                $s3Driver->upload($localPath, $key);

                if ($verify) {
                    $this->verifyUpload($localPath, $s3Driver, $key);
                }

                $this->stats['uploaded']++;
                CLI::write("  uploaded: {$key} ({$size} bytes)", 'green');

                if ($deleteLocal) {
                    if (@unlink($localPath)) {
                        $this->stats['deleted']++;
                        CLI::write('  deleted local: ' . $localPath, 'dark_gray');
                    } else {
                        throw new FileStorageException('Uploaded to S3 but failed to delete local file: ' . $localPath);
                    }
                }
            } catch (\Throwable $e) {
                $this->stats['failed']++;
                CLI::error("  failed: {$key} -> " . $e->getMessage());
            }
        }

        CLI::newLine();
    }

    /**
     * @return list<string> absolute local file paths
     */
    private function listLocalFiles(string $directory, bool $topLevelOnly): array
    {
        $files = [];

        if ($topLevelOnly) {
            foreach (scandir($directory) ?: [] as $item) {
                if ($item === '.' || $item === '..') {
                    continue;
                }

                $path = $directory . DIRECTORY_SEPARATOR . $item;

                if (! is_file($path)) {
                    continue;
                }

                if ($this->shouldSkipFile($item)) {
                    continue;
                }

                $files[] = $path;
            }

            sort($files);

            return $files;
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS)
        );

        foreach ($iterator as $fileInfo) {
            if (! $fileInfo->isFile()) {
                continue;
            }

            $name = $fileInfo->getFilename();

            if ($this->shouldSkipFile($name)) {
                continue;
            }

            $files[] = $fileInfo->getPathname();
        }

        sort($files);

        return $files;
    }

    private function shouldSkipFile(string $fileName): bool
    {
        return in_array(strtolower($fileName), ['index.html', '.htaccess', '.gitkeep'], true);
    }

    private function verifyUpload(string $localPath, S3StorageDriver $s3Driver, string $key): void
    {
        $localHash = md5_file($localPath);

        if ($localHash === false) {
            throw new FileStorageException('Unable to hash local file: ' . $localPath);
        }

        $remoteContents = $s3Driver->read($key);
        $remoteHash     = md5($remoteContents);

        if ($localHash !== $remoteHash) {
            throw new FileStorageException('Verification failed: S3 content hash mismatch for ' . $key);
        }
    }
}
