<?php

declare(strict_types=1);

namespace App\Services;

use DateInterval;
use DateTimeImmutable;
use DateTimeZone;

final class HolidayService
{
    private const STATES_WITH_EPIPHANY = ['BW', 'BY', 'ST'];
    private const STATES_WITH_WOMENS_DAY = ['BE', 'MV'];
    private const STATES_WITH_CORPUS_CHRISTI = ['BW', 'BY', 'HE', 'NW', 'RP', 'SL'];
    private const STATES_WITH_ASSUMPTION = ['BY', 'SL'];
    private const STATES_WITH_REFORMATION = ['BB', 'HB', 'HH', 'MV', 'NI', 'SN', 'ST', 'SH', 'TH'];
    private const STATES_WITH_ALL_SAINTS = ['BW', 'BY', 'NW', 'RP', 'SL'];
    private const STATES_WITH_REPENTANCE = ['SN'];

    public function forRange(array $tenant, string $from, string $to): array
    {
        $timezone = new DateTimeZone((string) ($tenant['timezone'] ?? 'Europe/Berlin'));
        $state = strtoupper((string) ($tenant['federal_state'] ?? 'BE'));
        $start = (new DateTimeImmutable($from, new DateTimeZone('UTC')))->setTimezone($timezone)->setTime(0, 0);
        $end = (new DateTimeImmutable($to, new DateTimeZone('UTC')))->setTimezone($timezone)->setTime(0, 0);

        $years = [];
        $day = $start;
        while ($day <= $end) {
          $years[(int) $day->format('Y')] = true;
          $day = $day->add(new DateInterval('P1D'));
        }

        $holidaysByDate = [];
        foreach (array_keys($years) as $year) {
            foreach ($this->holidaysForYear((int) $year, $state, $timezone) as $date => $name) {
                $holidaysByDate[$date] = $name;
            }
        }

        $result = [];
        $day = $start;
        while ($day < $end) {
            $key = $day->format('Y-m-d');
            $result[$key] = [
                'isSunday' => (int) $day->format('N') === 7,
                'holidayName' => $holidaysByDate[$key] ?? null,
            ];
            $day = $day->add(new DateInterval('P1D'));
        }

        return $result;
    }

    private function holidaysForYear(int $year, string $state, DateTimeZone $timezone): array
    {
        $easter = (new DateTimeImmutable('@' . (string) easter_date($year)))->setTimezone($timezone)->setTime(0, 0);
        $holidays = [
            $this->dateKey($year, 1, 1, $timezone) => 'Neujahr',
            $easter->sub(new DateInterval('P2D'))->format('Y-m-d') => 'Karfreitag',
            $easter->add(new DateInterval('P1D'))->format('Y-m-d') => 'Ostermontag',
            $this->dateKey($year, 5, 1, $timezone) => 'Tag der Arbeit',
            $easter->add(new DateInterval('P39D'))->format('Y-m-d') => 'Christi Himmelfahrt',
            $easter->add(new DateInterval('P50D'))->format('Y-m-d') => 'Pfingstmontag',
            $this->dateKey($year, 10, 3, $timezone) => 'Tag der Deutschen Einheit',
            $this->dateKey($year, 12, 25, $timezone) => '1. Weihnachtstag',
            $this->dateKey($year, 12, 26, $timezone) => '2. Weihnachtstag',
        ];

        if (in_array($state, self::STATES_WITH_EPIPHANY, true)) {
            $holidays[$this->dateKey($year, 1, 6, $timezone)] = 'Heilige Drei Koenige';
        }
        if (in_array($state, self::STATES_WITH_WOMENS_DAY, true)) {
            $holidays[$this->dateKey($year, 3, 8, $timezone)] = 'Internationaler Frauentag';
        }
        if (in_array($state, self::STATES_WITH_CORPUS_CHRISTI, true)) {
            $holidays[$easter->add(new DateInterval('P60D'))->format('Y-m-d')] = 'Fronleichnam';
        }
        if (in_array($state, self::STATES_WITH_ASSUMPTION, true)) {
            $holidays[$this->dateKey($year, 8, 15, $timezone)] = 'Mariae Himmelfahrt';
        }
        if (in_array($state, self::STATES_WITH_REFORMATION, true)) {
            $holidays[$this->dateKey($year, 10, 31, $timezone)] = 'Reformationstag';
        }
        if (in_array($state, self::STATES_WITH_ALL_SAINTS, true)) {
            $holidays[$this->dateKey($year, 11, 1, $timezone)] = 'Allerheiligen';
        }
        if (in_array($state, self::STATES_WITH_REPENTANCE, true)) {
            $holidays[$this->repentanceDay($year, $timezone)->format('Y-m-d')] = 'Buss- und Bettag';
        }

        return $holidays;
    }

    private function dateKey(int $year, int $month, int $day, DateTimeZone $timezone): string
    {
        return (new DateTimeImmutable(sprintf('%04d-%02d-%02d', $year, $month, $day), $timezone))->format('Y-m-d');
    }

    private function repentanceDay(int $year, DateTimeZone $timezone): DateTimeImmutable
    {
        $day = new DateTimeImmutable(sprintf('%04d-11-23', $year), $timezone);
        while ((int) $day->format('N') !== 3) {
            $day = $day->sub(new DateInterval('P1D'));
        }
        return $day;
    }
}
