<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Services\AchievementService;
use App\Services\XPService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class RecalculateUserXpCommand extends Command
{
    protected $signature = 'skinbase:recalculate-user-xp
                            {user_id? : The ID of a single user to recompute}
                            {--all : Recompute XP and level for all non-deleted users}
                            {--chunk=1000 : Chunk size when --all is used}
                            {--dry-run : Show computed values without writing}
                            {--sync-achievements : Re-run achievement checks after a live recalculation}';

    protected $description = 'Rebuild stored user XP, level, and rank from user_xp_logs';

    public function handle(XPService $xp, AchievementService $achievements): int
    {
        $userId = $this->argument('user_id');
        $all = (bool) $this->option('all');
        $dryRun = (bool) $this->option('dry-run');
        $syncAchievements = (bool) $this->option('sync-achievements');
        $chunk = max(1, (int) $this->option('chunk'));

        if ($userId !== null && $all) {
            $this->error('Provide either a user_id or --all, not both.');

            return self::FAILURE;
        }

        if ($userId !== null) {
            return $this->recalculateSingle((int) $userId, $xp, $achievements, $dryRun, $syncAchievements);
        }

        if ($all) {
            return $this->recalculateAll($xp, $achievements, $chunk, $dryRun, $syncAchievements);
        }

        $this->error('Provide a user_id or use --all.');

        return self::FAILURE;
    }

    private function recalculateSingle(
        int $userId,
        XPService $xp,
        AchievementService $achievements,
        bool $dryRun,
        bool $syncAchievements,
    ): int {
        $exists = DB::table('users')->where('id', $userId)->exists();
        if (! $exists) {
            $this->error("User {$userId} not found.");

            return self::FAILURE;
        }

        $label = $dryRun ? '[DRY-RUN]' : '[LIVE]';
        $this->line("{$label} Recomputing XP for user #{$userId}...");

        $result = $xp->recalculateStoredProgress($userId, ! $dryRun);
        $this->table(
            ['Field', 'Stored', 'Computed'],
            [
                ['xp', $result['previous']['xp'], $result['computed']['xp']],
                ['level', $result['previous']['level'], $result['computed']['level']],
                ['rank', $result['previous']['rank'], $result['computed']['rank']],
            ]
        );

        if ($dryRun) {
            if ($syncAchievements) {
                $pending = $achievements->previewUnlocks($userId);
                $this->line('Achievements preview: ' . (empty($pending) ? 'no pending unlocks' : implode(', ', $pending)));
            }

            $this->warn('Dry-run: no changes written.');

            return self::SUCCESS;
        }

        if ($syncAchievements) {
            $unlocked = $achievements->checkAchievements($userId);
            $this->line('Achievements checked: ' . (empty($unlocked) ? 'no new unlocks' : implode(', ', $unlocked)));
        }

        $this->info($result['changed'] ? "XP updated for user #{$userId}." : "User #{$userId} was already in sync.");

        return self::SUCCESS;
    }

    private function recalculateAll(
        XPService $xp,
        AchievementService $achievements,
        int $chunk,
        bool $dryRun,
        bool $syncAchievements,
    ): int {
        $total = DB::table('users')->whereNull('deleted_at')->count();
        $label = $dryRun ? '[DRY-RUN]' : '[LIVE]';

        $this->info("{$label} Recomputing XP for {$total} users (chunk={$chunk})...");

        $processed = 0;
        $changed = 0;
        $pendingAchievementUsers = 0;
        $pendingAchievementUnlocks = 0;
        $appliedAchievementUnlocks = 0;
        $bar = $this->output->createProgressBar($total);
        $bar->start();

        DB::table('users')
            ->whereNull('deleted_at')
            ->orderBy('id')
            ->chunkById($chunk, function ($users) use ($xp, $achievements, $dryRun, $syncAchievements, &$processed, &$changed, &$pendingAchievementUsers, &$pendingAchievementUnlocks, &$appliedAchievementUnlocks, $bar): void {
                foreach ($users as $user) {
                    $result = $xp->recalculateStoredProgress((int) $user->id, ! $dryRun);

                    if ($result['changed']) {
                        $changed++;
                    }

                    if ($syncAchievements) {
                        if ($dryRun) {
                            $pending = $achievements->previewUnlocks((int) $user->id);
                            if (! empty($pending)) {
                                $pendingAchievementUsers++;
                                $pendingAchievementUnlocks += count($pending);
                            }
                        } else {
                            $unlocked = $achievements->checkAchievements((int) $user->id);
                            $appliedAchievementUnlocks += count($unlocked);
                        }
                    }

                    $processed++;
                    $bar->advance();
                }
            });

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

        $summary = "Done - {$processed} users processed, {$changed} " . ($dryRun ? 'would change.' : 'updated.');
        if ($syncAchievements) {
            if ($dryRun) {
                $summary .= " Achievement preview: {$pendingAchievementUnlocks} pending unlock(s) across {$pendingAchievementUsers} user(s).";
            } else {
                $summary .= " Achievements re-checked: {$appliedAchievementUnlocks} unlock(s) applied.";
            }
        }

        $this->info($summary);

        return self::SUCCESS;
    }
}
