<?php

declare(strict_types=1);

namespace Tests\Support;

/**
 * Dispatches requests through api/index.php (same as production routing).
 */
final class ApiClient
{
    public function __construct(
        private readonly string $apiIndexPath,
    ) {
    }

    /**
     * @param array<string, string> $headers
     * @return array{ok: bool, status: int, data?: mixed, error?: array<string, mixed>}
     */
    public function request(
        string $method,
        string $route,
        ?array $body = null,
        array $headers = [],
        array $query = [],
    ): array {
        qdb_test_reset_http_state();

        $_SERVER['REQUEST_METHOD'] = strtoupper($method);
        $queryString = $query !== [] ? '?' . http_build_query($query) : '';
        $_SERVER['REQUEST_URI'] = '/api/' . ltrim($route, '/') . $queryString;
        $_SERVER['SCRIPT_NAME'] = '/api/index.php';
        $_GET = $query;
        if ($queryString !== '') {
            parse_str(ltrim($queryString, '?'), $_GET);
        }

        unset($_SERVER['HTTP_AUTHORIZATION'], $_SERVER['Authorization']);
        unset($_SERVER['HTTP_X_QDB_CLIENT']);

        foreach ($headers as $name => $value) {
            $key = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
            $_SERVER[$key] = $value;
        }

        if ($body !== null) {
            qdb_test_set_request_body(json_encode($body, JSON_THROW_ON_ERROR));
        }

        ob_start();
        try {
            require $this->apiIndexPath;
            ob_end_clean();
            return ['ok' => false, 'status' => 500, 'error' => ['code' => 'NO_RESPONSE', 'message' => 'Handler did not return JSON']];
        } catch (JsonSuccessResponse $e) {
            $output = ob_get_clean();
            return [
                'ok'     => true,
                'status' => $e->httpStatus,
                'data'   => $e->data,
                '_raw'   => $output,
            ];
        } catch (JsonErrorResponse $e) {
            ob_get_clean();
            $err = ['code' => $e->errorCode, 'message' => $e->errorMessage];
            if ($e->details !== null) {
                $err['details'] = $e->details;
            }
            return [
                'ok'     => false,
                'status' => $e->httpStatus,
                'error'  => $err,
            ];
        } catch (RawHttpResponse $e) {
            ob_get_clean();
            return [
                'ok'      => true,
                'status'  => $e->httpStatus,
                'raw'     => true,
                'body'    => $e->body,
                'headers' => $e->headers,
            ];
        } catch (\Throwable $e) {
            ob_end_clean();
            throw $e;
        }
    }

    public function loginWeb(string $username, string $password): array
    {
        return $this->request('POST', 'auth/login', [
            'username' => $username,
            'password' => $password,
        ], ['X-QDB-Client' => 'web']);
    }

    public function loginMobile(string $username, string $password): array
    {
        return $this->request('POST', 'auth/login', [
            'username' => $username,
            'password' => $password,
        ]);
    }

    /**
     * @return array{token: string, data: array<string, mixed>}
     */
    public function loginMobileAndGetToken(string $username, string $password): array
    {
        $res = $this->loginMobile($username, $password);
        if (!$res['ok']) {
            throw new \RuntimeException('Login failed: ' . ($res['error']['message'] ?? 'unknown'));
        }
        $token = $res['data']['token'] ?? '';
        if ($token === '') {
            throw new \RuntimeException('Login response missing token');
        }
        return ['token' => $token, 'data' => $res['data']];
    }

    /**
     * @return array{token: string, data: array<string, mixed>}
     */
    public function loginWebAndGetToken(string $username, string $password): array
    {
        $res = $this->loginWeb($username, $password);
        if (!$res['ok']) {
            throw new \RuntimeException('Login failed: ' . ($res['error']['message'] ?? 'unknown'));
        }
        $token = $res['data']['token'] ?? '';
        if ($token === '') {
            throw new \RuntimeException('Login response missing token');
        }
        return ['token' => $token, 'data' => $res['data']];
    }

    /**
     * @param array<string, string> $extraHeaders
     */
    public function withToken(string $token, string $method, string $route, ?array $body = null, array $query = [], array $extraHeaders = []): array
    {
        $headers = array_merge([
            'Authorization' => 'Bearer ' . $token,
            'X-QDB-Client'  => 'web',
        ], $extraHeaders);

        return $this->request($method, $route, $body, $headers, $query);
    }

    /**
     * @param array<string, string> $extraHeaders
     */
    public function withMobileToken(
        string $token,
        string $method,
        string $route,
        ?array $body = null,
        array $query = [],
        array $extraHeaders = [],
    ): array {
        $headers = array_merge(['Authorization' => 'Bearer ' . $token], $extraHeaders);

        return $this->request($method, $route, $body, $headers, $query);
    }

    /**
     * POST an encrypted payload (Android app API).
     *
     * @param array<string, mixed> $payload
     */
    public function encryptedPost(string $token, string $route, array $payload): array
    {
        $envelope = qdb_sensitive_envelope(
            json_encode($payload, JSON_THROW_ON_ERROR),
            $token
        );

        return $this->withMobileToken($token, 'POST', $route, $envelope);
    }
}
