<?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;

final class DateTime
{
    private static ?DateTimeZone $defaultTimezone = null;

    /**
     * Erstellt ein DateTimeImmutable-Objekt aus einem Zeitstempel.
     *
     * @param int $timestamp UNIX-Zeitstempel
     * @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
     * @return DateTimeImmutable
     * @throws InvalidTimezoneException|InvalidDateTimeException wenn die Zeitzone ungültig ist
     */
    public static function fromTimestamp(int $timestamp, DateTimeZone|string|null $timezone = null): DateTimeImmutable
    {
        $tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();

        try {
            return new DateTimeImmutable('@' . $timestamp)->setTimezone($tz);
        } catch (\Exception $e) {
            throw new InvalidDateTimeException((string)$timestamp, 'timestamp', $e);
        }
    }

    /**
     * Erstellt ein DateTimeImmutable-Objekt aus einem String.
     *
     * @param string $dateTime Der zu parsende Zeitstring
     * @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
     * @return DateTimeImmutable
     * @throws InvalidDateTimeException wenn das Parsen fehlschlägt
     * @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
     */
    public static function fromString(string $dateTime, DateTimeZone|string|null $timezone = null): DateTimeImmutable
    {
        $tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();

        try {
            return new DateTimeImmutable($dateTime, $tz);
        } catch (\Exception $e) {
            throw new InvalidDateTimeException($dateTime, null, $e);
        }
    }

    /**
     * Erstellt ein DateTimeImmutable-Objekt aus einem String mit spezifischem Format.
     *
     * @param string $dateTime Der zu parsende Zeitstring
     * @param string $format Das erwartete Format
     * @param DateTimeZone|string|null $timezone Optionale Zeitzone (verwendet Standard-Zeitzone wenn null)
     * @return DateTimeImmutable
     * @throws InvalidDateTimeException wenn das Parsen fehlschlägt
     * @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
     */
    public static function fromFormat(string $dateTime, string $format, DateTimeZone|string|null $timezone = null): DateTimeImmutable
    {
        $tz = $timezone !== null ? self::createTimezone($timezone) : self::getDefaultTimezone();

        try {
            $date = DateTimeImmutable::createFromFormat($format, $dateTime, $tz);

            if ($date === false) {
                throw new InvalidDateTimeException($dateTime, $format);
            }

            return $date;
        } catch (\Exception $e) {
            if ($e instanceof InvalidDateTimeException) {
                throw $e;
            }

            throw new InvalidDateTimeException($dateTime, $format, $e);
        }
    }

    /**
     * Erstellt ein DateTimeImmutable-Objekt aus einem anderen DateTimeInterface.
     *
     * @param DateTimeInterface $dateTime Das zu konvertierende DateTime
     * @param DateTimeZone|string|null $timezone Optionale neue Zeitzone
     * @return DateTimeImmutable
     * @throws InvalidDateTimeException wenn die Zeitzone ungültig ist
     */
    public static function fromDateTime(DateTimeInterface $dateTime, DateTimeZone|string|null $timezone = null): DateTimeImmutable
    {
        try {
            if ($dateTime instanceof DateTimeImmutable) {
                $result = $dateTime;
            } else {
                $result = DateTimeImmutable::createFromInterface($dateTime);
            }

            if ($timezone !== null) {
                $tz = self::createTimezone($timezone);
                $result = $result->setTimezone($tz);
            }

            return $result;
        } catch (\Exception $e) {
            throw new InvalidDateTimeException($dateTime->format('c'), null, $e);
        }
    }

    /**
     * Erstellt ein DateInterval-Objekt.
     *
     * @param string $interval Das Interval im ISO 8601 Format (z.B. 'P1D', 'PT1H')
     * @return DateInterval
     * @throws InvalidDateTimeException wenn das Interval ungültig ist
     */
    public static function createInterval(string $interval): DateInterval
    {
        try {
            return new DateInterval($interval);
        } catch (\Exception $e) {
            throw new InvalidDateTimeException($interval, 'DateInterval', $e);
        }
    }

    /**
     * Erstellt eine DateTimeZone.
     *
     * @param DateTimeZone|string $timezone Die Zeitzone
     * @return DateTimeZone
     * @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
     */
    public static function createTimezone(DateTimeZone|string $timezone): DateTimeZone
    {
        if ($timezone instanceof DateTimeZone) {
            return $timezone;
        }

        try {
            return new DateTimeZone($timezone);
        } catch (\Exception $e) {
            throw new InvalidTimezoneException($timezone, $e);
        }
    }

    /**
     * Gibt die Standard-Zeitzone zurück.
     *
     * @return DateTimeZone
     */
    public static function getDefaultTimezone(): DateTimeZone
    {
        return self::$defaultTimezone ?? new DateTimeZone('Europe/Berlin');
    }

    /**
     * Setzt eine neue Standard-Zeitzone.
     *
     * @param DateTimeZone|string $timezone Die neue Standard-Zeitzone
     * @throws InvalidTimezoneException wenn die Zeitzone ungültig ist
     */
    public static function setDefaultTimezone(DateTimeZone|string $timezone): void
    {
        self::$defaultTimezone = self::createTimezone($timezone);
    }
}
