<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

use InvalidArgumentException;

final readonly class Duration
{
    private function __construct(
        private int $nanoseconds
    ) {
        if ($nanoseconds < 0) {
            throw new InvalidArgumentException('Duration cannot be negative');
        }
    }

    /**
     * Create Duration directly from nanoseconds for maximum precision
     */
    public static function fromNanoseconds(int $nanoseconds): self
    {
        return new self($nanoseconds);
    }

    // Factory Methods
    public static function fromSeconds(float $seconds): self
    {
        if ($seconds < 0) {
            throw new InvalidArgumentException('Duration cannot be negative');
        }

        return new self((int) round($seconds * 1_000_000_000));
    }

    public static function fromUnit(float $value, TimeUnit $unit): self
    {
        if ($value < 0) {
            throw new InvalidArgumentException('Duration cannot be negative');
        }
        $seconds = $value * $unit->getMultiplierToSeconds();

        return new self((int) round($seconds * 1_000_000_000));
    }

    public static function fromMilliseconds(float $milliseconds): self
    {
        return self::fromUnit($milliseconds, TimeUnit::MILLISECOND);
    }

    public static function fromMicroseconds(float $microseconds): self
    {
        return self::fromUnit($microseconds, TimeUnit::MICROSECOND);
    }

    public static function fromMinutes(float $minutes): self
    {
        return self::fromUnit($minutes, TimeUnit::MINUTE);
    }

    public static function fromHours(float $hours): self
    {
        return self::fromUnit($hours, TimeUnit::HOUR);
    }

    public static function fromDays(float $days): self
    {
        return self::fromUnit($days, TimeUnit::DAY);
    }

    public static function between(Timestamp $timestamp, Timestamp $other): self
    {
        return $timestamp->diff($other);
    }

    // Parse from human-readable strings
    public static function parse(string $value): self
    {
        $value = trim($value);

        if (preg_match('/^(\d+(?:\.\d+)?)\s*([a-zA-Z]*)$/', $value, $matches)) {
            $number = (float) $matches[1];
            $unitString = $matches[2] ?: 's';

            $unit = TimeUnit::fromString($unitString);

            return self::fromUnit($number, $unit);
        }

        throw new InvalidArgumentException("Invalid duration format: $value");
    }

    // Conversion Methods
    public function toSeconds(): float
    {
        return $this->nanoseconds / 1_000_000_000;
    }

    public function toNanoseconds(): int
    {
        return $this->nanoseconds;
    }

    public function toUnit(TimeUnit $unit, int $precision = 2): float
    {
        $seconds = $this->nanoseconds / 1_000_000_000;

        return round($seconds / $unit->getMultiplierToSeconds(), $precision);
    }

    public function toMilliseconds(): float
    {
        return $this->toUnit(TimeUnit::MILLISECOND, 0);
    }

    public function toMicroseconds(): float
    {
        return $this->toUnit(TimeUnit::MICROSECOND, 0);
    }

    public function toMinutes(): float
    {
        return $this->toUnit(TimeUnit::MINUTE);
    }

    public function toHours(): float
    {
        return $this->toUnit(TimeUnit::HOUR);
    }

    // Human-readable format
    public function toHumanReadable(): string
    {
        if ($this->nanoseconds === 0) {
            return '0s';
        }

        $seconds = $this->nanoseconds / 1_000_000_000;
        $unit = TimeUnit::bestUnitFor($seconds);
        $value = $this->toUnit($unit);

        return $value . ' ' . $unit->value;
    }

    // Arithmetic Operations
    public function add(Duration $other): self
    {
        return new self($this->nanoseconds + $other->nanoseconds);
    }

    public function subtract(Duration $other): self
    {
        $result = $this->nanoseconds - $other->nanoseconds;
        if ($result < 0) {
            throw new InvalidArgumentException('Subtraction would result in negative duration');
        }

        return new self($result);
    }

    public function multiply(float $factor): self
    {
        if ($factor < 0) {
            throw new InvalidArgumentException('Factor cannot be negative');
        }

        return new self((int) round($this->nanoseconds * $factor));
    }

    // Comparison Methods
    public function equals(Duration $other): bool
    {
        return $this->nanoseconds === $other->nanoseconds;
    }

    public function greaterThan(Duration $other): bool
    {
        return $this->nanoseconds > $other->nanoseconds;
    }

    public function lessThan(Duration $other): bool
    {
        return $this->nanoseconds < $other->nanoseconds;
    }

    // Utility Methods
    public function isZero(): bool
    {
        return $this->nanoseconds === 0;
    }

    public function isNotZero(): bool
    {
        return $this->nanoseconds > 0;
    }

    // Framework Integration
    public function toCacheSeconds(): int
    {
        return (int) ceil($this->nanoseconds / 1_000_000_000);
    }

    public function toTimeoutSeconds(): int
    {
        return (int) ceil($this->nanoseconds / 1_000_000_000);
    }

    public function __toString(): string
    {
        return $this->toHumanReadable();
    }

    // Common constants
    public static function zero(): self
    {
        return new self(0);
    }

    public static function oneSecond(): self
    {
        return new self(1_000_000_000);
    }

    public static function oneMinute(): self
    {
        return self::fromMinutes(1);
    }

    public static function oneHour(): self
    {
        return self::fromHours(1);
    }
}
