<?php

namespace App\Console\Commands;

use App\Services\LegacySmileyMapper;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * php artisan skinbase:migrate-smileys
 *
 * Scans artworks.description, artwork_comments.content, and forum_posts.content,
 * replaces legacy smiley codes (:beer, :lol, etc.) with Unicode emoji.
 *
 * Options:
 *   --dry-run        Show what would change without writing to DB
 *   --chunk=200      Rows processed per batch (default 200)
 *   --table=artworks Limit scan to one table
 */
class MigrateSmileys extends Command
{
    protected $signature = 'skinbase:migrate-smileys
                            {--dry-run : Preview changes without writing to the database}
                            {--chunk=200 : Number of rows to process per batch}
                            {--table= : Limit scan to a single table (artworks|artwork_comments|forum_posts)}';

    protected $description = 'Convert legacy :smiley: codes to Unicode emoji in content fields.';

    /** Tables and their content columns to scan. */
    private const TARGETS = [
        'artworks'         => 'description',
        'artwork_comments' => 'content',
        'forum_posts'      => 'content',
    ];

    public function handle(): int
    {
        $dryRun  = (bool) $this->option('dry-run');
        $chunk   = max(1, (int) $this->option('chunk'));
        $tableOpt = $this->option('table');

        $targets = self::TARGETS;
        if ($tableOpt) {
            if (! isset($targets[$tableOpt])) {
                $this->error("Unknown table: {$tableOpt}. Allowed: " . implode(', ', array_keys($targets)));
                return self::FAILURE;
            }
            $targets = [$tableOpt => $targets[$tableOpt]];
        }

        if ($dryRun) {
            $this->warn('DRY-RUN mode — no changes will be written.');
        }

        $totalChanged = 0;
        $totalRows    = 0;

        foreach ($targets as $table => $column) {
            $this->line("Scanning <info>{$table}.{$column}</info>…");

            [$changed, $rows] = $this->processTable($table, $column, $chunk, $dryRun);

            $totalChanged += $changed;
            $totalRows    += $rows;

            $this->line("  → {$rows} rows scanned, {$changed} updated.");
        }

        $this->newLine();
        $this->info("Summary: {$totalRows} rows scanned, {$totalChanged} rows " . ($dryRun ? 'would be ' : '') . 'updated.');

        return self::SUCCESS;
    }

    private function processTable(
        string $table,
        string $column,
        int    $chunk,
        bool   $dryRun
    ): array {
        $totalChanged = 0;
        $totalRows    = 0;

        DB::table($table)
            ->whereNotNull($column)
            ->orderBy('id')
            ->chunk($chunk, function ($rows) use ($table, $column, $dryRun, &$totalChanged, &$totalRows) {
                foreach ($rows as $row) {
                    $original  = $row->$column ?? '';
                    $converted = LegacySmileyMapper::convert($original);

                    // Collapse emoji flood runs BEFORE size/DB checks so that
                    // rows like ":beer :beer :beer …" (×500) don't exceed MEDIUMTEXT.
                    $collapsed = LegacySmileyMapper::collapseFlood($converted);
                    if ($collapsed !== $converted) {
                        $beforeBytes = mb_strlen($converted, '8bit');
                        $afterBytes  = mb_strlen($collapsed,  '8bit');
                        $floodMsg    = "[{$table}#{$row->id}] Emoji flood collapsed "
                                     . "({$beforeBytes} bytes \u{2192} {$afterBytes} bytes).";
                        $this->warn("  {$floodMsg}");
                        Log::warning($floodMsg);
                        $converted = $collapsed;
                    }

                    $totalRows++;

                    if ($converted === $original) {
                        continue;
                    }

                    $totalChanged++;

                    $codes = LegacySmileyMapper::detect($original);
                    $msg   = "[{$table}#{$row->id}] Converting: " . implode(', ', $codes);
                    $this->line("  {$msg}");
                    Log::info($msg);

                    if (! $dryRun) {
                        // Guard: MEDIUMTEXT max is 16,777,215 bytes.
                        if (mb_strlen($converted, '8bit') > 16_777_215) {
                            $warn = "[{$table}#{$row->id}] SKIP — converted content exceeds MEDIUMTEXT limit (" . mb_strlen($converted, '8bit') . " bytes). Row left unchanged.";
                            $this->warn("  {$warn}");
                            Log::warning($warn);
                            continue;
                        }

                        try {
                            DB::table($table)
                                ->where('id', $row->id)
                                ->update([$column => $converted]);
                        } catch (\Throwable $e) {
                            $err = "[{$table}#{$row->id}] DB error: {$e->getMessage()}";
                            $this->warn("  {$err}");
                            Log::error($err);
                        }
                    }
                }
            });

        return [$totalChanged, $totalRows];
    }
}
