<?php

namespace App\Services;

use App\Exceptions\DuplicateGameAccountException;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;

class AzerothCoreAccountRegistrar
{
    public function __construct(private readonly AzerothCoreSrpService $srp)
    {
    }

    /**
     * @return array{id: int, username: string}
     */
    public function register(string $username, string $email, string $password): array
    {
        $normalizedUsername = $this->srp->normalizeUsername($username);
        $normalizedPassword = $this->srp->normalizePassword($password);
        $normalizedEmail = $this->srp->normalizeEmail($email);
        [$saltHex, $verifierHex] = $this->srp->makeRegistrationData($normalizedUsername, $normalizedPassword);

        $connection = DB::connection(config('moonwell.auth_connection'));

        return $connection->transaction(function () use (
            $connection,
            $normalizedEmail,
            $normalizedUsername,
            $saltHex,
            $verifierHex,
        ): array {
            if ($this->accountExists($connection, 'username', $normalizedUsername)) {
                throw DuplicateGameAccountException::forUsername();
            }

            if (
                config('moonwell.registration.enforce_unique_email')
                && $normalizedEmail !== ''
                && $this->accountExists($connection, 'email', $normalizedEmail)
            ) {
                throw DuplicateGameAccountException::forEmail();
            }

            try {
                $connection->insert(
                    'INSERT INTO account (username, salt, verifier, expansion, reg_mail, email, joindate) VALUES (?, UNHEX(?), UNHEX(?), ?, ?, ?, ?)',
                    [
                        $normalizedUsername,
                        $saltHex,
                        $verifierHex,
                        config('moonwell.registration.expansion'),
                        $normalizedEmail,
                        $normalizedEmail,
                        now()->toDateTimeString(),
                    ],
                );
            } catch (QueryException $exception) {
                if ($this->isDuplicateKeyException($exception)) {
                    throw DuplicateGameAccountException::forUsername();
                }

                throw $exception;
            }

            $accountId = (int) $connection->getPdo()->lastInsertId();

            $connection->insert(
                'INSERT IGNORE INTO realmcharacters (realmid, acctid, numchars) SELECT id, ?, 0 FROM realmlist',
                [$accountId],
            );

            $connection->statement(
                'INSERT INTO account_access (id, gmlevel, RealmID, comment) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE gmlevel = VALUES(gmlevel), comment = VALUES(comment)',
                [
                    $accountId,
                    config('moonwell.registration.gmlevel'),
                    config('moonwell.registration.realm_id'),
                    config('moonwell.registration.access_comment'),
                ],
            );

            return [
                'id' => $accountId,
                'username' => $normalizedUsername,
            ];
        }, 3);
    }

    private function accountExists(ConnectionInterface $connection, string $column, string $value): bool
    {
        return $connection->table('account')->where($column, $value)->exists();
    }

    private function isDuplicateKeyException(QueryException $exception): bool
    {
        return (string) $exception->getCode() === '23000';
    }
}
