<?php

namespace Velis\Timezone;

use DateInvalidTimeZoneException;
use DateTimeZone;
use Velis\Exception as VelisException;

final readonly class DateTimeZoneProvider
{
    public function __construct(
        private bool $safe = true,
        private string $default = 'Europe/Warsaw'
    ) {
    }

    public const array TIMEZONE_MAPPING = [
        'Europe/Belfast' => 'Europe/London',
        'Europe/Uzhgorod' => 'Europe/Kyiv',
        'Europe/Zaporozhye' => 'Europe/Kyiv',
        'Europe/Nicosia' => 'Asia/Nicosia',
        'Europe/Tiraspol' => 'Europe/Chisinau',
        'Europe/Kiev' => 'Europe/Kyiv',
        'Pacific/Truk' => 'Pacific/Chuuk',
        'Pacific/Johnston' => 'Pacific/Honolulu',
        'Pacific/Samoa' => 'Pacific/Pago_Pago',
        'Pacific/Yap' => 'Pacific/Chuuk',
        'Pacific/Ponape' => 'Pacific/Pohnpei',
        'Pacific/Enderbury' => 'Pacific/Kanton',
        'Canada/Central' => 'America/Winnipeg',
        'Canada/Eastern' => 'America/Toronto',
        'Canada/Newfoundland' => 'America/St_Johns',
        'Canada/Pacific' => 'America/Vancouver',
        'Canada/Yukon' => 'America/Whitehorse',
        'Canada/Atlantic' => 'America/Halifax',
        'Canada/Mountain' => 'America/Edmonton',
        'Canada/Saskatchewan' => 'America/Regina',
        'Etc/Greenwich' => 'Africa/Abidjan',
        'Etc/Zulu' => 'Africa/Abidjan',
        'Etc/UTC' => 'Africa/Abidjan',
        'Etc/Universal' => 'Africa/Abidjan',
        'Etc/UCT' => 'Africa/Abidjan',
        'Atlantic/Jan_Mayen' => 'Europe/Oslo',
        'Atlantic/Faeroe' => 'Atlantic/Faroe',
        'Brazil/Acre' => 'America/Rio_Branco',
        'Brazil/East' => 'America/Sao_Paulo',
        'Brazil/West' => 'America/Manaus',
        'Brazil/DeNoronha' => 'America/Noronha',
        'US/Central' => 'America/Chicago',
        'US/Eastern' => 'America/New_York',
        'US/Pacific' => 'America/Los_Angeles',
        'US/Alaska' => 'America/Anchorage',
        'US/Arizona' => 'America/Phoenix',
        'US/Aleutian' => 'America/Adak',
        'US/Michigan' => 'America/Detroit',
        'US/Samoa' => 'Pacific/Pago_Pago',
        'US/Mountain' => 'America/Denver',
        'US/Hawaii' => 'Pacific/Honolulu',
        'America/Fort_Wayne' => 'America/Indiana/Indianapolis',
        'America/Coral_Harbour' => 'America/Atikokan',
        'America/Porto_Acre' => 'America/Rio_Branco',
        'America/Rosario' => 'America/Argentina/Cordoba',
        'America/Godthab' => 'America/Nuuk',
        'America/Nipigon' => 'America/Toronto',
        'America/Mendoza' => 'America/Argentina/Mendoza',
        'America/Shiprock' => 'America/Denver',
        'America/Knox_IN' => 'America/Indiana/Knox',
        'America/Rainy_River' => 'America/Winnipeg',
        'America/Ensenada' => 'America/Tijuana',
        'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
        'America/Atka' => 'America/Adak',
        'America/Virgin' => 'America/St_Thomas',
        'America/Cordoba' => 'America/Argentina/Cordoba',
        'America/Louisville' => 'America/Kentucky/Louisville',
        'America/Catamarca' => 'America/Argentina/Catamarca',
        'America/Yellowknife' => 'America/Edmonton',
        'America/Montreal' => 'America/Toronto',
        'America/Pangnirtung' => 'America/Iqaluit',
        'America/Santa_Isabel' => 'America/Tijuana',
        'America/Thunder_Bay' => 'America/Toronto',
        'America/Jujuy' => 'America/Argentina/Jujuy',
        'America/Argentina/ComodRivadavia' => 'America/Argentina/Rio_Gallegos',
        'Chile/EasterIsland' => 'Pacific/Easter',
        'Chile/Continental' => 'America/Santiago',
        'Mexico/BajaNorte' => 'America/Tijuana',
        'Mexico/General' => 'America/Mexico_City',
        'Mexico/BajaSur' => 'America/Mazatlan',
        'Etc/GMT-7' => 'Asia/Bangkok',
        'Etc/GMT+7' => 'America/Phoenix',
        'Etc/GMT+8' => 'Pacific/Pitcairn',
        'Japan' => 'Asia/Tokyo',
    ];

    public function getMappedIdentifier(string $identifier): ?string
    {
        return self::TIMEZONE_MAPPING[$identifier];
    }

    private function notifyInvalidIdentifier(string $identifier): void
    {
        VelisException::raise("Invalid timezone identifier '$identifier' provided");
    }

    private function notifyInvalidMapping(string $identifier, string $mappedIdentifier): void
    {
        VelisException::raise(
            "Invalid mapped timezone identifier '$mappedIdentifier' for input '$identifier' provided"
        );
    }

    public function get(string $identifier): DateTimeZone
    {
        if (!$this->safe) {
            return new DateTimeZone($identifier);
        }

        try {
            return new DateTimeZone($identifier);
        } catch (DateInvalidTimeZoneException $e) {
            $matchingIdentifier = $this->getMappedIdentifier($identifier);

            if (!$matchingIdentifier) {
                $this->notifyInvalidIdentifier($identifier);
            }

            $validIdentifiers = DateTimeZone::listIdentifiers();

            if (in_array($matchingIdentifier, $validIdentifiers, true)) {
                return new DateTimeZone($matchingIdentifier);
            }

            $this->notifyInvalidMapping($identifier, $matchingIdentifier);
            return new DateTimeZone($this->default);
        }
    }
}
