<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

use InvalidArgumentException;

final readonly class Byte
{
    public function __construct(
        private int $bytes
    ) {
        if ($bytes < 0) {
            throw new InvalidArgumentException('Bytes cannot be negative');
        }
    }

    // Factory Methods
    public static function fromBytes(int $bytes): self
    {
        return new self($bytes);
    }

    public static function fromUnit(float $value, ByteUnit $unit): self
    {
        return new self((int) round($value * $unit->getMultiplier()));
    }

    public static function fromKilobytes(float $kilobytes): self
    {
        return self::fromUnit($kilobytes, ByteUnit::KILOBYTE);
    }

    public static function fromMegabytes(float $megabytes): self
    {
        return self::fromUnit($megabytes, ByteUnit::MEGABYTE);
    }

    public static function fromGigabytes(float $gigabytes): self
    {
        return self::fromUnit($gigabytes, ByteUnit::GIGABYTE);
    }

    public static function fromTerabytes(float $terabytes): self
    {
        return self::fromUnit($terabytes, ByteUnit::TERABYTE);
    }

    // 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] ?: 'B';

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

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

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

    // Conversion Methods
    public function toBytes(): int
    {
        return $this->bytes;
    }

    public function toUnit(ByteUnit $unit, int $precision = 2): float
    {
        return round($this->bytes / $unit->getMultiplier(), $precision);
    }

    public function toKilobytes(int $precision = 2): float
    {
        return $this->toUnit(ByteUnit::KILOBYTE, $precision);
    }

    public function toMegabytes(int $precision = 2): float
    {
        return $this->toUnit(ByteUnit::MEGABYTE, $precision);
    }

    public function toGigabytes(int $precision = 2): float
    {
        return $this->toUnit(ByteUnit::GIGABYTE, $precision);
    }

    public function toTerabytes(int $precision = 2): float
    {
        return $this->toUnit(ByteUnit::TERABYTE, $precision);
    }

    // Human-readable format
    public function toHumanReadable(int $precision = 2): string
    {
        $unit = ByteUnit::bestUnitFor($this->bytes);
        $value = $this->toUnit($unit, $precision);

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

    public function getBestUnit(): ByteUnit
    {
        return ByteUnit::bestUnitFor($this->bytes);
    }

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

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

        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->bytes * $factor));
    }

    public function divide(float $divisor): self
    {
        if ($divisor <= 0) {
            throw new InvalidArgumentException('Divisor must be positive');
        }

        return new self((int) round($this->bytes / $divisor));
    }

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

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

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

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

    public function isNotEmpty(): bool
    {
        return $this->bytes > 0;
    }

    public function percentOf(Byte $total): Percentage
    {
        if ($total->bytes === 0) {
            return Percentage::zero();
        }

        return Percentage::fromRatio($this->bytes, $total->bytes);
    }

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

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

    public static function oneKilobyte(): self
    {
        return self::fromUnit(1, ByteUnit::KILOBYTE);
    }

    public static function oneMegabyte(): self
    {
        return self::fromUnit(1, ByteUnit::MEGABYTE);
    }

    public static function oneGigabyte(): self
    {
        return self::fromUnit(1, ByteUnit::GIGABYTE);
    }
}
