<?php

namespace App\Service\Adapter;

use AllowDynamicProperties;
use App\Kernel;
use DateTimeImmutable;
use Exception;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable;

#[AllowDynamicProperties] class Orm
{
    const SUCCESS_HTTP_STATUS = 200;
    const MAX_AGE_PRICE_SECONDS = 240;
    const CLIENT_MAX_DURATION_SECONDS = 20;
    const CLIENT_TIMEOUT_SECONDS = 10;

    /**
     * @var ContainerInterface
     */
    private ContainerInterface $container;

    /**
     * The current URL read from the list of possible URLs. including fallback urls
     * @var string
     */
    private string $currentUrl;

    /**
     * List of possible URLs including fallback URLs
     * @var array
     */
    private array $urlList;

    /**
     * @var string
     */
    private string $user;

    /**
     * @var string
     */
    private string $pwd;

    /**
     * @var HttpClientInterface
     */
    private HttpClientInterface $httpClient;

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

    /**
     * @var array|string
     */
    private array|string $shopIds;

    /**
     * @param Kernel $kernel
     * @param HttpClientInterface $httpClient
     * @param LoggerInterface $logger
     */
    public function __construct(Kernel $kernel, HttpClientInterface $httpClient, LoggerInterface $logger)
    {
        $this->container = $kernel->getContainer();
        $this->httpClient = $httpClient;
        $this->logger = $logger;
    }

    /**
     * @param array $shopIds
     * @return array|null
     */
    public function call(array $shopIds): ?array
    {
        $result = null;
        $this->shopIds = implode(',', $shopIds);
        $this->setConfig();

        foreach ($this->urlList as $url) {
            $this->currentUrl = $url;
            $this->setParams();

            try {
                $result = $this->runCall();
                break;
            } catch (Throwable $throwable) {
                $this->logger->error("Error in {$url}: " . $throwable->getMessage());
            }
        }

        if ($result === null) {
            throw new RuntimeException("All API calls failed. See errors before this message");
        }

        return $result;
    }

    /**
     * @return void
     */
    private function setConfig(): void
    {
        $url = $this->container->getParameter('mitho.import.orm.url');
        $urlBckp1 = $this->container->getParameter('mitho.import.orm.urlBckp1');
        $urlBckp2 = $this->container->getParameter('mitho.import.orm.urlBckp2');
        $this->user = $this->container->getParameter('mitho.import.orm.user');
        $this->pwd = $this->container->getParameter('mitho.import.orm.pwd');
        $this->urlList = array_filter([$url, $urlBckp1, $urlBckp2]);
    }

    /**
     * @return void
     */
    private function setParams(): void
    {
        $this->currentUrl = $this->currentUrl . '?shopids=' . $this->shopIds;
    }

    /**
     * @return string[]
     */
    private function getHeaders(): array
    {
        return [
            'Authorization: Basic ' . base64_encode($this->user . ':' . $this->pwd),
            'User-Agent' => 'MtoPriceService v2'
        ];
    }

    /**
     * @return int[]
     */
    private function getOptions(): array
    {
        return [
            'headers' => $this->getHeaders(),
            'timeout' => self::CLIENT_TIMEOUT_SECONDS,
            'max_duration' => self::CLIENT_MAX_DURATION_SECONDS,
        ];
    }

    /**
     * @return array|null
     * @throws Exception
     */
    private function runCall(): ?array
    {
        try {
            $response = $this->httpClient->request(
                'GET',
                $this->currentUrl,
                $this->getOptions()
            );

            $statusCode = $response->getStatusCode() ?: 500;

            if ($statusCode !== self::SUCCESS_HTTP_STATUS) {
                throw new HttpException($statusCode, "Unexpected HTTP status: $statusCode");
            }

            $responseArray = $response->toArray();
            $responseArray = !empty($responseArray) ? $responseArray : throw new RuntimeException('API response is empty, expected valid JSON data.');

            //Check the age of data
            $this->validateMaxAgeOfData($responseArray);

            return $responseArray;

        } catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface $e) {
            throw new HttpException($e->getCode(), 'HTTP Client Error: ' . $e->getMessage(), $e);
        } catch (Throwable $e) {
            throw new RuntimeException('Unexpected API system error: ' . $e->getMessage(), 0, $e);
        }
    }

    /**
     * Check the age of the data and throw an exception if the data is older than the maximum time period
     * @param array $responseArray
     * @return void
     */
    private function validateMaxAgeOfData(array $responseArray): void
    {
        $lastUpdate = $responseArray['data']['lastupdate'] ?? null;

        try {
            $dateTime = new DateTimeImmutable($lastUpdate);
        } catch (Exception) {
            throw new RuntimeException('API response was not accepted at '.date('Y-m-d H:i:s').', because no valid timestamp was found.');
        }

        $timeSpan = (time() - $dateTime->getTimestamp());
        if ($timeSpan > self::MAX_AGE_PRICE_SECONDS) {
            throw new RuntimeException('API response was not accepted at '.date('Y-m-d H:i:s').', because the date is out of range. Max age: ' . self::MAX_AGE_PRICE_SECONDS . ' | age of data: ' . $timeSpan);
        }
    }

}