<?php

namespace App\Console\Commands;

use App\Services\Configurator\ConfiguratorCopyReport;
use App\Services\Configurator\ConfiguratorCopyService;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Mtc\MercuryDataModels\Tenant;

/**
 * Superadmin action: copy car configurator models (with imagery) from one site
 * to another. Prompts for the source/destination sites, a make and its models
 * (or all models of the make), then delegates the copy to
 * {@see ConfiguratorCopyService}.
 */
class CopyConfiguratorModelCommand extends Command
{
    protected $signature = 'configurator:copy
        {--source= : Source tenant (site) id to copy from}
        {--destination= : Destination tenant (site) id to copy into}
        {--make= : Make id or name to copy models for}
        {--model=* : Configurator model id(s), or "all" for every model of the make}
        {--overwrite : Replace matching configurator models already in the destination}
        {--dry-run : Report what would be copied without writing anything}';

    protected $description = 'Copy car configurator models (with imagery) from one site to another';

    private const ALL_MODELS = '* ALL models *';

    public function handle(ConfiguratorCopyService $service): int
    {
        $source = $this->resolveTenant('source', 'Select the SOURCE site to copy FROM');
        if (!$source) {
            return self::FAILURE;
        }

        $destination = $this->resolveTenant('destination', 'Select the DESTINATION site to copy INTO', $source);
        if (!$destination) {
            return self::FAILURE;
        }

        $makes = $service->availableMakes($source);
        if ($makes->isEmpty()) {
            $this->error("Source site '{$source->name}' has no configurator models.");
            return self::FAILURE;
        }

        $makeId = $this->resolveMake($makes);
        if ($makeId === null) {
            return self::FAILURE;
        }

        $models = $service->modelsForMake($source, $makeId);
        if ($models->isEmpty()) {
            $this->error('That make has no configurator models in the source site.');
            return self::FAILURE;
        }

        $modelIds = $this->resolveModels($models);
        if ($modelIds === []) {
            return self::FAILURE;
        }

        $overwrite = (bool) $this->option('overwrite');
        $dryRun = (bool) $this->option('dry-run');

        if (!$this->confirmPlan($source, $destination, $modelIds, $overwrite, $dryRun)) {
            $this->info('Operation cancelled.');
            return self::SUCCESS;
        }

        $this->newLine();
        $this->info($dryRun ? 'Dry run - inspecting...' : 'Copying...');
        $report = $service->copy($source, $destination, $modelIds, $overwrite, $dryRun);
        $this->renderReport($report);

        return $report->hasFailures() ? self::FAILURE : self::SUCCESS;
    }

    /**
     * Resolve a tenant from its option or an interactive picker. When $exclude
     * is given, that tenant may not be chosen (source != destination).
     */
    private function resolveTenant(string $option, string $prompt, ?Tenant $exclude = null): ?Tenant
    {
        $id = $this->option($option);

        if (!$id) {
            $tenants = Tenant::query()->orderBy('name')->get()
                ->reject(fn (Tenant $tenant) => $exclude && $tenant->id === $exclude->id);

            if ($tenants->isEmpty()) {
                $this->error('No eligible sites available.');
                return null;
            }

            $options = $tenants->mapWithKeys(fn (Tenant $tenant) => ["{$tenant->name} (#{$tenant->id})" => $tenant->id]);
            $choice = $this->choice($prompt, $options->keys()->all());
            $id = $options[$choice];
        }

        $tenant = Tenant::find($id);
        if (!$tenant) {
            $this->error("Site not found: {$id}");
            return null;
        }

        if ($exclude && $tenant->id === $exclude->id) {
            $this->error('Source and destination sites must be different.');
            return null;
        }

        return $tenant;
    }

    /**
     * Resolve the make id from the option (id or name) or an interactive picker.
     *
     * @param Collection<int, object{make_id: int, name: string}> $makes
     */
    private function resolveMake(Collection $makes): ?int
    {
        $option = $this->option('make');

        if ($option !== null && $option !== '') {
            $match = is_numeric($option)
                ? $makes->firstWhere('make_id', (int) $option)
                : $makes->first(fn ($make) => strcasecmp($make->name, (string) $option) === 0);

            if (!$match) {
                $this->error("Make '{$option}' has no configurator models in the source site.");
                return null;
            }
            return (int) $match->make_id;
        }

        $options = $makes->mapWithKeys(fn ($make) => ["{$make->name} (#{$make->make_id})" => (int) $make->make_id]);
        $choice = $this->choice('Which make?', $options->keys()->all());

        return $options[$choice];
    }

    /**
     * Resolve the configurator model ids from the option ("all" or ids) or an
     * interactive multi-select that includes an "all models" option.
     *
     * @param Collection<int, object{id: int, name: string, slug: ?string, model_name: ?string}> $models
     * @return int[]
     */
    private function resolveModels(Collection $models): array
    {
        $selected = array_values(array_filter((array) $this->option('model'), fn ($v) => $v !== null && $v !== ''));

        if ($selected !== []) {
            if (in_array('all', array_map('strtolower', $selected), true)) {
                return $models->pluck('id')->map(fn ($id) => (int) $id)->all();
            }
            $ids = $models->whereIn('id', array_map('intval', $selected))->pluck('id')->map(fn ($id) => (int) $id)->all();
            if ($ids === []) {
                $this->error('None of the given model ids belong to that make in the source site.');
            }
            return $ids;
        }

        $options = $models->mapWithKeys(fn ($model) => [
            $this->modelLabel($model) => (int) $model->id,
        ]);
        $labels = $options->keys()->prepend(self::ALL_MODELS)->all();

        $chosen = (array) $this->choice(
            'Which model(s)? (comma-separated for multiple)',
            $labels,
            null,
            null,
            true,
        );

        if (in_array(self::ALL_MODELS, $chosen, true)) {
            return $models->pluck('id')->map(fn ($id) => (int) $id)->all();
        }

        return array_values(array_map(fn ($label) => $options[$label], $chosen));
    }

    private function modelLabel(object $model): string
    {
        $modelName = $model->model_name ? " [{$model->model_name}]" : '';
        $slug = $model->slug ? " {$model->slug}" : '';

        return "{$model->name}{$modelName}{$slug} (#{$model->id})";
    }

    private function confirmPlan(
        Tenant $source,
        Tenant $destination,
        array $modelIds,
        bool $overwrite,
        bool $dryRun,
    ): bool {
        $this->newLine();
        $this->info('Plan:');
        $this->line("  From: {$source->name} (#{$source->id})");
        $this->line("  Into: {$destination->name} (#{$destination->id})");
        $this->line('  Models: ' . count($modelIds));
        $this->line('  Existing matches: ' . ($overwrite ? 'REPLACE (--overwrite)' : 'skip'));
        if ($dryRun) {
            $this->line('  Mode: DRY RUN (no changes written)');
        }

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

        $this->newLine();

        return $this->confirm('Proceed?', true);
    }

    private function renderReport(ConfiguratorCopyReport $report): void
    {
        $this->newLine();
        foreach ($report->models() as $model) {
            $suffix = $model['message'] ? " - {$model['message']}" : '';
            $counts = $model['counts'] !== [] ? '  [' . $this->formatCounts($model['counts']) . ']' : '';
            match ($model['status']) {
                ConfiguratorCopyReport::STATUS_COPIED,
                ConfiguratorCopyReport::STATUS_REPLACED => $this->info("  ✓ {$model['status']}: {$model['name']}{$counts}{$suffix}"),
                ConfiguratorCopyReport::STATUS_SKIPPED => $this->warn("  • skipped: {$model['name']}{$suffix}"),
                default => $this->error("  ✗ failed: {$model['name']}{$suffix}"),
            };
        }

        foreach ($report->warnings() as $warning) {
            $this->warn("  ! {$warning}");
        }

        $this->newLine();
        $this->info(sprintf(
            'Done. Copied: %d, replaced: %d, skipped: %d, failed: %d.',
            $report->countByStatus(ConfiguratorCopyReport::STATUS_COPIED),
            $report->countByStatus(ConfiguratorCopyReport::STATUS_REPLACED),
            $report->countByStatus(ConfiguratorCopyReport::STATUS_SKIPPED),
            $report->countByStatus(ConfiguratorCopyReport::STATUS_FAILED),
        ));
    }

    private function formatCounts(array $counts): string
    {
        return collect($counts)
            ->map(fn ($value, $key) => "{$key}: {$value}")
            ->implode(', ');
    }
}
