<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Http\Client;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class LoggingClient implements ClientInterface
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var array<string, mixed>
     */
    private $options;

    /**
     * @param array<string, mixed> $options
     */
    public function __construct(ClientInterface $client, LoggerInterface $logger, array $options = [])
    {
        $this->client = $client;
        $this->logger = $logger;
        $this->options = $options;
    }

    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $this->logRequest($request);

        try {
            $response = $this->client->sendRequest($request);
            $this->logResponse($request, $response);

            return $response;
        } catch (\Throwable $e) {
            $this->logError($request, $e);

            throw $e;
        }
    }

    private function logRequest(RequestInterface $request): void
    {
        $this->logger->info('Request: "{method} {uri}"', [
            'method' => $request->getMethod(),
            'uri' => (string) $request->getUri(),
        ]);

        if ('' !== $body = (string) $request->getBody()) {
            $this->logger->debug('Request body: "{body}"', ['body' => $body]);
        }
    }

    private function logResponse(RequestInterface $request, ResponseInterface $response): void
    {
        $context = [
            'status_code' => $statusCode = $response->getStatusCode(),
            'uri' => (string) $request->getUri(),
        ];

        if (400 <= $statusCode) {
            if ('' !== $body = (string) $response->getBody()) {
                $context['body'] = $body;
            }

            if ($headers = $this->collectResponseHeaders($response)) {
                $context['headers'] = $headers;
            }

            $this->logger->error('Response: "{status_code} {uri}"', $context);
        } elseif (300 <= $statusCode) {
            $context['location'] = $response->getHeaderLine('location');
            $this->logger->info('Response: "{status_code} {uri}"', $context);
        } else {
            $this->logger->info('Response: "{status_code} {uri}"', $context);
            $this->logResponseBody($response);
        }
    }

    private function logError(RequestInterface $request, \Throwable $e): void
    {
        if ($e instanceof NetworkExceptionInterface) {
            $this->logger->error('Network error for {uri}: "{message}"', [
                'uri' => (string) $e->getRequest()->getUri(),
                'message' => $e->getMessage(),
            ]);
        } else {
            $this->logger->critical('Unexpected error for {uri}: {exception}', [
                'uri' => (string) $request->getUri(),
                'exception' => $e,
            ]);
        }
    }

    private function logResponseBody(ResponseInterface $response): void
    {
        if ('' === $body = (string) $response->getBody()) {
            return;
        }

        if (!isset($this->options['max_response_body_size']) || mb_strlen($body, 'UTF-8') <= $this->options['max_response_body_size']) {
            $context = ['body' => $body];
        } else {
            $context = [
                'body' => mb_substr($body, 0, $this->options['max_response_body_size'], 'UTF-8') . '...',
                'body_size' => $response->getBody()->getSize(),
            ];
        }

        if ($headers = $this->collectResponseHeaders($response)) {
            $context['headers'] = $headers;
        }

        $this->logger->debug('Response body: "{body}"', $context);
    }

    private function collectResponseHeaders(ResponseInterface $response): array
    {
        if (!isset($this->options['log_response_headers'])) {
            return [];
        }

        if (true === $this->options['log_response_headers']) {
            return array_map(static function (string $name) use ($response): string {
                return $response->getHeaderLine($name);
            }, array_keys($response->getHeaders()));
        }

        $headers = [];

        foreach ($this->options['log_response_headers'] as $header) {
            if ($response->hasHeader($header)) {
                $headers[$header] = $response->getHeaderLine($header);
            }
        }

        return $headers;
    }
}
