<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Service\RollingDate;

use Symfony\Component\Clock\ClockInterface;

final readonly class RollingDateConverter implements RollingDateConverterInterface
{
    public function __construct(private readonly ClockInterface $clock) {}

    public function convert(?RollingDate $rollingDate): ?\DateTimeImmutable
    {
        if (null === $rollingDate) {
            return null;
        }

        switch ($rollingDate->getRoll()) {
            case RollingDate::T_MONTH_CURRENT_START:
                return $this->toBeginOfMonth($rollingDate->getPivotDate() ?? $this->clock->now());

            case RollingDate::T_MONTH_NEXT_START:
                return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1M')));

            case RollingDate::T_MONTH_PREVIOUS_START:
                return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1M')));

            case RollingDate::T_QUARTER_CURRENT_START:
                return $this->toBeginOfQuarter($rollingDate->getPivotDate() ?? $this->clock->now());

            case RollingDate::T_QUARTER_NEXT_START:
                return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P3M')));

            case RollingDate::T_QUARTER_PREVIOUS_START:
                return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P3M')));

            case RollingDate::T_WEEK_CURRENT_START:
                return $this->toBeginOfWeek($rollingDate->getPivotDate() ?? $this->clock->now());

            case RollingDate::T_WEEK_NEXT_START:
                return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1W')));

            case RollingDate::T_WEEK_PREVIOUS_START:
                return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1W')));

            case RollingDate::T_YEAR_CURRENT_START:
                return $this->toBeginOfYear($rollingDate->getPivotDate() ?? $this->clock->now());

            case RollingDate::T_YEAR_PREVIOUS_START:
                return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1Y')));

            case RollingDate::T_YEAR_NEXT_START:
                return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1Y')));

            case RollingDate::T_TODAY:
                return $rollingDate->getPivotDate() ?? $this->clock->now();

            case RollingDate::T_FIXED_DATE:
                if (null === $rollingDate->getFixedDate()) {
                    throw new \LogicException('You must provide a fixed date when selecting a fixed date');
                }

                return $rollingDate->getFixedDate();

            default:
                throw new \UnexpectedValueException(sprintf('%s rolling operation not supported', $rollingDate->getRoll()));
        }
    }

    private function toBeginOfMonth(\DateTimeImmutable $date): \DateTimeImmutable
    {
        return \DateTimeImmutable::createFromFormat(
            'Y-m-d His',
            sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m')),
        );
    }

    private function toBeginOfQuarter(\DateTimeImmutable $date): \DateTimeImmutable
    {
        $month = match ((int) $date->format('n')) {
            1, 2, 3 => '01',
            4, 5, 6 => '04',
            7, 8, 9 => '07',
            10, 11, 12 => '10',
        };

        return \DateTimeImmutable::createFromFormat(
            'Y-m-d His',
            sprintf('%s-%s-01 000000', $date->format('Y'), $month),
        );
    }

    private function toBeginOfWeek(\DateTimeImmutable $date): \DateTimeImmutable
    {
        if (1 === $dayOfWeek = (int) $date->format('N')) {
            return $date->setTime(0, 0, 0);
        }

        return $date
            ->sub(new \DateInterval('P'.($dayOfWeek - 1).'D'))
            ->setTime(0, 0, 0);
    }

    private function toBeginOfYear(\DateTimeImmutable $date): \DateTimeImmutable
    {
        return \DateTimeImmutable::createFromFormat(
            'Y-m-d His',
            sprintf('%s-01-01 000000', $date->format('Y'))
        );
    }
}
