<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\ArtworkAward;
use App\Services\ArtworkAwardService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

/**
 * Migrates legacy `users_opinions` (projekti_old_skinbase) into `artwork_medals`.
 *
 * Score mapping  (legacy score → new medal):
 *   5 → gold
 *   4 → gold
 *   3 → silver
 *   2 → silver
 *   1 → bronze
 *   0 → bronze
 *
 * Usage:
 *   php artisan awards:import-legacy
 *   php artisan awards:import-legacy --dry-run
 *   php artisan awards:import-legacy --chunk=500
 *   php artisan awards:import-legacy --skip-stats   (skip final stats recalc)
 */
class ImportLegacyAwards extends Command
{
    protected $signature = 'awards:import-legacy
                            {--connection=legacy : Legacy database connection name}
                            {--artwork-id=*      : Restrict import to one or more artwork IDs}
                            {--show-duplicates   : Output skipped duplicate artwork/user pairs at the end}
                            {--duplicates-limit=100 : Maximum duplicate rows to print when --show-duplicates is used}
                            {--dry-run         : Preview only — no writes to DB}
                            {--chunk=250       : Rows to process per batch}
                            {--skip-stats      : Skip per-artwork stats recalculation at the end}
                            {--force           : Overwrite existing awards instead of skipping duplicates}';

    protected $description = 'Import legacy users_opinions into artwork_medals';

    /** Maps legacy score value → medal string */
    private const SCORE_MAP = [
        0 => 'bronze',
        1 => 'bronze',
        2 => 'silver',
        3 => 'silver',
        4 => 'gold',
        5 => 'gold',
    ];

    public function handle(ArtworkAwardService $service): int
    {
        $legacyConnection = (string) $this->option('connection');
        $artworkIds = collect((array) $this->option('artwork-id'))
            ->map(static fn (mixed $value): int => (int) $value)
            ->filter(static fn (int $value): bool => $value > 0)
            ->unique()
            ->values()
            ->all();
        $showDuplicates = (bool) $this->option('show-duplicates');
        $duplicatesLimit = max(1, (int) $this->option('duplicates-limit'));
        $dryRun    = (bool) $this->option('dry-run');
        $chunk     = max(1, (int) $this->option('chunk'));
        $skipStats = (bool) $this->option('skip-stats');
        $force     = (bool) $this->option('force');

        if ($dryRun) {
            $this->warn('[DRY-RUN] No data will be written.');
        }

        // Verify legacy connection is reachable
        try {
            DB::connection($legacyConnection)->getPdo();
        } catch (\Throwable $e) {
            $this->error("Cannot connect to legacy database connection [{$legacyConnection}]: " . $e->getMessage());
            return self::FAILURE;
        }

        if (! DB::connection($legacyConnection)->getSchemaBuilder()->hasTable('users_opinions')) {
            $this->error("Legacy table `users_opinions` not found on connection [{$legacyConnection}].");
            return self::FAILURE;
        }

        $legacyQuery = DB::connection($legacyConnection)->table('users_opinions');

        if ($artworkIds !== []) {
            $legacyQuery->whereIn('artwork_id', $artworkIds);
            $this->info('Restricting import to artwork IDs: ' . implode(', ', $artworkIds));
        }

        // Pre-load sets of valid artwork IDs and user IDs from the new DB
        $this->info('Loading new-DB artwork and user ID sets…');
        $validArtworkIds = DB::table('artworks')
            ->whereNull('deleted_at')
            ->pluck('id')
            ->flip()   // flip so we can use isset() for O(1) lookup
            ->all();

        $validUserIds = DB::table('users')
            ->whereNull('deleted_at')
            ->pluck('id')
            ->flip()
            ->all();

        $this->info(sprintf(
            'Found %d artworks and %d users in new DB.',
            count($validArtworkIds),
            count($validUserIds)
        ));

        // Count legacy rows for progress bar
        $total = (clone $legacyQuery)->count();

        $this->info("Legacy rows to process: {$total}");

        if ($total === 0) {
            $this->warn('No legacy rows found. Nothing to do.');
            return self::SUCCESS;
        }

        $stats = [
            'imported'         => 0,
            'skipped_score'    => 0,
            'skipped_artwork'  => 0,
            'skipped_user'     => 0,
            'skipped_duplicate'=> 0,
            'reported_duplicate'=> 0,
            'updated_force'    => 0,
            'errors'           => 0,
        ];

        $affectedArtworkIds = [];
        $duplicateRows = [];

        $bar = $this->output->createProgressBar($total);
        $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% | imported: %imported% | skipped: %skipped%');
        $bar->setMessage('0', 'imported');
        $bar->setMessage('0', 'skipped');
        $bar->start();

        $legacyQuery
            ->orderBy('opinion_id')
            ->chunk($chunk, function ($rows) use (
                &$stats,
                &$affectedArtworkIds,
                &$duplicateRows,
                $validArtworkIds,
                $validUserIds,
                $dryRun,
                $force,
                $showDuplicates,
                $duplicatesLimit,
                $bar
            ) {
                $inserts = [];
                $now     = now();

                foreach ($rows as $row) {
                    // Legacy users_opinions semantics:
                    // - artwork_id = the artwork being scored
                    // - author_id  = the artwork owner / author
                    // - user_id    = the voter who gave the score
                    $artworkId = (int) $row->artwork_id;
                    $userId    = (int) $row->user_id;
                    $score     = (int) $row->score;
                    $postedAt  = $row->post_date ?? $now;

                    // --- score → medal ---
                    $medal = self::SCORE_MAP[$score] ?? null;
                    if ($medal === null) {
                        $stats['skipped_score']++;
                        $bar->advance();
                        continue;
                    }

                    // --- Artwork must exist in new DB ---
                    if (! isset($validArtworkIds[$artworkId])) {
                        $stats['skipped_artwork']++;
                        $bar->advance();
                        continue;
                    }

                    // --- User must exist in new DB ---
                    if (! isset($validUserIds[$userId])) {
                        $stats['skipped_user']++;
                        $bar->advance();
                        continue;
                    }

                    if (! $dryRun) {
                        if ($force) {
                            // Upsert: update medal if row already exists
                            $affected = DB::table('artwork_medals')
                                ->where('artwork_id', $artworkId)
                                ->where('user_id', $userId)
                                ->update([
                                    'medal_type' => $medal,
                                    'weight'     => ArtworkAward::WEIGHTS[$medal],
                                    'updated_at' => $now,
                                ]);

                            if ($affected > 0) {
                                $stats['updated_force']++;
                                $affectedArtworkIds[$artworkId] = true;
                                $bar->advance();
                                continue;
                            }
                        } else {
                            // Skip if already exists
                            $existingMedal = DB::table('artwork_medals')
                                ->where('artwork_id', $artworkId)
                                ->where('user_id', $userId)
                                ->value('medal_type');

                            if ($existingMedal !== null) {
                                $stats['skipped_duplicate']++;

                                if ($showDuplicates && count($duplicateRows) < $duplicatesLimit) {
                                    $duplicateRows[] = [
                                        'opinion_id' => (int) ($row->opinion_id ?? 0),
                                        'artwork_id' => $artworkId,
                                        'user_id' => $userId,
                                        'legacy_score' => $score,
                                        'legacy_medal' => $medal,
                                        'existing_medal' => (string) $existingMedal,
                                    ];
                                    $stats['reported_duplicate']++;
                                }

                                $bar->advance();
                                continue;
                            }
                        }

                        $inserts[] = [
                            'artwork_id' => $artworkId,
                            'user_id'    => $userId,
                            'medal_type' => $medal,
                            'weight'     => ArtworkAward::WEIGHTS[$medal],
                            'created_at' => $postedAt,
                            'updated_at' => $postedAt,
                        ];

                        $affectedArtworkIds[$artworkId] = true;
                    }

                    $stats['imported']++;
                    $bar->advance();
                }

                // Bulk insert the batch (DB::table bypasses the observer intentionally;
                // stats are recalculated in bulk at the end for performance)
                if (! $dryRun && ! empty($inserts)) {
                    try {
                        DB::table('artwork_medals')->insert($inserts);
                    } catch (\Throwable $e) {
                        // Fallback: insert one-by-one to isolate constraint violations
                        foreach ($inserts as $row) {
                            try {
                                DB::table('artwork_medals')->insertOrIgnore([$row]);
                            } catch (\Throwable) {
                                $stats['errors']++;
                            }
                        }
                    }
                }

                $skippedTotal = $stats['skipped_score']
                    + $stats['skipped_artwork']
                    + $stats['skipped_user']
                    + $stats['skipped_duplicate'];

                $bar->setMessage((string) $stats['imported'], 'imported');
                $bar->setMessage((string) $skippedTotal, 'skipped');
            });

        $bar->finish();
        $this->newLine(2);

        // -------------------------------------------------------------------------
        // Recalculate stats for every affected artwork
        // -------------------------------------------------------------------------
        if (! $dryRun && ! $skipStats && ! empty($affectedArtworkIds)) {
            $artworkCount = count($affectedArtworkIds);
            $this->info("Recalculating award stats for {$artworkCount} artworks…");

            $statsBar = $this->output->createProgressBar($artworkCount);
            $statsBar->start();

            foreach (array_keys($affectedArtworkIds) as $artworkId) {
                try {
                    $service->recalcStats($artworkId);
                } catch (\Throwable $e) {
                    $this->newLine();
                    $this->warn("Stats recalc failed for artwork #{$artworkId}: {$e->getMessage()}");
                }
                $statsBar->advance();
            }

            $statsBar->finish();
            $this->newLine(2);
        }

        // -------------------------------------------------------------------------
        // Summary
        // -------------------------------------------------------------------------
        $this->table(
            ['Result', 'Count'],
            [
                ['Imported (new rows)',     $stats['imported']],
                ['Forced updates',          $stats['updated_force']],
                ['Skipped – bad score',     $stats['skipped_score']],
                ['Skipped – artwork gone',  $stats['skipped_artwork']],
                ['Skipped – user gone',     $stats['skipped_user']],
                ['Skipped – duplicate',     $stats['skipped_duplicate']],
                ['Errors',                  $stats['errors']],
            ]
        );

        if ($showDuplicates && $stats['skipped_duplicate'] > 0) {
            $this->newLine();
            $this->info(sprintf(
                'Duplicate rows skipped: %d. Showing %d row(s)%s.',
                $stats['skipped_duplicate'],
                count($duplicateRows),
                $stats['skipped_duplicate'] > count($duplicateRows) ? " (truncated by --duplicates-limit={$duplicatesLimit})" : ''
            ));

            if ($duplicateRows !== []) {
                $this->table(
                    ['Legacy opinion', 'Artwork ID', 'Voter user_id', 'Legacy score', 'Legacy medal', 'Existing medal'],
                    array_map(static fn (array $row): array => [
                        $row['opinion_id'],
                        $row['artwork_id'],
                        $row['user_id'],
                        $row['legacy_score'],
                        $row['legacy_medal'],
                        $row['existing_medal'],
                    ], $duplicateRows)
                );
            }
        }

        if ($dryRun) {
            $this->warn('[DRY-RUN] Nothing was written. Re-run without --dry-run to apply.');
        } else {
            $this->info('Migration complete.');
        }

        return $stats['errors'] > 0 ? self::FAILURE : self::SUCCESS;
    }
}
