<?php

declare(strict_types=1);

namespace App\Framework\Performance\ValueObjects;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\HighResolutionClock;
use App\Framework\Performance\MemoryMonitor;

/**
 * Immutable value object representing a single performance measurement
 */
final readonly class Measurement
{
    public function __construct(
        private Duration $duration,
        private Byte $memory,
        private Timestamp $timestamp
    ) {
    }

    public static function create(Duration $duration, Byte $memory, Timestamp $timestamp): self
    {
        return new self($duration, $memory, $timestamp);
    }

    public static function fromMicrotime(
        float $startTime,
        float $endTime,
        int $startMemory,
        int $endMemory,
        ?Timestamp $timestamp = null
    ): self {
        $duration = Duration::fromSeconds($endTime - $startTime);
        $memoryDiff = $endMemory - $startMemory;
        $memory = Byte::fromBytes((int) max(0, $memoryDiff));
        $timestamp = $timestamp ?? Timestamp::fromFloat($endTime);

        return new self($duration, $memory, $timestamp);
    }

    public static function startTiming(Clock $clock, MemoryMonitor $memoryMonitor): array
    {
        return [
            'start_time' => $clock->time(),
            'start_memory' => $memoryMonitor->getCurrentMemory()->toBytes(),
        ];
    }

    public static function endTiming(array $startData, Clock $clock, MemoryMonitor $memoryMonitor): self
    {
        $endTime = $clock->time();
        $endMemory = $memoryMonitor->getCurrentMemory()->toBytes();

        $duration = $endTime->diff($startData['start_time']);
        $memoryDiff = $endMemory - $startData['start_memory'];
        $memory = Byte::fromBytes((int) max(0, $memoryDiff));

        return new self($duration, $memory, $endTime);
    }

    /**
     * Create measurement from high-resolution nanosecond timing data
     */
    public static function fromNanoseconds(
        int $startTimeNanos,
        int $endTimeNanos,
        int $startMemory,
        int $endMemory,
        Timestamp $timestamp
    ): self {
        $durationNanos = $endTimeNanos - $startTimeNanos;
        $duration = Duration::fromNanoseconds($durationNanos);
        $memoryDiff = $endMemory - $startMemory;
        $memory = Byte::fromBytes((int) max(0, $memoryDiff));

        return new self($duration, $memory, $timestamp);
    }

    /**
     * End timing with high-resolution clock for nanosecond precision
     */
    public static function endHighResTiming(array $startData, HighResolutionClock $highResClock, MemoryMonitor $memoryMonitor, Clock $clock): self
    {
        $endTime = $highResClock->hrtime();
        $endMemory = $memoryMonitor->getCurrentMemory()->toBytes();

        $duration = $endTime->subtract($startData['start_time']);
        $memoryDiff = $endMemory - $startData['start_memory'];
        $memory = Byte::fromBytes((int) max(0, $memoryDiff));

        return new self($duration, $memory, $clock->time());
    }

    public function getDuration(): Duration
    {
        return $this->duration;
    }

    public function getMemory(): Byte
    {
        return $this->memory;
    }

    public function getTimestamp(): Timestamp
    {
        return $this->timestamp;
    }

    public function toArray(): array
    {
        return [
            'duration' => $this->duration->toMilliseconds(),
            'memory' => $this->memory->toBytes(),
            'timestamp' => $this->timestamp->toFloat(),
        ];
    }

    public function equals(self $other): bool
    {
        return $this->duration->equals($other->duration)
            && $this->memory->equals($other->memory)
            && $this->timestamp->equals($other->timestamp);
    }
}
