<?php

namespace App\Console\Commands;

use App\Components\Functions;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class MigrateLegacyRemoteStorage extends Command
{
    protected $signature = 'storage:migrate-legacy-remote
        {--execute : Upload files and update database. Without this option the command only reports what would change}
        {--include-legacy-aws : Also migrate legacy AWS S3 hosts used before MinIO}
        {--hosts= : Comma-separated source hosts to migrate}
        {--limit= : Limit rows per table column}
        {--table= : Only process one table}
        {--column= : Only process one column}
        {--disk=s3 : Target filesystem disk}';

    protected $description = 'Migrate legacy public remote file URLs to the configured S3-compatible disk';

    private $targets = [
        ['table' => 'collect_spend_files', 'key' => 'id', 'columns' => ['file']],
        ['table' => 'hostel_videos', 'key' => 'id', 'columns' => ['path', 'preview']],
        ['table' => 'reserve_files', 'key' => 'id', 'columns' => ['file']],
    ];

    private $digitalOceanHosts = [
        'resident.sgp1.digitaloceanspaces.com',
    ];

    private $legacyAwsHosts = [
        'itro.s3-ap-southeast-1.amazonaws.com',
        's3-ap-southeast-1.amazonaws.com',
    ];

    public function handle()
    {
        $execute = (bool) $this->option('execute');
        $disk = $this->option('disk') ?: 's3';
        $hosts = $this->sourceHosts();
        $limit = $this->option('limit') ? (int) $this->option('limit') : null;

        if (empty($hosts)) {
            $this->error('No source hosts configured.');

            return 1;
        }

        $this->line('Mode: ' . ($execute ? 'EXECUTE' : 'DRY RUN'));
        $this->line('Target disk: ' . $disk);
        $this->line('Source hosts: ' . implode(', ', $hosts));
        $this->line('Target base URL: ' . rtrim(config('filesystems.disks.' . $disk . '.url'), '/'));

        if (!$execute) {
            $this->warn('Dry-run only. Re-run with --execute to upload and update database.');
        }

        $client = new Client([
            'timeout' => 120,
            'connect_timeout' => 15,
            'verify' => false,
            'headers' => [
                'User-Agent' => 'itro-storage-migration/1.0',
            ],
        ]);

        $summary = [
            'found' => 0,
            'uploaded' => 0,
            'already_exists' => 0,
            'updated' => 0,
            'failed' => 0,
            'skipped' => 0,
        ];

        foreach ($this->filteredTargets() as $target) {
            foreach ($target['columns'] as $column) {
                $this->migrateColumn($client, $disk, $target['table'], $target['key'], $column, $hosts, $limit, $execute, $summary);
            }
        }

        $this->line('');
        $this->info('Summary');
        foreach ($summary as $key => $value) {
            $this->line($key . ': ' . $value);
        }

        return $summary['failed'] > 0 ? 1 : 0;
    }

    private function migrateColumn(Client $client, $disk, $table, $primaryKey, $column, array $hosts, $limit, $execute, array &$summary)
    {
        $query = DB::table($table)
            ->select($primaryKey, $column)
            ->whereNotNull($column)
            ->where(function ($query) use ($column, $hosts) {
                foreach ($hosts as $host) {
                    $query->orWhere($column, 'like', '%' . $host . '%');
                }
            })
            ->orderBy($primaryKey, 'asc');

        if ($limit) {
            $query->limit($limit);
        }

        $rows = $query->get();

        if ($rows->isEmpty()) {
            return;
        }

        $this->line('');
        $this->line($table . '.' . $column . ': ' . $rows->count() . ' row(s)');

        foreach ($rows as $row) {
            $sourceUrl = trim($row->{$column});
            $sourceHost = strtolower(parse_url($sourceUrl, PHP_URL_HOST));

            if (!in_array($sourceHost, $hosts)) {
                $summary['skipped']++;
                continue;
            }

            $key = $this->objectKeyFromUrl($sourceUrl, $sourceHost);
            if (empty($key)) {
                $summary['skipped']++;
                $this->warn($table . '#' . $row->{$primaryKey} . ' skipped invalid URL: ' . $sourceUrl);
                continue;
            }

            $targetUrl = Functions::cloudFileUrl($key, $disk);
            $summary['found']++;

            $this->line($table . '#' . $row->{$primaryKey} . ' ' . $column . ' -> ' . $key);

            if (!$execute) {
                continue;
            }

            try {
                if (Storage::disk($disk)->exists($key)) {
                    $summary['already_exists']++;
                } else {
                    $this->uploadUrl($client, $disk, $sourceUrl, $key);
                    $summary['uploaded']++;
                }

                DB::table($table)
                    ->where($primaryKey, $row->{$primaryKey})
                    ->update([$column => $targetUrl]);

                $summary['updated']++;
            } catch (\Throwable $e) {
                $summary['failed']++;
                $this->error($table . '#' . $row->{$primaryKey} . ' failed: ' . $e->getMessage());
            }
        }
    }

    private function uploadUrl(Client $client, $disk, $sourceUrl, $key)
    {
        $response = $client->get($sourceUrl, [
            'stream' => true,
            'http_errors' => false,
        ]);

        $statusCode = $response->getStatusCode();
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new \RuntimeException('Download returned HTTP ' . $statusCode);
        }

        $body = $response->getBody();
        $resource = $body->detach();

        if (is_resource($resource)) {
            try {
                $uploaded = Storage::disk($disk)->put($key, $resource);
            } finally {
                if (is_resource($resource)) {
                    fclose($resource);
                }
            }

            if ($uploaded === false) {
                throw new \RuntimeException('Upload returned false');
            }

            return;
        }

        $uploaded = Storage::disk($disk)->put($key, $body->getContents());

        if ($uploaded === false) {
            throw new \RuntimeException('Upload returned false');
        }
    }

    private function objectKeyFromUrl($url, $host)
    {
        $path = parse_url($url, PHP_URL_PATH);
        if (empty($path)) {
            return null;
        }

        $path = ltrim(rawurldecode($path), '/');

        if ($host === 's3-ap-southeast-1.amazonaws.com') {
            foreach (['itro/', 'resident/', 'ishopgo/'] as $bucketPrefix) {
                if (strpos($path, $bucketPrefix) === 0) {
                    return substr($path, strlen($bucketPrefix));
                }
            }
        }

        return $path;
    }

    private function filteredTargets()
    {
        $table = $this->option('table');
        $column = $this->option('column');
        $targets = $this->targets;

        if ($this->option('include-legacy-aws')) {
            $targets[] = ['table' => 'contract_attachments', 'key' => 'id', 'columns' => ['preview']];
        }

        if (!empty($table)) {
            $targets = array_values(array_filter($targets, function ($target) use ($table) {
                return $target['table'] === $table;
            }));
        }

        if (!empty($column)) {
            foreach ($targets as &$target) {
                $target['columns'] = array_values(array_filter($target['columns'], function ($targetColumn) use ($column) {
                    return $targetColumn === $column;
                }));
            }
            unset($target);
        }

        return array_values(array_filter($targets, function ($target) {
            return !empty($target['columns']);
        }));
    }

    private function sourceHosts()
    {
        $hosts = [];
        $hostOption = trim((string) $this->option('hosts'));

        if (!empty($hostOption)) {
            $hosts = array_map('trim', explode(',', $hostOption));
        } else {
            $hosts = $this->digitalOceanHosts;
            if ($this->option('include-legacy-aws')) {
                $hosts = array_merge($hosts, $this->legacyAwsHosts);
            }
        }

        $hosts = array_filter(array_map('strtolower', $hosts));

        return array_values(array_unique($hosts));
    }
}
