<?php

namespace KupShop\KupShopBundle\Util;

use Yasumi\ProviderInterface;
use Yasumi\Yasumi;

class DateUtil
{
    public static function calcWorkingDaysWithHours($days, $hours, $seconds, $fromDay = null, $workdayFunction = null)
    {
        $secondsTotal = $days * 24 * 60 + $hours * 60 + $seconds;

        $days = $secondsTotal > 0 ? floor($secondsTotal / 24 / 60) : ceil($secondsTotal / 24 / 60);

        $seconds = $secondsTotal - $days * 24 * 60;

        $fromDay = self::calcWorkingDays($days, $fromDay, $workdayFunction);

        if (!$seconds) {
            return $fromDay;
        }

        $timeDiff = new \DateInterval('PT'.abs($seconds).'M');
        $timeDiff->invert = $seconds < 0;
        $fromDay->add($timeDiff);

        if (!is_callable($workdayFunction)) {
            $workdayFunction = [self::class, 'isWorkday'];
        }

        $timeDiffDay = new \DateInterval('P1D');
        $timeDiffDay->invert = $seconds < 0;

        $i = 0;
        while (!$workdayFunction($fromDay)) {
            if ($i++ > 20) {
                getRaven()->captureMessage('Calculate working days from minutes error');
                break;
            }
            $fromDay->add($timeDiffDay);
        }

        return $fromDay;
    }

    /**
     * @param null $fromDay
     * @param null $workdayFunction
     *
     * @return \DateTime|null
     *
     * @throws \ReflectionException
     */
    public static function calcWorkingDays($days, $fromDay = null, $workdayFunction = null, ?array $countries = null)
    {
        if (empty($fromDay)) {
            $fromDay = new \DateTime();
        }

        if ($days > 20) {
            // if more than 20 days, don't calculate working days, just add days
            // see https://sentry.wpj.cz/organizations/wpj/issues/467352/ - Calculate working days error, 101 loops reached
            $fromDay->add(new \DateInterval('P'.abs($days).'D'));

            return $fromDay;
        }

        $dayDiff = new \DateInterval('P1D');
        if ($days < 0) {
            $dayDiff->invert = 1;
        }
        $days = abs($days);

        if (!is_callable($workdayFunction)) {
            $workdayFunction = [self::class, 'isWorkday'];
        }

        $i = 0;
        while ($days > 0) {
            if ($i++ > 100) {
                getRaven()->captureMessage('Calculate working days error, 101 loops reached');
                break;
            }
            $fromDay->add($dayDiff);
            if ($workdayFunction($fromDay, $countries)) {
                $days--;
            }
        }

        return $fromDay;
    }

    /**
     * @param $date \DateTime
     *
     * @return bool
     *
     * @throws \ReflectionException
     */
    public static function isWorkday($date, ?array $countries = null)
    {
        if (empty($countries)) {
            $countries = ['CZ'];
        }

        $result = true;

        foreach ($countries as $country) {
            $result = $result && static::createHolidays($country, (int) $date->format('Y'))->isWorkingDay($date);
        }

        return $result;
    }

    public static function compareTimes($first, $second = null): bool
    {
        $first = new \DateTime('2000-01-01 '.$first);

        if (!$second) {
            $second = date('H:i:s');
        }
        $second = new \DateTime('2000-01-01 '.$second);

        return $first < $second;
    }

    public static function isOpen(array $opening_hours, $dayOfWeek = null, $compareTime = null): array
    {
        if ($dayOfWeek === null) {
            $dayOfWeek = (new \DateTime())->format('N');
        }
        if ($compareTime === null) {
            $compareTime = date('H:i:s');
        }

        $result = [
            'is_open' => false,
            'is_open_today' => false,
        ];

        if (($from = ($opening_hours[$dayOfWeek][0] ?? false))
            && ($to = ($opening_hours[$dayOfWeek][1] ?? false))) {
            $breakFrom = ($opening_hours[$dayOfWeek][2] ?? false);
            $breakTo = ($opening_hours[$dayOfWeek][3] ?? false);
            if (DateUtil::compareTimes($from, $compareTime) && !DateUtil::compareTimes($to, $compareTime)) {
                if (!$breakFrom || !$breakTo || DateUtil::compareTimes($compareTime, $breakFrom) || DateUtil::compareTimes($breakTo,
                    $compareTime)) {
                    $result['is_open'] = true;
                }
            }
            $result['is_open_today'] = ['from' => $from, 'to' => $to, 'break_from' => $breakFrom, 'break_to' => $breakTo];
        }

        return $result;
    }

    private static function createHolidays(string $country, int $year): ProviderInterface
    {
        static $holidaysCache = [];

        $cacheKey = implode('-', [$country, $year]);

        if (!($holidaysCache[$cacheKey] ?? false)) {
            try {
                // method `createByISO3166_2` does not exists in older versions of Yasumi lib (< 1.7.0)
                $holidaysCache[$cacheKey] = Yasumi::createByISO3166_2($country, $year);
            } catch (\Throwable $e) {
                // fallback
                $holidaysCache[$cacheKey] = Yasumi::create('CzechRepublic', $year);
            }
        }

        return $holidaysCache[$cacheKey];
    }

    public static function getNowWithOffset(int $offset)
    {
        return (new \DateTime())->modify(($offset > 0 ? '+' : '-').abs($offset).' days');
    }
}
