<?php
declare(strict_types=1);

function mocker_external_players_api_ready(): bool
{
    return defined('EXTERNAL_PLAYERS_API_URL')
        && defined('EXTERNAL_PLAYERS_BEARER_TOKEN')
        && trim((string) EXTERNAL_PLAYERS_API_URL) !== ''
        && trim((string) EXTERNAL_PLAYERS_BEARER_TOKEN) !== '';
}

/** Harici üye listesi senkronu (admin) — SYNC_ENABLED açık olmalı. */
function mocker_external_players_enabled(): bool
{
    if (defined('EXTERNAL_PLAYERS_SYNC_ENABLED') && !(bool) EXTERNAL_PLAYERS_SYNC_ENABLED) {
        return false;
    }

    return mocker_external_players_api_ready();
}

/** RX hesabıyla site girişi — token yeterli; senkron kapalı olsa da çalışır. */
function mocker_external_players_login_available(): bool
{
    if (defined('EXTERNAL_PLAYERS_LOGIN_ENABLED') && !(bool) EXTERNAL_PLAYERS_LOGIN_ENABLED) {
        return false;
    }

    return mocker_external_players_api_ready();
}

function mocker_external_players_request_headers(): array
{
    $headers = [
        'Accept: application/json',
        'Authorization: Bearer ' . trim((string) EXTERNAL_PLAYERS_BEARER_TOKEN),
    ];
    if (defined('EXTERNAL_PLAYERS_X_CLIENT') && trim((string) EXTERNAL_PLAYERS_X_CLIENT) !== '') {
        $headers[] = 'X-Client: ' . trim((string) EXTERNAL_PLAYERS_X_CLIENT);
    }
    if (defined('EXTERNAL_PLAYERS_ORIGIN') && trim((string) EXTERNAL_PLAYERS_ORIGIN) !== '') {
        $headers[] = 'Origin: ' . trim((string) EXTERNAL_PLAYERS_ORIGIN);
    }
    if (defined('EXTERNAL_PLAYERS_REFERER') && trim((string) EXTERNAL_PLAYERS_REFERER) !== '') {
        $headers[] = 'Referer: ' . trim((string) EXTERNAL_PLAYERS_REFERER);
    }
    $headers[] = 'Accept-Language: tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7';
    $headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';

    return $headers;
}

/**
 * @param array<string, mixed> $body
 */
function mocker_external_players_http_json(string $method, string $url, array $body = []): array
{
    $headers = mocker_external_players_request_headers();
    $timeout = max(5, (int) (defined('EXTERNAL_PLAYERS_TIMEOUT_SEC') ? EXTERNAL_PLAYERS_TIMEOUT_SEC : 15));
    $sslVerify = (int) (defined('EXTERNAL_PLAYERS_SSL_VERIFY') ? EXTERNAL_PLAYERS_SSL_VERIFY : 0) === 1;
    $method = strtoupper($method);
    $payload = $body !== [] ? json_encode($body, JSON_UNESCAPED_UNICODE) : '';
    if ($payload !== '') {
        $headers[] = 'Content-Type: application/json';
    }

    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        if ($ch === false) {
            throw new RuntimeException('external_curl_init_failed');
        }
        $opts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HEADER => false,
            CURLOPT_SSL_VERIFYPEER => $sslVerify,
            CURLOPT_SSL_VERIFYHOST => $sslVerify ? 2 : 0,
            CURLOPT_CUSTOMREQUEST => $method,
        ];
        if ($payload !== '') {
            $opts[CURLOPT_POSTFIELDS] = $payload;
        }
        curl_setopt_array($ch, $opts);
        $raw = curl_exec($ch);
        $curlErr = curl_error($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);
        if (!is_string($raw) || $raw === '') {
            throw new RuntimeException('external_fetch_failed' . ($curlErr !== '' ? ':' . $curlErr : ''));
        }
        if ($httpCode >= 400) {
            throw new RuntimeException('external_http_' . $httpCode);
        }
    } else {
        $ctx = stream_context_create([
            'http' => [
                'method' => $method,
                'header' => implode("\r\n", $headers) . "\r\n",
                'content' => $payload,
                'ignore_errors' => true,
                'timeout' => $timeout,
            ],
            'ssl' => [
                'verify_peer' => $sslVerify,
                'verify_peer_name' => $sslVerify,
            ],
        ]);
        $raw = @file_get_contents($url, false, $ctx);
        if ($raw === false || $raw === '') {
            throw new RuntimeException('external_fetch_failed');
        }
    }

    $json = json_decode((string) $raw, true);
    if (!is_array($json)) {
        throw new RuntimeException('external_invalid_json');
    }

    return $json;
}

function mocker_external_players_http_get_json(string $url): array
{
    return mocker_external_players_http_json('GET', $url);
}

function mocker_external_players_login_url(): string
{
    if (defined('EXTERNAL_PLAYERS_LOGIN_URL') && trim((string) EXTERNAL_PLAYERS_LOGIN_URL) !== '') {
        return trim((string) EXTERNAL_PLAYERS_LOGIN_URL);
    }
    $base = trim((string) EXTERNAL_PLAYERS_API_URL);
    if ($base === '') {
        return '';
    }
    if (preg_match('#/players/?(\?.*)?$#i', $base)) {
        return (string) preg_replace('#/players/?(\?.*)?$#i', '/login$1', $base);
    }

    return rtrim($base, '/') . '/login';
}

function mocker_external_players_login_success(array $json): bool
{
    if (($json['status'] ?? null) === true) {
        return true;
    }
    if (($json['ok'] ?? null) === true) {
        return true;
    }
    if (($json['success'] ?? null) === true) {
        return true;
    }
    $data = $json['data'] ?? null;
    if (is_array($data)) {
        if (!empty($data['token']) || !empty($data['access_token']) || !empty($data['api_token'])) {
            return true;
        }
        if (isset($data['user']) && is_array($data['user'])) {
            return true;
        }
        if (isset($data['id']) && (int) $data['id'] > 0) {
            return true;
        }
    }
    if (isset($json['token']) && (string) $json['token'] !== '') {
        return true;
    }
    if (isset($json['access_token']) && (string) $json['access_token'] !== '') {
        return true;
    }

    return false;
}

/**
 * @return list<string>
 */
function mocker_external_players_login_url_candidates(): array
{
    $out = [];
    $add = static function (string $url) use (&$out): void {
        $url = trim($url);
        if ($url !== '' && !in_array($url, $out, true)) {
            $out[] = $url;
        }
    };

    $add(mocker_external_players_login_url());
    $base = trim((string) (defined('EXTERNAL_PLAYERS_API_URL') ? EXTERNAL_PLAYERS_API_URL : ''));
    if ($base !== '') {
        $origin = preg_replace('#/api/.*$#i', '', $base) ?: $base;
        $add(rtrim($origin, '/') . '/api/bc/login');
        $add(rtrim($origin, '/') . '/api/bc/auth/login');
        $add(rtrim($origin, '/') . '/api/client/login');
        $add(rtrim($origin, '/') . '/api/login');
        if (preg_match('#/players/?(\?.*)?$#i', $base)) {
            $add((string) preg_replace('#/players/?(\?.*)?$#i', '/authenticate$1', $base));
        }
    }

    return $out;
}

/**
 * @param array<string, mixed> $playerRow
 */
function mocker_external_player_row_password_matches(array $playerRow, string $password): bool
{
    if ($password === '') {
        return false;
    }
    $candidates = [];
    foreach (['password_hash', 'password', 'pass', 'pwd'] as $key) {
        if (!empty($playerRow[$key]) && is_string($playerRow[$key])) {
            $candidates[] = trim($playerRow[$key]);
        }
    }
    foreach ($candidates as $stored) {
        if ($stored === '') {
            continue;
        }
        if (
            str_starts_with($stored, '$2y$')
            || str_starts_with($stored, '$2a$')
            || str_starts_with($stored, '$argon2')
        ) {
            if (password_verify($password, $stored)) {
                return true;
            }
            continue;
        }
        if (strlen($stored) === 32 && ctype_xdigit($stored) && md5($password) === strtolower($stored)) {
            return true;
        }
        if (hash_equals($stored, $password)) {
            return true;
        }
    }

    return false;
}

function mocker_external_player_verify_via_players_list(string $identity, string $password): bool
{
    if (!mocker_external_players_login_available() || $identity === '' || $password === '') {
        return false;
    }
    try {
        $page = mocker_external_players_fetch_page(1, ['username' => $identity]);
        foreach ($page['rows'] as $row) {
            if (!is_array($row)) {
                continue;
            }
            $uname = strtolower(trim((string) ($row['username'] ?? '')));
            $email = strtolower(trim((string) ($row['email'] ?? '')));
            $want = strtolower(trim($identity));
            if ($want === '' || ($uname !== $want && $email !== $want)) {
                continue;
            }
            if (mocker_external_player_row_password_matches($row, $password)) {
                return true;
            }
        }
    } catch (Throwable $e) {
        error_log('external_player_verify_via_players_list: ' . $e->getMessage());
    }

    return false;
}

/**
 * @param array<string, string> $fields
 */
function mocker_external_players_http_post_form(string $url, array $fields): array
{
    $headers = mocker_external_players_request_headers();
    $headers[] = 'Content-Type: application/x-www-form-urlencoded';
    $timeout = max(5, (int) (defined('EXTERNAL_PLAYERS_TIMEOUT_SEC') ? EXTERNAL_PLAYERS_TIMEOUT_SEC : 15));
    $sslVerify = (int) (defined('EXTERNAL_PLAYERS_SSL_VERIFY') ? EXTERNAL_PLAYERS_SSL_VERIFY : 0) === 1;
    $payload = http_build_query($fields);

    if (!function_exists('curl_init')) {
        throw new RuntimeException('external_curl_required');
    }
    $ch = curl_init($url);
    if ($ch === false) {
        throw new RuntimeException('external_curl_init_failed');
    }
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => $sslVerify,
        CURLOPT_SSL_VERIFYHOST => $sslVerify ? 2 : 0,
    ]);
    $raw = curl_exec($ch);
    $curlErr = curl_error($ch);
    $httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if (!is_string($raw) || $raw === '') {
        throw new RuntimeException('external_fetch_failed' . ($curlErr !== '' ? ':' . $curlErr : ''));
    }
    if ($httpCode >= 400) {
        throw new RuntimeException('external_http_' . $httpCode);
    }
    $json = json_decode($raw, true);
    if (!is_array($json)) {
        throw new RuntimeException('external_invalid_json');
    }

    return $json;
}

function mocker_external_player_verify_login(string $identity, string $password): bool
{
    if (!mocker_external_players_login_available()) {
        return false;
    }
    if ($identity === '' || $password === '') {
        return false;
    }

    $payloads = [
        ['username' => $identity, 'password' => $password],
        ['user_name' => $identity, 'password' => $password],
        ['login' => $identity, 'password' => $password],
    ];
    if (str_contains($identity, '@')) {
        $payloads[] = ['email' => $identity, 'password' => $password];
    }

    foreach (mocker_external_players_login_url_candidates() as $url) {
        foreach ($payloads as $body) {
            try {
                $resp = mocker_external_players_http_json('POST', $url, $body);
                if (mocker_external_players_login_success($resp)) {
                    return true;
                }
            } catch (Throwable $e) {
                error_log('external_player_verify_login json ' . $url . ': ' . $e->getMessage());
            }
            try {
                $resp = mocker_external_players_http_post_form($url, $body);
                if (mocker_external_players_login_success($resp)) {
                    return true;
                }
            } catch (Throwable $e) {
                error_log('external_player_verify_login form ' . $url . ': ' . $e->getMessage());
            }
        }
    }

    return mocker_external_player_verify_via_players_list($identity, $password);
}

function mocker_external_players_to_unix($value): int
{
    $s = trim((string) ($value ?? ''));
    if ($s === '') {
        return 0;
    }
    $ts = strtotime($s);

    return $ts !== false ? (int) $ts : 0;
}

/**
 * @return array{rows: list<array<string, mixed>>, pagination: array{current_page: int, last_page: int, per_page: int, total: int}}
 */
function mocker_external_players_fetch_page(int $page, array $filters = []): array
{
    $base = trim((string) EXTERNAL_PLAYERS_API_URL);
    $perPage = max(20, min(100, (int) (defined('EXTERNAL_PLAYERS_PER_PAGE') ? EXTERNAL_PLAYERS_PER_PAGE : 100)));
    $page = max(1, $page);

    $qs = http_build_query([
        'page' => $page,
        'id' => trim((string) ($filters['id'] ?? '')),
        'username' => trim((string) ($filters['username'] ?? '')),
        'telegram_username' => trim((string) ($filters['telegram_username'] ?? '')),
        'ip' => trim((string) ($filters['ip'] ?? '')),
        'per_page' => $perPage,
    ]);
    $url = $base . (str_contains($base, '?') ? '&' : '?') . $qs;
    $resp = null;
    $lastErr = null;
    for ($attempt = 0; $attempt < 4; $attempt++) {
        try {
            $resp = mocker_external_players_http_get_json($url);
            break;
        } catch (Throwable $e) {
            $lastErr = $e;
            if ($attempt < 3) {
                usleep(400000 * ($attempt + 1));
            }
        }
    }
    if (!is_array($resp)) {
        throw $lastErr ?? new RuntimeException('external_fetch_failed');
    }
    if (($resp['status'] ?? false) !== true || !isset($resp['data']['data']) || !is_array($resp['data']['data'])) {
        throw new RuntimeException('external_bad_response');
    }

    $payload = $resp['data'];
    $rows = [];
    foreach ($payload['data'] as $row) {
        if (is_array($row)) {
            $rows[] = $row;
        }
    }

    return [
        'rows' => $rows,
        'pagination' => [
            'current_page' => max(1, (int) ($payload['current_page'] ?? $page)),
            'last_page' => max(1, (int) ($payload['last_page'] ?? 1)),
            'per_page' => max(1, (int) ($payload['per_page'] ?? $perPage)),
            'total' => max(0, (int) ($payload['total'] ?? count($rows))),
        ],
    ];
}
