<?php

declare(strict_types=1);

namespace App\Framework\DateTime;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateInvalidOperationException;
use DateTimeImmutable;
use DateTimeZone;

final class FrozenClock implements Clock
{
    private DateTimeImmutable $frozenTime;

    private DateTimeZone $timezone;

    /**
     * @param DateTimeImmutable|string|null $time Die fixierte Zeit (Standard: aktuelle Zeit über SystemClock)
     * @param DateTimeZone|string|null $timezone Die zu verwendende Zeitzone (Standard: UTC)
     * @throws InvalidDateTimeException wenn die Zeit ungültig ist
     * @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
     */
    public function __construct(
        DateTimeImmutable|string|null $time = null,
        DateTimeZone|string|null $timezone = null
    ) {
        $this->timezone = $timezone !== null
            ? DateTime::createTimezone($timezone)
            : DateTime::getDefaultTimezone();

        if ($time === null) {
            // Verwende SystemClock für die initiale Zeit
            $systemClock = new SystemClock($this->timezone);
            $this->frozenTime = $systemClock->now();
        } elseif (is_string($time)) {
            $this->frozenTime = DateTime::fromString($time, $this->timezone);
        } else {
            $this->frozenTime = DateTime::fromDateTime($time, $this->timezone);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function now(): DateTimeImmutable
    {
        return $this->frozenTime;
    }

    /**
     * {@inheritDoc}
     * @throws InvalidTimezoneException
     */
    public function fromTimestamp(Timestamp $timestamp): DateTimeImmutable
    {
        return DateTime::fromTimestamp($timestamp->toTimestamp(), $this->timezone);
    }

    /**
     * {@inheritDoc}
     */
    public function fromString(string $dateTime, ?string $format = null): DateTimeImmutable
    {
        if ($format === null) {
            return DateTime::fromString($dateTime, $this->timezone);
        }

        return DateTime::fromFormat($dateTime, $format, $this->timezone);
    }

    /**
     * Erstellt ein Today-Datum (00:00:00) basierend auf der gefrorenen Zeit.
     *
     * @return DateTimeImmutable
     */
    public function today(): DateTimeImmutable
    {
        return $this->frozenTime->setTime(0, 0, 0);
    }

    /**
     * Erstellt ein Tomorrow-Datum (00:00:00) basierend auf der gefrorenen Zeit.
     *
     * @return DateTimeImmutable
     */
    public function tomorrow(): DateTimeImmutable
    {
        return $this->frozenTime->modify('+1 day')->setTime(0, 0, 0);
    }

    /**
     * Erstellt ein Yesterday-Datum (00:00:00) basierend auf der gefrorenen Zeit.
     *
     * @return DateTimeImmutable
     */
    public function yesterday(): DateTimeImmutable
    {
        return $this->frozenTime->modify('-1 day')->setTime(0, 0, 0);
    }

    /**
     * Setzt die fixierte Zeit auf einen neuen Wert.
     *
     * @param DateTimeImmutable|string $time Die neue fixierte Zeit
     * @return self
     * @throws InvalidDateTimeException|InvalidTimezoneException wenn die Zeit ungültig ist
     */
    public function setTo(DateTimeImmutable|string $time): self
    {
        if (is_string($time)) {
            $this->frozenTime = DateTime::fromString($time, $this->timezone);
        } else {
            $this->frozenTime = DateTime::fromDateTime($time, $this->timezone);
        }

        return $this;
    }

    /**
     * Bewegt die fixierte Zeit um eine bestimmte Zeit vorwärts.
     *
     * @param string $interval Eine DateInterval-kompatible Zeichenfolge (z.B. 'PT1H' für eine Stunde)
     * @return self
     * @throws InvalidDateTimeException wenn das Interval ungültig ist
     */
    public function moveForward(string $interval): self
    {
        $dateInterval = DateTime::createInterval($interval);
        $this->frozenTime = $this->frozenTime->add($dateInterval);

        return $this;
    }

    /**
     * Bewegt die fixierte Zeit um eine bestimmte Zeit zurück.
     *
     * @param string $interval Eine DateInterval-kompatible Zeichenfolge (z.B. 'PT1H' für eine Stunde)
     * @return self
     * @throws InvalidDateTimeException|DateInvalidOperationException wenn das Interval ungültig ist
     */
    public function moveBackward(string $interval): self
    {
        $dateInterval = DateTime::createInterval($interval);
        $this->frozenTime = $this->frozenTime->sub($dateInterval);

        return $this;
    }

    public function time(): Timestamp
    {
        return Timestamp::fromDateTime($this->frozenTime);
    }
}
