<?php

declare(strict_types=1);

namespace App\Service;

use Exception;
use RuntimeException;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
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;

final readonly class PublicHolidayService implements PublicHolidayServiceInterface
{
    /**
     * @var list<string>
     */
    private array $excludedLabels;

    public function __construct(
        private HttpClientInterface $client,
        private string $holidayUrl,
        private CacheInterface $cache,
        string $excludedLabels = '',
    ) {
        $this->excludedLabels = array_values(array_filter(
            array_map('trim', explode(',', $excludedLabels)),
            static fn (string $label): bool => '' !== $label,
        ));
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     * @throws Exception
     */
    public function getHolidaysDay(string $zone): array
    {
        $zone = strtolower(trim($zone));
        $key  = "public_holidays_{$zone}_all";

        $holidays = $this->cache->get($key, function (ItemInterface $item) use ($zone): array {
            $item->expiresAfter(30 * 86400);
            $url = $this->holidayUrl."{$zone}.json";

            try {
                $response = $this->client->request(
                    'GET',
                    $url
                );
            } catch (TransportExceptionInterface) {
                throw new RuntimeException('Unable to reach public holidays API.');
            } catch (ClientExceptionInterface) {
                throw new RuntimeException('Invalid zone provided for public holidays.');
            } catch (ServerExceptionInterface) {
                throw new RuntimeException('Public holidays API is temporarily unavailable.');
            } catch (Throwable) {
                throw new RuntimeException('Unexpected error while fetching public holidays.');
            }

            return json_decode($response->getContent(), true);
        });

        return $this->applyExclusions($holidays);
    }

    /**
     * @throws TransportExceptionInterface
     * @throws ServerExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ClientExceptionInterface
     */
    public function getHolidaysDayByYears(string $zone, string $years): array
    {
        return $this->applyExclusions($this->fetchHolidaysByYears($zone, $years));
    }

    public function getRawHolidaysDayByYears(string $zone, string $years): array
    {
        return $this->fetchHolidaysByYears($zone, $years);
    }

    /**
     * @return array<string, string>
     */
    private function fetchHolidaysByYears(string $zone, string $years): array
    {
        $zone  = strtolower(trim($zone));
        $years = trim($years);
        $key   = "public_holidays_{$zone}_{$years}";

        return $this->cache->get($key, function (ItemInterface $item) use ($zone, $years): array {
            $item->expiresAfter(30 * 86400);
            $url = $this->holidayUrl."{$zone}/{$years}.json";

            try {
                $response = $this->client->request('GET', $url);
            } catch (TransportExceptionInterface) {
                throw new RuntimeException('Unable to reach public holidays API.');
            } catch (ClientExceptionInterface) {
                throw new RuntimeException('Invalid zone or year provided for public holidays.');
            } catch (ServerExceptionInterface) {
                throw new RuntimeException('Public holidays API is temporarily unavailable.');
            } catch (Throwable) {
                throw new RuntimeException('Unexpected error while fetching public holidays.');
            }

            return json_decode($response->getContent(), true);
        });
    }

    /**
     * @param array<string, string> $holidays
     *
     * @return array<string, string>
     */
    private function applyExclusions(array $holidays): array
    {
        if ([] === $this->excludedLabels) {
            return $holidays;
        }

        return array_filter(
            $holidays,
            fn (string $label): bool => !in_array($label, $this->excludedLabels, true),
        );
    }
}
