<?php

declare(strict_types=1);

namespace App\Support;

use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

final class UsernamePolicy
{
    public static function min(): int
    {
        return (int) config('usernames.min', 3);
    }

    public static function max(): int
    {
        return (int) config('usernames.max', 20);
    }

    public static function regex(): string
    {
        return (string) config('usernames.regex', '/^[a-zA-Z0-9_]{3,20}$/');
    }

    /**
     * @return array<int, string>
     */
    public static function reserved(): array
    {
        $pool = [
            ...(array) config('usernames.reserved', []),
            ...(array) config('skinbase.reserved_usernames', []),
        ];

        return array_values(array_unique(array_map(static fn (string $v): string => strtolower(trim($v)), $pool)));
    }

    public static function normalize(string $value): string
    {
        return strtolower(trim($value));
    }

    public static function sanitizeLegacy(string $value): string
    {
        $value = Str::ascii($value);
        $value = strtolower(trim($value));
        $value = preg_replace('/[^a-z0-9_-]+/', '_', $value) ?? '';
        $value = trim($value, '_-');

        if ($value === '') {
            return 'user';
        }

        return substr($value, 0, self::max());
    }

    public static function isReserved(string $username): bool
    {
        return in_array(self::normalize($username), self::reserved(), true);
    }

    public static function similarReserved(string $username): ?string
    {
        $normalized = self::normalize($username);
        $reduced = self::reduceForSimilarity($normalized);
        $threshold = (int) config('usernames.similarity_threshold', 2);

        foreach (self::reserved() as $reserved) {
            if (levenshtein($reduced, self::reduceForSimilarity($reserved)) <= $threshold) {
                return $reserved;
            }
        }

        return null;
    }

    public static function hasApprovedOverride(string $username, ?int $userId = null): bool
    {
        if (! Schema::hasTable('username_approval_requests')) {
            return false;
        }

        $normalized = self::normalize($username);

        return DB::table('username_approval_requests')
            ->where('requested_username', $normalized)
            ->where('status', 'approved')
            ->when($userId !== null, fn ($q) => $q->where(function ($sub) use ($userId) {
                $sub->where('user_id', $userId)->orWhereNull('user_id');
            }))
            ->exists();
    }

    public static function uniqueCandidate(string $base, ?int $ignoreUserId = null): string
    {
        $base = self::sanitizeLegacy($base);
        if ($base === '' || self::isReserved($base) || self::similarReserved($base) !== null) {
            $base = 'user';
        }

        $max = self::max();
        $candidate = substr($base, 0, $max);
        $suffix = 1;

        while (self::exists($candidate, $ignoreUserId) || self::isReserved($candidate) || self::similarReserved($candidate) !== null) {
            $suffixStr = (string) $suffix;
            $prefixLen = max(1, $max - strlen($suffixStr));
            $candidate = substr($base, 0, $prefixLen) . $suffixStr;
            $suffix++;
        }

        return $candidate;
    }

    private static function exists(string $username, ?int $ignoreUserId = null): bool
    {
        $query = User::query()->whereRaw('LOWER(username) = ?', [strtolower($username)]);
        if ($ignoreUserId !== null) {
            $query->where('id', '!=', $ignoreUserId);
        }

        return $query->exists();
    }

    private static function reduceForSimilarity(string $value): string
    {
        return preg_replace('/[0-9_-]+/', '', strtolower($value)) ?? strtolower($value);
    }
}
