<?php

namespace MintyPHP\Http;

use MintyPHP\Http\Input\FormErrors;
use MintyPHP\Service\Access\AuthorizationService;
use MintyPHP\Service\Audit\ApiAuditService;

class ApiResponse
{
    /** @var (callable(): ApiAuditService)|null */
    private static $apiAuditServiceResolver = null;
    /** @var (callable(): AuthorizationService)|null */
    private static $authorizationServiceResolver = null;
    /** @var (callable(): ApiSystemAuditReporter)|null */
    private static $apiSystemAuditReporterResolver = null;

    public static function configure(
        callable $apiAuditServiceResolver,
        callable $authorizationServiceResolver,
        callable $apiSystemAuditReporterResolver
    ): void {
        self::$apiAuditServiceResolver = $apiAuditServiceResolver;
        self::$authorizationServiceResolver = $authorizationServiceResolver;
        self::$apiSystemAuditReporterResolver = $apiSystemAuditReporterResolver;
    }

    public static function success(array $data = [], int $status = 200): never
    {
        self::send($data, $status);
    }

    public static function created(array $data = []): never
    {
        self::success($data, 201);
    }

    public static function noContent(): never
    {
        self::send(null, 204);
    }

    public static function error(string $error, int $status = 400, array $extra = []): never
    {
        $body = self::buildErrorBody($error, $extra);
        self::send($body, $status, $error);
    }

    public static function unauthorized(): never
    {
        self::error('unauthorized', 401);
    }

    public static function forbidden(string $detail = 'forbidden'): never
    {
        self::error($detail, 403);
    }

    public static function notFound(string $detail = 'not_found'): never
    {
        self::error($detail, 404);
    }

    public static function methodNotAllowed(): never
    {
        self::error('method_not_allowed', 405);
    }

    public static function validationError(array $errors): never
    {
        self::error('validation_error', 422, ['errors' => $errors]);
    }

    public static function validationFromFormErrors(FormErrors $errors): never
    {
        self::validationError($errors->toArray());
    }

    public static function tooManyRequests(int $retryAfter = 60): never
    {
        $retryAfter = max(1, $retryAfter);
        self::send(
            self::buildErrorBody('rate_limit_exceeded', ['retry_after' => $retryAfter]),
            429,
            'rate_limit_exceeded',
            ['Retry-After: ' . $retryAfter]
        );
    }

    /**
     * Transform a service result (['ok' => bool, ...] pattern) into an API response.
     */
    public static function fromServiceResult(array $result, int $successStatus = 200): never
    {
        if ($result['ok'] ?? false) {
            $data = $result;
            unset($data['ok']);
            self::success($data, $successStatus);
        }

        $status = (int) ($result['status'] ?? 0);
        if ($status < 400) {
            $status = 422;
        }
        $error = (string) ($result['error'] ?? 'operation_failed');
        $errors = $result['errors'] ?? [];
        $extra = [];
        if ($errors) {
            $extra['errors'] = $errors;
        }
        self::error($error, $status, $extra);
    }

    /**
     * Read JSON request body into an array.
     */
    public static function readJsonBody(): array
    {
        $raw = file_get_contents('php://input');
        if ($raw === false || $raw === '') {
            return [];
        }
        $data = json_decode($raw, true);
        return is_array($data) ? $data : [];
    }

    /**
     * Require specific HTTP method(s).
     */
    public static function requireMethod(string ...$methods): void
    {
        $method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
        if (!in_array($method, $methods, true)) {
            self::methodNotAllowed();
        }
    }

    /**
     * Require API authentication.
     */
    public static function requireAuth(): void
    {
        if (!ApiAuth::isAuthenticated()) {
            self::unauthorized();
        }
    }

    public static function requireAbility(string $ability, array $context = []): void
    {
        self::requireAuth();
        $decision = self::authorizationService()->authorize($ability, [
            'actor_user_id' => ApiAuth::userId(),
            'scoped_tenant_id' => ApiAuth::scopedTenantId(),
            ...$context,
        ]);
        if (!$decision->isAllowed()) {
            self::forbidden();
        }
    }

    private static function send(?array $body, int $status, ?string $errorCode = null, array $headers = []): never
    {
        $requestId = self::requestId();
        http_response_code($status);
        header('X-Request-Id: ' . $requestId);
        foreach ($headers as $headerLine) {
            header($headerLine);
        }

        if ($body === null) {
            self::finishSystemAuditReporter($status, $errorCode);
            self::apiAuditService()->finish($status, $errorCode);
            die();
        }

        if (!array_key_exists('request_id', $body)) {
            $body['request_id'] = $requestId;
        }

        $json = json_encode($body, JSON_UNESCAPED_UNICODE);
        if (!is_string($json)) {
            $status = 500;
            http_response_code($status);
            $errorCode = $errorCode ?: 'serialization_error';
            $fallbackBody = self::buildErrorBody('serialization_error');
            $fallbackBody['request_id'] = $requestId;
            $json = json_encode($fallbackBody, JSON_UNESCAPED_UNICODE)
                ?: '{"ok":false,"error_code":"serialization_error","request_id":"unknown","details":{}}';
        }

        header('Content-Type: application/json; charset=utf-8');
        self::finishSystemAuditReporter($status, $errorCode);
        self::apiAuditService()->finish($status, $errorCode);
        die($json);
    }

    private static function requestId(): string
    {
        $requestId = RequestContext::currentId();
        if (is_string($requestId) && trim($requestId) !== '') {
            return trim($requestId);
        }

        $requestId = self::apiAuditService()->currentRequestId();
        if (is_string($requestId) && trim($requestId) !== '') {
            return trim($requestId);
        }

        return RequestContext::id();
    }

    private static function buildErrorBody(string $errorCode, array $details = []): array
    {
        $normalizedErrorCode = trim($errorCode);
        if ($normalizedErrorCode === '') {
            $normalizedErrorCode = 'unknown_error';
        }

        $normalizedDetails = self::normalizeDetails($details);

        return [
            'ok' => false,
            'error_code' => $normalizedErrorCode,
            'details' => $normalizedDetails,
        ];
    }

    // Empty details must be stdClass so json_encode produces {} instead of [].
    private static function normalizeDetails(array $details): array|\stdClass
    {
        if ($details === []) {
            return new \stdClass();
        }

        if (array_is_list($details)) {
            return ['items' => $details];
        }

        return $details;
    }

    private static function apiAuditService(): ApiAuditService
    {
        if (!is_callable(self::$apiAuditServiceResolver)) {
            throw new \RuntimeException('ApiResponse is not configured for dependency: ' . ApiAuditService::class);
        }

        $service = (self::$apiAuditServiceResolver)();
        if (!$service instanceof ApiAuditService) {
            throw new \RuntimeException('ApiResponse resolver returned invalid dependency: ' . ApiAuditService::class);
        }

        return $service;
    }

    private static function authorizationService(): AuthorizationService
    {
        if (!is_callable(self::$authorizationServiceResolver)) {
            throw new \RuntimeException('ApiResponse is not configured for dependency: ' . AuthorizationService::class);
        }

        $service = (self::$authorizationServiceResolver)();
        if (!$service instanceof AuthorizationService) {
            throw new \RuntimeException('ApiResponse resolver returned invalid dependency: ' . AuthorizationService::class);
        }

        return $service;
    }

    private static function systemAuditReporter(): ApiSystemAuditReporter
    {
        if (!is_callable(self::$apiSystemAuditReporterResolver)) {
            throw new \RuntimeException('ApiResponse is not configured for dependency: ' . ApiSystemAuditReporter::class);
        }

        $service = (self::$apiSystemAuditReporterResolver)();
        if (!$service instanceof ApiSystemAuditReporter) {
            throw new \RuntimeException('ApiResponse resolver returned invalid dependency: ' . ApiSystemAuditReporter::class);
        }

        return $service;
    }

    private static function finishSystemAuditReporter(int $statusCode, ?string $errorCode = null): void
    {
        try {
            self::systemAuditReporter()->finish($statusCode, $errorCode);
        } catch (\Throwable) {
            // fail-open
        }
    }
}
