<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\SupportCenterException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * Loongdom Support Center Open Host API 传输层。
 */
final class SupportCenterOpenApiTransport
{
    public function __construct(
        private readonly SupportCenterConnectivityGuard $connectivityGuard,
        private readonly SupportCenterCredentialResolver $credentialResolver,
        private readonly SupportCenterUserFacingErrors $userFacingErrors,
    ) {}

    /**
     * @param  array<string, mixed>  $query
     * @return array<string, mixed>
     */
    public function get(string $endpoint, array $query = [], ?string $operation = null): array
    {
        return $this->send('GET', $endpoint, $query, [], $operation);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function post(string $endpoint, array $payload = [], ?string $operation = null): array
    {
        return $this->send('POST', $endpoint, [], $payload, $operation);
    }

    /**
     * @param  array<string, mixed>  $query
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function send(
        string $method,
        string $endpoint,
        array $query,
        array $payload,
        ?string $operation,
    ): array {
        $this->connectivityGuard->assertRealCallsAllowed($operation);

        $url = $this->buildUrl($endpoint);
        $operationName = $operation ?? $endpoint;

        try {
            $response = $this->pendingRequest()->send($method, $url, [
                'query' => $query,
                'json' => $payload,
            ]);
        } catch (ConnectionException $exception) {
            $this->logFailure($operationName, $endpoint, null, null, null, $exception);

            throw SupportCenterException::networkTimeout($operationName, $exception);
        } catch (Throwable $exception) {
            $this->logFailure($operationName, $endpoint, null, null, null, $exception);

            throw SupportCenterException::remoteServiceUnavailable(
                operation: $operationName,
                previous: $exception,
            );
        }

        return $this->parseEnvelopeResponse($response, $operationName, $endpoint);
    }

    private function pendingRequest(): PendingRequest
    {
        $hostToken = (string) ($this->credentialResolver->hostToken() ?? '');
        $hostKey = $this->credentialResolver->hostKey();
        $timeout = max(1, (int) config('dberp.support_center.timeout', 10));
        $connectTimeout = max(1, (int) config('dberp.support_center.connect_timeout', 5));

        $request = Http::withToken($hostToken)
            ->acceptJson()
            ->asJson()
            ->timeout($timeout)
            ->connectTimeout($connectTimeout);

        if (is_string($hostKey) && trim($hostKey) !== '') {
            $request = $request->withHeaders([
                'X-Host-Key' => trim($hostKey),
            ]);
        }

        return $request;
    }

    private function buildUrl(string $endpoint): string
    {
        $baseUrl = rtrim((string) ($this->credentialResolver->normalizedBaseUrl() ?? ''), '/');

        return $baseUrl.'/'.ltrim($endpoint, '/');
    }

    /**
     * @return array<string, mixed>
     */
    private function parseEnvelopeResponse(Response $response, string $operation, string $endpoint): array
    {
        $httpStatus = $response->status();

        try {
            $decoded = $response->json();
        } catch (Throwable $exception) {
            $this->logFailure($operation, $endpoint, $httpStatus, null, null, $exception);

            throw SupportCenterException::invalidResponseFormat(
                httpStatus: $httpStatus,
                operation: $operation,
                previous: $exception,
            );
        }

        if (! is_array($decoded)) {
            $this->logFailure($operation, $endpoint, $httpStatus, null, null);

            throw SupportCenterException::invalidResponseFormat(
                httpStatus: $httpStatus,
                operation: $operation,
            );
        }

        if (! array_key_exists('success', $decoded)) {
            $this->logFailure(
                $operation,
                $endpoint,
                $httpStatus,
                null,
                is_string($decoded['request_id'] ?? null) ? $decoded['request_id'] : null,
            );

            throw SupportCenterException::invalidResponseFormat(
                httpStatus: $httpStatus,
                requestId: is_string($decoded['request_id'] ?? null) ? $decoded['request_id'] : null,
                operation: $operation,
            );
        }

        if ($decoded['success'] !== true) {
            $this->logFailure(
                $operation,
                $endpoint,
                $httpStatus,
                is_string($decoded['error_code'] ?? null) ? $decoded['error_code'] : null,
                is_string($decoded['request_id'] ?? null) ? $decoded['request_id'] : null,
            );

            throw SupportCenterException::fromFailureEnvelope($decoded, $httpStatus, $operation);
        }

        $data = $decoded['data'] ?? [];

        if ($data === null) {
            return [];
        }

        if (is_array($data)) {
            if (is_string($decoded['request_id'] ?? null) && trim($decoded['request_id']) !== '') {
                $data['request_id'] = trim($decoded['request_id']);
            }

            /** @var array<string, mixed> $data */
            return $data;
        }

        return ['value' => $data];
    }

    private function logFailure(
        string $operation,
        string $endpoint,
        ?int $httpStatus,
        ?string $errorCode,
        ?string $requestId,
        ?Throwable $exception = null,
    ): void {
        $context = array_merge(
            [
                'operation' => $operation,
                'endpoint' => $endpoint,
                'http_status' => $httpStatus,
                'error_code' => $errorCode,
                'request_id' => $requestId,
            ],
            $this->connectivityGuard->credentialPresence(),
        );

        if ($exception !== null) {
            $context['exception_class'] = $exception::class;
            $context['exception_message'] = $this->sanitizeLogMessage($exception->getMessage());
        }

        Log::warning('support_center.open_host.request_failed', $context);
    }

    private function sanitizeLogMessage(string $message): string
    {
        if ($this->userFacingErrors->containsSensitiveValue($message)) {
            return 'remote_error_message_redacted';
        }

        return $message;
    }
}
