<?php

declare(strict_types=1);

namespace App\Framework\DateTime;

use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;

final readonly class DateRange
{
    /**
     * @param DateTimeImmutable $start Startdatum des Bereichs
     * @param DateTimeImmutable $end Enddatum des Bereichs
     * @throws InvalidArgumentException wenn das Enddatum vor dem Startdatum liegt
     */
    public function __construct(
        private DateTimeImmutable $start,
        private DateTimeImmutable $end
    ) {
        if ($end < $start) {
            throw new InvalidArgumentException(
                'Enddatum kann nicht vor dem Startdatum liegen.'
            );
        }
    }

    /**
     * Erzeugt einen DateRange aus zwei Datumsstrings.
     *
     * @param string $start Startdatum als String
     * @param string $end Enddatum als String
     * @param DateTimeZone|string|null $timezone Optionale Zeitzone (Standard: UTC)
     * @return self
     * @throws InvalidTimezoneException
     * @throws InvalidDateTimeException
     */
    public static function fromStrings(
        string $start,
        string $end,
        DateTimeZone|string|null $timezone = null
    ): self {
        if (is_string($timezone)) {
            $timezone = DateTime::createTimezone($timezone);
        }

        $tz = $timezone ?? DateTime::createTimezone('UTC');
        $startDate = DateTime::fromString($start, $tz);
        $endDate = DateTime::fromString($end, $tz);

        return new self($startDate, $endDate);
    }

    /**
     * Gibt das Startdatum zurück.
     *
     * @return DateTimeImmutable
     */
    public function getStart(): DateTimeImmutable
    {
        return $this->start;
    }

    /**
     * Gibt das Enddatum zurück.
     *
     * @return DateTimeImmutable
     */
    public function getEnd(): DateTimeImmutable
    {
        return $this->end;
    }

    /**
     * Überprüft, ob ein bestimmtes Datum im Bereich liegt.
     *
     * @param DateTimeInterface $date Das zu überprüfende Datum
     * @return bool
     */
    public function contains(DateTimeInterface $date): bool
    {
        return ($date >= $this->start && $date <= $this->end);
    }

    /**
     * Überprüft, ob ein anderer Bereich vollständig in diesem Bereich enthalten ist.
     *
     * @param DateRange $range Der zu überprüfende Bereich
     * @return bool
     */
    public function containsRange(DateRange $range): bool
    {
        return ($range->getStart() >= $this->start && $range->getEnd() <= $this->end);
    }

    /**
     * Überprüft, ob sich dieser Bereich mit einem anderen überschneidet.
     *
     * @param DateRange $range Der zu überprüfende Bereich
     * @return bool
     */
    public function overlaps(DateRange $range): bool
    {
        return ($this->start <= $range->getEnd() && $this->end >= $range->getStart());
    }

    /**
     * Berechnet die Dauer dieses Bereichs.
     *
     * @return DateInterval
     */
    public function getDuration(): DateInterval
    {
        return $this->start->diff($this->end);
    }

    /**
     * Gibt die Dauer in Sekunden zurück.
     *
     * @return int
     */
    public function getDurationInSeconds(): int
    {
        return $this->end->getTimestamp() - $this->start->getTimestamp();
    }
}
