<?php

require_once __DIR__ . '/encrypted_payload.php';

/** @internal PHPUnit: inject request body (cleared by qdb_test_reset_http_state). */
function qdb_test_set_request_body(string $body): void
{
    if (!defined('QDB_TESTING')) {
        throw new RuntimeException('qdb_test_set_request_body is only available in tests');
    }
    $GLOBALS['qdb_test_request_body'] = $body;
}

/** @internal PHPUnit: reset cached body between API calls. */
function qdb_test_reset_http_state(): void
{
    if (!defined('QDB_TESTING')) {
        return;
    }
    unset($GLOBALS['qdb_test_request_body']);
}

/** @internal PHPUnit: rethrow response control-flow exceptions. */
function qdb_test_is_control_flow_exception(\Throwable $e): bool
{
    return $e instanceof \Tests\Support\JsonSuccessResponse
        || $e instanceof \Tests\Support\JsonErrorResponse
        || $e instanceof \Tests\Support\RawHttpResponse;
}

function json_success(mixed $data, int $status = 200): never {
    if (defined('QDB_TESTING')) {
        throw new \Tests\Support\JsonSuccessResponse($data, $status);
    }
    http_response_code($status);
    header('Content-Type: application/json; charset=UTF-8');
    echo json_encode(["ok" => true, "data" => $data]);
    exit;
}

/**
 * @param mixed $details Optional structured payload (e.g. validation error list).
 */
/**
 * Send a non-JSON download response (CSV, ZIP, etc.).
 *
 * @param array<string, string> $headers
 */
function qdb_http_download(string $body, array $headers, int $status = 200): never
{
    if (defined('QDB_TESTING')) {
        throw new \Tests\Support\RawHttpResponse($body, $headers, $status);
    }
    http_response_code($status);
    foreach ($headers as $name => $value) {
        header($name . ': ' . $value);
    }
    echo $body;
    exit;
}

function json_error(string $code, string $message, int $status = 400, mixed $details = null): never {
    if (defined('QDB_TESTING')) {
        throw new \Tests\Support\JsonErrorResponse($code, $message, $status, $details);
    }
    if (function_exists('qdb_api_log_note_api_error')) {
        qdb_api_log_note_api_error($code, $message, $status);
    }
    http_response_code($status);
    header('Content-Type: application/json; charset=UTF-8');
    $error = ["code" => $code, "message" => $message];
    if ($details !== null) {
        $error['details'] = $details;
    }
    echo json_encode(["ok" => false, "error" => $error]);
    exit;
}

/** Raw request body (cached; php://input is single-read). */
function qdb_raw_request_body(): string {
    if (defined('QDB_TESTING') && array_key_exists('qdb_test_request_body', $GLOBALS)) {
        return (string)$GLOBALS['qdb_test_request_body'];
    }
    static $raw = null;
    if ($raw === null) {
        $read = file_get_contents('php://input');
        $raw  = $read === false ? '' : $read;
    }
    return $raw;
}

function read_json_body(): array {
    $raw = qdb_raw_request_body();
    $data = json_decode($raw, true);
    if (!is_array($data)) {
        json_error('INVALID_BODY', 'Request body must be valid JSON', 400);
    }
    return $data;
}

function read_encrypted_json_body(string $tokenHex): array {
    $outer = read_json_body();
    if (empty($outer['encrypted'])) {
        json_error('ENCRYPTION_REQUIRED', 'Sensitive requests must send an encrypted payload', 400);
    }
    try {
        $plain = qdb_decrypt_sensitive_envelope($outer, $tokenHex);
    } catch (Throwable $e) {
        error_log('decrypt body failed: ' . $e->getMessage());
        json_error('DECRYPT_FAILED', 'Could not decrypt request payload', 400);
    }
    $data = json_decode($plain, true);
    if (!is_array($data)) {
        json_error('INVALID_BODY', 'Decrypted body must be valid JSON object', 400);
    }
    return $data;
}

function json_success_sensitive(mixed $data, string $tokenHex, int $status = 200): never {
    $plain = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    json_success(qdb_sensitive_envelope($plain, $tokenHex), $status);
}
