<?php

namespace App\Console\Commands;

use App\Master\SiteManager;
use App\Repositories\SiteExportRepository;
use App\Services\SiteDownloadService;
use App\Tier;
use Illuminate\Console\Command;
use Mtc\MercuryDataModels\Tenant;

class DownloadSiteCommand extends Command
{
    protected $signature = 'download:site
        {--environment= : Environment to connect to (production/staging/custom)}
        {--url= : Custom URL for the environment}
        {--token= : Access token generated in the remote environment\'s Content Sync section}
        {--email= : User email associated with the token}
        {--tenant= : Remote tenant ID to download from}
        {--local-tenant= : Local tenant ID to import into (for update mode)}
        {--site-name= : Name for new site (for new mode)}
        {--full : Full sync (all modules)}
        {--modules=* : Modules for a partial sync (content, media, settings, dealerships, vehicles, offers, car-config, forms, customers)}
        {--new : Create new site (default is update existing)}
        {--files : Also download media files (binary assets) without prompting}';

    protected $description = 'Download a complete site from a remote environment to local';

    /**
     * Known environments mapped to their services.sync config URL keys.
     */
    private const ENVIRONMENTS = [
        'production' => 'services.sync.prod_url',
        'staging' => 'services.sync.vps_url',
    ];

    public function handle(SiteDownloadService $service, SiteManager $siteManager): int
    {

        // Get configuration from options or prompts
        $config = $this->getConfiguration();

        if (!$config) {
            return self::FAILURE;
        }

        // Configure the service
        $service->setConfig(
            $config['url'],
            $config['token'],
            $config['email'],
            $config['remote_tenant']
        );

        // Test connection
        $this->info('Testing connection...');
        $result = $service->testConnection();

        if (!$result['success']) {
            $this->error($result['message']);
            return self::FAILURE;
        }

        $this->info('Connection successful!');
        $this->newLine();

        // Get table list
        $this->info('Fetching table list...');

        try {
            $tables = $service->getTableList($config['modules']);
        } catch (\Exception $e) {
            $this->error('Failed to fetch table list: ' . $e->getMessage());
            return self::FAILURE;
        }

        if (empty($tables)) {
            $this->warn('No tables found to download.');
            return self::SUCCESS;
        }

        // Display tables to be downloaded
        $this->info('Tables to download:');
        $totalRecords = 0;
        foreach ($tables as $table) {
            $redacted = !empty($table['redacted']) ? ' [PII redacted]' : '';
            $this->line("  - {$table['name']} ({$table['count']} records){$redacted}");
            $totalRecords += $table['count'];
        }
        $this->newLine();
        $this->info("Total: " . count($tables) . " tables, {$totalRecords} records");
        $this->newLine();

        // Confirm before proceeding - only updates overwrite existing data,
        // new sites only lose their seed rows
        if (
            !$config['is_new']
            && !$this->confirm(
                "This will TRUNCATE the matching tables in local tenant '{$config['local_tenant']}' "
                . 'before importing, replacing their current data. Continue?',
                false
            )
        ) {
            $this->info('Operation cancelled.');
            return self::SUCCESS;
        }

        // Create or initialize tenant
        if ($config['is_new']) {
            $this->info("Creating new site: {$config['site_name']}...");
            $tenant = $siteManager->addSiteRecord($config['site_name'], '', Tier::ENTERPRISE->value);
            $this->info("Created tenant: {$tenant->id}");
        } else {
            $tenant = Tenant::find($config['local_tenant']);
            if (!$tenant) {
                $this->error("Local tenant not found: {$config['local_tenant']}");
                return self::FAILURE;
            }
        }

        // Initialize tenancy to switch to tenant database
        $this->info("Initializing tenancy for: {$tenant->name}...");
        tenancy()->initialize($tenant);
        $this->newLine();

        // Begin import process
        $this->info('Starting import...');
        $this->newLine();

        $importedTables = 0;
        $importedRecords = 0;
        $errors = [];

        foreach ($tables as $tableInfo) {
            $table = $tableInfo['name'];
            $recordCount = $tableInfo['count'];

            $this->info("Importing {$table}...");

            try {
                // Always truncate table first (new sites have seeded data)
                $service->truncateTable($table);

                if ($recordCount === 0) {
                    $this->line("  Skipped (empty table)");
                    continue;
                }

                // Calculate pages
                $perPage = 100;
                $totalPages = (int) ceil($recordCount / $perPage);

                // Create progress bar
                $progressBar = $this->output->createProgressBar($recordCount);
                $progressBar->start();

                $tableImported = 0;

                // Fetch and import each page
                for ($page = 1; $page <= $totalPages; $page++) {
                    $response = $service->fetchTableData($table, $page);

                    $records = $response['data'] ?? [];

                    if (!empty($records)) {
                        $imported = $service->importTable($table, $records);
                        $tableImported += $imported;
                        $progressBar->advance(count($records));
                    }
                }

                $progressBar->finish();
                $this->newLine();
                $this->line("  Imported {$tableImported} records");

                $importedTables++;
                $importedRecords += $tableImported;

            } catch (\Exception $e) {
                $this->newLine();
                $this->error("  Error: " . $e->getMessage());
                $errors[] = [
                    'table' => $table,
                    'error' => $e->getMessage(),
                ];
            }
        }

        // Summary
        $this->newLine();
        $this->info('==================');
        $this->info('Import Complete!');
        $this->info('==================');
        $this->info("Tables imported: {$importedTables}/" . count($tables));
        $this->info("Records imported: {$importedRecords}");

        // Optionally mirror the media files themselves. The table import only
        // copies media records; the files they reference are transferred here.
        $this->downloadFiles($service);

        if (!empty($errors)) {
            $this->newLine();
            $this->warn('Errors occurred:');
            foreach ($errors as $error) {
                $this->error("  - {$error['table']}: {$error['error']}");
            }
            return self::FAILURE;
        }

        return self::SUCCESS;
    }

    /**
     * Optionally download the remote tenant's media files to the local media
     * disk. Defaults to no: only runs when --files is passed or the user
     * confirms the prompt. Files keep their original (remote-prefixed) paths
     * so they line up with the media rows imported above.
     */
    private function downloadFiles(SiteDownloadService $service): void
    {
        $wantsFiles = $this->option('files');

        if (!$wantsFiles) {
            if ($this->option('no-interaction')) {
                return;
            }

            $this->newLine();
            $wantsFiles = $this->confirm(
                'Also download media files (images, documents) from remote to local? '
                . 'This can transfer a large amount of data.',
                false
            );
        }

        if (!$wantsFiles) {
            return;
        }

        $this->newLine();
        $this->info('Fetching file list...');

        try {
            $files = $service->getFileList();
        } catch (\Exception $e) {
            $this->error('Failed to fetch file list: ' . $e->getMessage());
            return;
        }

        if (empty($files)) {
            $this->warn('No media files found to download.');
            return;
        }

        $totalBytes = array_sum(array_column($files, 'size'));
        $this->info(count($files) . ' files (' . $this->formatBytes($totalBytes) . ') to download.');
        $this->newLine();

        $progressBar = $this->output->createProgressBar(count($files));
        $progressBar->start();

        $downloaded = 0;
        $failed = [];

        foreach ($files as $file) {
            try {
                $service->importFile($file['path'], $service->downloadFile($file));
                $downloaded++;
            } catch (\Exception $e) {
                $failed[] = ['path' => $file['path'], 'error' => $e->getMessage()];
            }

            $progressBar->advance();
        }

        $progressBar->finish();
        $this->newLine(2);
        $this->info("Files downloaded: {$downloaded}/" . count($files));

        if (!empty($failed)) {
            $this->warn('Some files failed to download:');
            foreach ($failed as $file) {
                $this->error("  - {$file['path']}: {$file['error']}");
            }
        }
    }

    /**
     * Human-readable byte size for the download summary.
     */
    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $size = (float) $bytes;
        $unit = 0;

        while ($size >= 1024 && $unit < count($units) - 1) {
            $size /= 1024;
            $unit++;
        }

        return round($size, 2) . ' ' . $units[$unit];
    }

    /**
     * Get configuration from options or interactive prompts
     */
    private function getConfiguration(): ?array
    {
        // Environment selection
        $env = $this->option('environment');
        $url = $this->option('url');

        if (!$env && !$url) {
            $this->hint('production and staging connect to their preconfigured URLs; '
                . 'choose custom to enter a URL manually (e.g. another VPS or a colleague\'s environment).');
            $env = $this->choice(
                'Which environment to connect to?',
                ['production', 'staging', 'custom'],
                0
            );
        }

        if ($env === 'custom' || (!$env && $url)) {
            if (!$url) {
                $this->hint('Full base URL of the remote admin API, including scheme and no trailing path, '
                    . 'e.g. https://my.example.co.uk');
                $url = $this->ask('Enter the custom endpoint URL');
            }
            if (!$url) {
                $this->error('URL is required for custom environment');
                return null;
            }
        } else {
            $url = isset(self::ENVIRONMENTS[$env]) ? config(self::ENVIRONMENTS[$env]) : null;
            if (!$url) {
                $this->error("Unknown environment: {$env}");
                return null;
            }
        }

        // Token
        $token = $this->option('token');
        if (!$token) {
            $this->hint('Generate this on the remote environment you selected above, not locally: '
                . 'sign in there and open the Content Sync section to create an access token.');
            $this->hint('It is tied to your remote user account (the email below) and is also used to decrypt '
                . 'protected settings, so copy it exactly.');
            $token = $this->secret('Enter the access token');
        }

        if (!$token) {
            $this->error('Access token is required');
            return null;
        }

        // Email
        $email = $this->option('email');
        if (!$email) {
            $this->hint('The email of the user account on the remote environment that generated the token above; '
                . 'it must match or authentication will fail.');
            $email = $this->ask('Enter your email address');
        }

        if (!$email) {
            $this->error('Email is required');
            return null;
        }

        // Remote Tenant ID (to download from)
        $remoteTenant = $this->option('tenant');
        if (!$remoteTenant) {
            $this->hint('The site (tenant) identifier on the remote environment to copy from, '
                . 'e.g. acme_cars_Ab12Cd, not a local tenant ID.');
            $remoteTenant = $this->ask('Enter the remote tenant ID to download from');
        }

        if (!$remoteTenant) {
            $this->error('Remote tenant ID is required');
            return null;
        }

        // New or update
        $isNew = $this->option('new');
        if (!$isNew && !$this->option('no-interaction')) {
            $this->hint('update: import into an existing local tenant, truncating its matching tables first. '
                . 'new: create a fresh local site to import into.');
            $mode = $this->choice(
                'Create new site or update existing?',
                ['update', 'new'],
                0
            );
            $isNew = $mode === 'new';
        }

        // Get local tenant or site name based on mode
        $localTenant = null;
        $siteName = null;

        if ($isNew) {
            $siteName = $this->option('site-name');
            if (!$siteName) {
                $this->hint('Display name for the new local site; a new local tenant is created automatically for it.');
                $siteName = $this->ask('Enter the name for the new site');
            }
            if (!$siteName) {
                $this->error('Site name is required for new sites');
                return null;
            }
        } else {
            $localTenant = $this->option('local-tenant');
            if (!$localTenant) {
                $this->hint('The existing local tenant to import into; its matching tables are truncated first. '
                    . 'List local IDs with: php artisan tenants:list');
                $localTenant = $this->ask('Enter the local tenant ID to import into');
            }
            if (!$localTenant) {
                $this->error('Local tenant ID is required for updates');
                return null;
            }
        }

        // Full or partial sync
        $modules = $this->getModuleSelection();

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

        // Show configuration summary
        $this->newLine();
        $this->info('Configuration:');
        $this->line("  Environment: {$url}");
        $this->line("  Remote Tenant: {$remoteTenant}");
        if ($isNew) {
            $this->line("  Mode: Create new site '{$siteName}'");
        } else {
            $this->line("  Mode: Update existing tenant '{$localTenant}'");
        }
        $this->line("  Sync: " . (empty($modules) ? 'full (all modules)' : 'partial (' . implode(', ', $modules) . ')'));
        $this->newLine();

        if (!$this->confirm('Proceed with this configuration?', true)) {
            return null;
        }

        return [
            'url' => $url,
            'token' => $token,
            'email' => $email,
            'remote_tenant' => $remoteTenant,
            'local_tenant' => $localTenant,
            'site_name' => $siteName,
            'is_new' => $isNew,
            'modules' => $modules,
        ];
    }

    /**
     * Resolve the module selection for the sync. Returns [] for a full sync,
     * a list of module keys for a partial sync, or null when input is invalid.
     *
     * @return string[]|null
     */
    private function getModuleSelection(): ?array
    {
        $available = array_keys(SiteExportRepository::MODULES);
        $modules = array_values(array_filter((array) $this->option('modules')));

        $unknown = array_diff($modules, $available);
        if (!empty($unknown)) {
            $this->error('Unknown modules: ' . implode(', ', $unknown));
            $this->line('Available modules: ' . implode(', ', $available));
            return null;
        }

        if ($this->option('full') || !empty($modules) || $this->option('no-interaction')) {
            return $this->option('full') ? [] : $modules;
        }

        $this->hint('full copies every module listed below; partial lets you pick specific modules next.');
        $mode = $this->choice('Full or partial sync?', ['full', 'partial'], 0);

        if ($mode === 'full') {
            return [];
        }

        $choices = [];
        foreach (SiteExportRepository::MODULES as $key => $module) {
            $choices[$key] = $module['label'];
        }

        $this->newLine();
        $this->info('Available modules:');
        foreach ($choices as $key => $label) {
            $this->line("  {$key} - {$label}");
        }

        $selected = $this->choice(
            'Which modules to sync? (comma-separated for multiple)',
            array_keys($choices),
            null,
            null,
            true
        );

        return array_values(array_unique((array) $selected));
    }

    /**
     * Print a muted context line to sit directly above an interactive prompt.
     */
    private function hint(string $message): void
    {
        $this->line("<fg=gray>  {$message}</>");
    }
}
