<?php

namespace App\Console\Commands;

use App\Support\UsernamePolicy;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class ImportLegacyUsers extends Command
{
    protected $signature = 'skinbase:import-legacy-users {--chunk=200 : Chunk size for processing} {--force-reset-all : Force reset passwords for all imported users} {--restore-temp-usernames : Restore legacy usernames for existing users still using tmpu12345-style placeholders} {--dry-run : Preview which users would be skipped/deleted without making changes}';
    protected $description = 'Import legacy users into the new auth schema per legacy_users_migration spec';

    protected string $migrationLogPath;
    /** @var array<int,true> Legacy user IDs that qualify for import */
    protected array $activeUserIds = [];

    public function handle(): int
    {
        $this->migrationLogPath = (string) storage_path('logs/username_migration.log');
        @file_put_contents($this->migrationLogPath, '['.now()."] Starting legacy username policy migration\n", FILE_APPEND);

        // Build the set of legacy user IDs that have any meaningful activity.
        // Users outside this set will be skipped (or deleted from the new DB if already imported).
        $this->activeUserIds = $this->buildActiveUserIds();
        $this->info('Active legacy users (uploads / comments / forum): ' . count($this->activeUserIds));

        $chunk     = (int) $this->option('chunk');
        $dryRun    = (bool) $this->option('dry-run');
        $imported  = 0;
        $skipped   = 0;
        $purged    = 0;

        if (! DB::connection('legacy')->getPdo()) {
            $this->error('Legacy DB connection "legacy" is not configured or reachable.');
            return self::FAILURE;
        }

        DB::connection('legacy')->table('users')
            ->chunkById($chunk, function ($rows) use (&$imported, &$skipped, &$purged, $dryRun) {
                $ids = $rows->pluck('user_id')->all();
                $stats = DB::connection('legacy')->table('users_statistics')
                    ->whereIn('user_id', $ids)
                    ->get()
                    ->keyBy('user_id');

                foreach ($rows as $row) {
                    $legacyId = (int) $row->user_id;

                    // ── Inactive user: no uploads, no comments, no forum activity ──
                    if (! isset($this->activeUserIds[$legacyId])) {
                        // If already imported into the new DB, purge it.
                        $existsInNew = DB::table('users')->where('id', $legacyId)->exists();
                        if ($existsInNew) {
                            if ($dryRun) {
                                $this->warn("[dry] Would DELETE inactive user_id={$legacyId} from new DB");
                            } else {
                                $this->purgeNewUser($legacyId);
                                $this->warn("[purge] Deleted inactive user_id={$legacyId} from new DB");
                                $purged++;
                            }
                        } else {
                            $this->line("[skip] user_id={$legacyId} no activity — skipping");
                        }
                        $skipped++;
                        continue;
                    }

                    if ($dryRun) {
                        $this->line("[dry] Would import user_id={$legacyId}");
                        $imported++;
                        continue;
                    }

                    try {
                        $this->importRow($row, $stats[$row->user_id] ?? null);
                        $imported++;
                    } catch (\Throwable $e) {
                        $skipped++;
                        $this->warn("Skip user_id {$row->user_id}: {$e->getMessage()}");
                    }
                }
            }, 'user_id');

        $this->info("Imported: {$imported}, Skipped: {$skipped}, Purged: {$purged}");
        return self::SUCCESS;
    }

    /**
     * Build a lookup array of legacy user IDs that qualify for import:
     * — uploaded at least one artwork (users_statistics.uploads > 0)
     * — posted at least one artwork comment (artworks_comments.user_id)
     * — created or posted to a forum thread (forum_topics / forum_posts)
     *
     * @return array<int,true>
     */
    protected function buildActiveUserIds(): array
    {
        $rows = DB::connection('legacy')->select("
            SELECT DISTINCT user_id FROM users_statistics WHERE uploads > 0
            UNION
            SELECT DISTINCT user_id FROM artworks_comments WHERE user_id > 0
            UNION
            SELECT DISTINCT user_id FROM forum_posts WHERE user_id > 0
            UNION
            SELECT DISTINCT user_id FROM forum_topics WHERE user_id > 0
        ");

        $map = [];
        foreach ($rows as $r) {
            $map[(int) $r->user_id] = true;
        }
        return $map;
    }

    /**
     * Remove all new-DB records for a given legacy user ID.
     * Covers: users, user_profiles, user_statistics, username_redirects.
     */
    protected function purgeNewUser(int $userId): void
    {
        DB::transaction(function () use ($userId) {
            DB::table('username_redirects')->where('user_id', $userId)->delete();
            DB::table('user_statistics')->where('user_id', $userId)->delete();
            DB::table('user_profiles')->where('user_id', $userId)->delete();
            DB::table('users')->where('id', $userId)->delete();
        });
    }

    protected function importRow($row, $statRow = null): void
    {
        $legacyId = (int) $row->user_id;

        // Use legacy username as-is by default. Placeholder tmp usernames can be
        // restored explicitly with --restore-temp-usernames using safe uniqueness rules.
        $existingUser = DB::table('users')
            ->select(['id', 'username'])
            ->where('id', $legacyId)
            ->first();

        $username = $this->resolveImportUsername($row, $legacyId, $existingUser?->username ?? null);

        $normalizedLegacy = UsernamePolicy::normalize((string) ($row->uname ?? ''));
        if ($normalizedLegacy !== $username) {
            @file_put_contents(
                $this->migrationLogPath,
                sprintf("[%s] user_id=%d old=%s new=%s\n", now()->toDateTimeString(), $legacyId, $normalizedLegacy, $username),
                FILE_APPEND
            );
        }

        // Use the real legacy email; only synthesise a placeholder when missing.
        $rawEmail = $row->email ? strtolower(trim($row->email)) : null;
        $email = $rawEmail ?: ($this->sanitizeEmailLocal($username) . '@users.skinbase.org');

        $legacyPassword = $row->password2 ?: $row->password ?: null;

        // Optionally force-reset every imported user's password to a secure random value.
        if ($this->option('force-reset-all')) {
            $this->warn("Force-reset-all enabled: generating secure password for user_id {$row->user_id}.");
            $passwordHash = Hash::make(Str::random(64));
        } else {
            // Force-reset known weak default passwords (e.g. "abc123").
            if ($legacyPassword !== null && trim($legacyPassword) === 'abc123') {
                $this->warn("Weak password 'abc123' detected for user_id {$row->user_id}; forcing reset.");
                $passwordHash = Hash::make(Str::random(64));
            } else {
                $passwordHash = Hash::make($legacyPassword ?: Str::random(32));
            }
        }

        $uploads = $this->sanitizeStatValue($statRow->uploads ?? 0);
        $downloads = $this->sanitizeStatValue($statRow->downloads ?? 0);
        $pageviews = $this->sanitizeStatValue($statRow->pageviews ?? 0);
        $awards = $this->sanitizeStatValue($statRow->awards ?? 0);

        DB::transaction(function () use ($legacyId, $username, $email, $passwordHash, $row, $uploads, $downloads, $pageviews, $awards) {
            $now = now();
            $existingUser = DB::table('users')
                ->select(['id', 'username'])
                ->where('id', $legacyId)
                ->first();
            $alreadyExists = $existingUser !== null;
            $previousUsername = (string) ($existingUser?->username ?? '');

            // All fields synced from legacy on every run
            $sharedFields = [
                'username'             => $username,
                'username_changed_at'  => $now,
                'name'                 => $row->real_name ?: $username,
                'email'                => $email,
                'is_active'            => (int) ($row->active ?? 1) === 1,
                'needs_password_reset' => true,
                'role'                 => 'user',
                'legacy_password_algo' => null,
                'last_visit_at'        => $row->LastVisit ?: null,
                'updated_at'           => $now,
            ];

            if ($alreadyExists) {
                // Sync all fields from legacy — password is never overwritten on re-runs
                // (unless --force-reset-all was passed, in which case the caller handles it
                // separately outside this transaction).
                DB::table('users')->where('id', $legacyId)->update($sharedFields);
            } else {
                DB::table('users')->insert(array_merge($sharedFields, [
                    'id'         => $legacyId,
                    'password'   => $passwordHash,
                    'created_at' => $row->joinDate ?: $now,
                ]));
            }

            DB::table('user_profiles')->updateOrInsert(
                ['user_id' => $legacyId],
                [
                    'about'          => $row->about_me ?: $row->description ?: null,
                    'avatar_legacy'  => $row->picture ?: null,
                    'cover_image'    => $row->cover_art ?: null,
                    'country'        => $row->country ?: null,
                    'country_code'   => $row->country_code ? substr($row->country_code, 0, 2) : null,
                    'language'       => $row->lang ?: null,
                    'birthdate'      => $row->birth ?: null,
                    'gender'         => $this->normalizeLegacyGender($row->gender ?? null),
                    'website'        => $row->web ?: null,
                    'updated_at'     => $now,
                ]
            );

            // Do not duplicate `website` into `user_social_links` — keep canonical site in `user_profiles.website`.

            DB::table('user_statistics')->updateOrInsert(
                ['user_id' => $legacyId],
                [
                    'uploads_count'                => $uploads,
                    'downloads_received_count'     => $downloads,
                    'artwork_views_received_count' => $pageviews,
                    'awards_received_count'        => $awards,
                    'updated_at'                   => $now,
                ]
            );

            if (Schema::hasTable('username_redirects')) {
                $old = $this->usernameRedirectKey((string) ($row->uname ?? ''));
                if ($old !== '' && $old !== $username) {
                    DB::table('username_redirects')->updateOrInsert(
                        ['old_username' => $old],
                        [
                            'new_username' => $username,
                            'user_id' => $legacyId,
                            'created_at' => $now,
                            'updated_at' => $now,
                        ]
                    );
                }

                if ($this->shouldRestoreTemporaryUsername($previousUsername) && $previousUsername !== $username) {
                    DB::table('username_redirects')->updateOrInsert(
                        ['old_username' => $this->usernameRedirectKey($previousUsername)],
                        [
                            'new_username' => $username,
                            'user_id' => $legacyId,
                            'created_at' => $now,
                            'updated_at' => $now,
                        ]
                    );
                }
            }
        });
    }

    protected function resolveImportUsername(object $row, int $legacyId, ?string $existingUsername = null): string
    {
        $legacyUsername = $this->sanitizeUsername((string) ($row->uname ?: ('user' . $legacyId)));

        if (! $this->option('restore-temp-usernames')) {
            return $legacyUsername;
        }

        if ($existingUsername === null || $existingUsername === '') {
            return $legacyUsername;
        }

        if (! $this->shouldRestoreTemporaryUsername($existingUsername)) {
            return $existingUsername;
        }

        return UsernamePolicy::uniqueCandidate((string) ($row->uname ?: ('user' . $legacyId)), $legacyId);
    }

    protected function shouldRestoreTemporaryUsername(?string $username): bool
    {
        if (! is_string($username) || trim($username) === '') {
            return false;
        }

        return preg_match('/^tmpu\d+$/i', trim($username)) === 1;
    }

    /**
     * Ensure statistic values are safe for unsigned DB columns.
     */
    protected function sanitizeStatValue($value): int
    {
        $n = is_numeric($value) ? (int) $value : 0;
        if ($n < 0) {
            return 0;
        }
        return $n;
    }

    protected function sanitizeUsername(string $username): string
    {
        return UsernamePolicy::sanitizeLegacy($username);
    }

    protected function usernameRedirectKey(?string $username): string
    {
        $value = $this->sanitizeUsername((string) ($username ?? ''));

        return $value === 'user' && trim((string) ($username ?? '')) === '' ? '' : $value;
    }

    protected function normalizeLegacyGender(mixed $value): ?string
    {
        $normalized = strtoupper(trim((string) ($value ?? '')));

        return match ($normalized) {
            'M', 'MALE', 'MAN', 'BOY' => 'M',
            'F', 'FEMALE', 'WOMAN', 'GIRL' => 'F',
            default => null,
        };
    }

    protected function sanitizeEmailLocal(string $value): string
    {
        $local = strtolower(trim($value));
        $local = preg_replace('/[^a-z0-9._-]/', '-', $local) ?: 'user';
        return trim($local, '.-') ?: 'user';
    }
}
