<?php
declare(strict_types=1);

require_once __DIR__ . '/rate_limit.php';

function golder_ensure_api_security_tables(PDO $master): void
{
    $master->exec(
        "CREATE TABLE IF NOT EXISTS api_tokens (
            id BIGINT AUTO_INCREMENT PRIMARY KEY,
            customer_id BIGINT NOT NULL,
            token_hash CHAR(64) NOT NULL,
            scope VARCHAR(80) NOT NULL,
            metadata_json JSON,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            expires_at DATETIME NOT NULL,
            last_used_at DATETIME DEFAULT NULL,
            revoked_at DATETIME DEFAULT NULL,
            UNIQUE KEY uniq_token_hash (token_hash),
            KEY idx_customer_scope (customer_id, scope),
            KEY idx_expires (expires_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
    );

    $master->exec(
        "CREATE TABLE IF NOT EXISTS api_nonces (
            id BIGINT AUTO_INCREMENT PRIMARY KEY,
            token_id BIGINT NOT NULL,
            nonce_value VARCHAR(120) NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_token_nonce (token_id, nonce_value),
            KEY idx_created (created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
    );
}

function golder_issue_api_token(PDO $master, int $customerId, string $scope = 'desktop_sync', int $ttlSeconds = 86400, array $metadata = []): string
{
    golder_ensure_api_security_tables($master);

    $rawToken = bin2hex(random_bytes(32));
    $hash = hash('sha256', $rawToken);
    $expiresAt = gmdate('Y-m-d H:i:s', time() + max(60, $ttlSeconds));

    $st = $master->prepare(
        'INSERT INTO api_tokens (customer_id, token_hash, scope, metadata_json, expires_at)
         VALUES (:cid, :th, :sc, :meta, :exp)'
    );
    $metaJson = json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if (!is_string($metaJson)) {
        $metaJson = '{}';
    }
    $st->execute([
        ':cid' => $customerId,
        ':th' => $hash,
        ':sc' => $scope,
        ':meta' => $metaJson,
        ':exp' => $expiresAt,
    ]);

    return $rawToken;
}

function golder_revoke_api_tokens(PDO $master, int $customerId, ?string $scope = null): int
{
    golder_ensure_api_security_tables($master);
    if ($scope === null || trim($scope) === '') {
        $st = $master->prepare('UPDATE api_tokens SET revoked_at = NOW() WHERE customer_id = :cid AND revoked_at IS NULL');
        $st->execute([':cid' => $customerId]);
        return (int)$st->rowCount();
    }
    $st = $master->prepare('UPDATE api_tokens SET revoked_at = NOW() WHERE customer_id = :cid AND scope = :sc AND revoked_at IS NULL');
    $st->execute([':cid' => $customerId, ':sc' => $scope]);
    return (int)$st->rowCount();
}

function golder_get_bearer_token(): string
{
    $header = (string)($_SERVER['HTTP_AUTHORIZATION'] ?? '');
    if ($header === '' && function_exists('getallheaders')) {
        $headers = getallheaders();
        if (is_array($headers)) {
            $header = (string)($headers['Authorization'] ?? $headers['authorization'] ?? '');
        }
    }
    if ($header === '') {
        return '';
    }
    if (stripos($header, 'Bearer ') !== 0) {
        return '';
    }
    return trim(substr($header, 7));
}

function golder_auth_token(PDO $master, string $requiredScope = ''): array
{
    golder_ensure_api_security_tables($master);

    $token = golder_get_bearer_token();
    if ($token === '') {
        golder_json_response(['status' => 'error', 'code' => 'UNAUTHORIZED', 'message' => 'Missing bearer token'], 401);
    }

    $tokenHash = hash('sha256', $token);
    $st = $master->prepare(
        'SELECT * FROM api_tokens WHERE token_hash = :th AND revoked_at IS NULL AND expires_at >= NOW() LIMIT 1'
    );
    $st->execute([':th' => $tokenHash]);
    $row = $st->fetch();
    if (!$row) {
        golder_json_response(['status' => 'error', 'code' => 'UNAUTHORIZED', 'message' => 'Invalid token'], 401);
    }

    if ($requiredScope !== '' && (string)($row['scope'] ?? '') !== $requiredScope) {
        golder_json_response(['status' => 'error', 'code' => 'FORBIDDEN', 'message' => 'Scope mismatch'], 403);
    }

    $tokenId = (int)($row['id'] ?? 0);
    if ($tokenId <= 0) {
        golder_json_response(['status' => 'error', 'code' => 'UNAUTHORIZED'], 401);
    }

    $nonce = trim((string)($_SERVER['HTTP_X_REQUEST_NONCE'] ?? ''));
    if ($nonce !== '') {
        golder_auth_register_nonce($master, $tokenId, $nonce);
    }

    $master->prepare('UPDATE api_tokens SET last_used_at = NOW() WHERE id = :id')->execute([':id' => $tokenId]);

    return $row;
}

function golder_auth_register_nonce(PDO $master, int $tokenId, string $nonce): void
{
    golder_ensure_api_security_tables($master);
    $nonce = substr(trim($nonce), 0, 120);
    if ($nonce === '') {
        return;
    }
    try {
        $ins = $master->prepare('INSERT INTO api_nonces (token_id, nonce_value) VALUES (:tid, :nonce)');
        $ins->execute([':tid' => $tokenId, ':nonce' => $nonce]);
    } catch (Throwable $e) {
        golder_json_response(['status' => 'error', 'code' => 'REPLAY_DETECTED', 'message' => 'Nonce replay detected'], 409);
    }
}

function golder_auth_cleanup_nonces(PDO $master, int $keepHours = 24): int
{
    golder_ensure_api_security_tables($master);
    $hours = max(1, $keepHours);
    $st = $master->prepare('DELETE FROM api_nonces WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL :h HOUR)');
    $st->execute([':h' => $hours]);
    return (int)$st->rowCount();
}

function golder_api_rate_limit_route_key(string $scope, ?string $scriptName = null): string
{
    $normalizedScope = strtolower(trim($scope));
    if ($normalizedScope === '') {
        $normalizedScope = 'default';
    }

    $script = trim((string)($scriptName ?? ($_SERVER['SCRIPT_NAME'] ?? $_SERVER['PHP_SELF'] ?? '')));
    if ($script === '') {
        return 'api_scope_' . $normalizedScope;
    }

    $route = strtolower((string)preg_replace('/\?.*$/', '', $script));
    $route = trim((string)preg_replace('/[^a-z0-9]+/', '_', $route), '_');
    if ($route === '') {
        return 'api_scope_' . $normalizedScope;
    }

    if (strlen($route) > 48) {
        $route = substr($route, 0, 24) . '_' . substr(hash('sha256', $route), 0, 12);
    }

    return 'api_scope_' . $normalizedScope . '_' . $route;
}

function golder_apply_default_rate_limit(PDO $master, string $routeKey, int $maxRequests, int $windowSeconds): void
{
    $ip = golder_client_ip();
    $bucket = $routeKey . ':' . $ip;
    if (!golder_rate_limit_allow($master, $bucket, $maxRequests, $windowSeconds)) {
        golder_json_response(['status' => 'error', 'code' => 'RATE_LIMITED', 'message' => 'Rate limit exceeded'], 429);
    }
}


