<?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\Performance\PerformanceCategory;

/**
 * Immutable snapshot of performance data during an operation
 *
 * Provides comprehensive performance tracking with domain context
 * for detailed analysis and monitoring.
 */
final readonly class PerformanceSnapshot
{
    public function __construct(
        public string $operationId,
        public PerformanceCategory $category,
        public Timestamp $startTime,
        public Byte $startMemory,
        public Byte $peakMemory,
        public ?Timestamp $endTime = null,
        public ?Byte $endMemory = null,
        public ?Duration $duration = null,
        public ?Byte $memoryDelta = null,
        public float $cpuUsage = 0.0,
        public int $ioOperations = 0,
        public int $cacheHits = 0,
        public int $cacheMisses = 0,
        public int $itemsProcessed = 0,
        public int $errorsEncountered = 0,
        public array $contextData = [],
        public array $customMetrics = []
    ) {
    }

    /**
     * Create a new snapshot for starting an operation
     */
    public static function start(
        string $operationId,
        PerformanceCategory $category,
        Timestamp $startTime,
        Byte $startMemory,
        array $contextData = []
    ): self {
        return new self(
            operationId: $operationId,
            category: $category,
            startTime: $startTime,
            startMemory: $startMemory,
            peakMemory: $startMemory,
            contextData: $contextData
        );
    }

    public function withEndTime(Timestamp $endTime): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withEndMemory(Byte $endMemory): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withDuration(Duration $duration): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withMemoryDelta(Byte $memoryDelta): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withPeakMemory(Byte $peakMemory): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withCacheHits(int $cacheHits): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withCacheMisses(int $cacheMisses): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withItemsProcessed(int $itemsProcessed): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withIoOperations(int $ioOperations): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withErrorsEncountered(int $errorsEncountered): self
    {
        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $errorsEncountered,
            $this->contextData,
            $this->customMetrics
        );
    }

    public function withCustomMetric(string $name, mixed $value): self
    {
        $customMetrics = $this->customMetrics;
        $customMetrics[$name] = $value;

        return new self(
            $this->operationId,
            $this->category,
            $this->startTime,
            $this->startMemory,
            $this->peakMemory,
            $this->endTime,
            $this->endMemory,
            $this->duration,
            $this->memoryDelta,
            $this->cpuUsage,
            $this->ioOperations,
            $this->cacheHits,
            $this->cacheMisses,
            $this->itemsProcessed,
            $this->errorsEncountered,
            $this->contextData,
            $customMetrics
        );
    }

    /**
     * Check if the operation is completed
     */
    public function isCompleted(): bool
    {
        return $this->endTime !== null && $this->duration !== null;
    }

    /**
     * Get cache hit rate
     */
    public function getCacheHitRate(): float
    {
        $total = $this->cacheHits + $this->cacheMisses;

        return $total > 0 ? $this->cacheHits / $total : 0.0;
    }

    /**
     * Get error rate
     */
    public function getErrorRate(): float
    {
        return $this->itemsProcessed > 0
            ? $this->errorsEncountered / $this->itemsProcessed
            : 0.0;
    }

    /**
     * Get throughput (items per second)
     */
    public function getThroughput(): float
    {
        if ($this->duration === null || $this->duration->toSeconds() === 0) {
            return 0.0;
        }

        return $this->itemsProcessed / $this->duration->toSeconds();
    }

    /**
     * Get memory efficiency (bytes per item)
     */
    public function getMemoryEfficiency(): float
    {
        if ($this->itemsProcessed === 0 || $this->memoryDelta === null) {
            return 0.0;
        }

        return $this->memoryDelta->toBytes() / $this->itemsProcessed;
    }

    /**
     * Get memory pressure (peak memory vs typical usage)
     */
    public function getMemoryPressure(): float
    {
        $memoryUsed = $this->peakMemory->subtract($this->startMemory);
        $typicalMemoryLimit = Byte::fromMegabytes(128); // 128MB as baseline

        return $memoryUsed->toBytes() / $typicalMemoryLimit->toBytes();
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'operation_id' => $this->operationId,
            'category' => $this->category->value,
            'start_time' => $this->startTime->toFloat(),
            'end_time' => $this->endTime?->toFloat(),
            'duration_ms' => $this->duration?->toMilliseconds(),
            'start_memory_bytes' => $this->startMemory->toBytes(),
            'end_memory_bytes' => $this->endMemory?->toBytes(),
            'peak_memory_bytes' => $this->peakMemory->toBytes(),
            'memory_delta_bytes' => $this->memoryDelta?->toBytes(),
            'cpu_usage' => $this->cpuUsage,
            'io_operations' => $this->ioOperations,
            'cache_hits' => $this->cacheHits,
            'cache_misses' => $this->cacheMisses,
            'items_processed' => $this->itemsProcessed,
            'errors_encountered' => $this->errorsEncountered,
            'context_data' => $this->contextData,
            'custom_metrics' => $this->customMetrics,
            'computed_metrics' => [
                'cache_hit_rate' => $this->getCacheHitRate(),
                'error_rate' => $this->getErrorRate(),
                'throughput' => $this->getThroughput(),
                'memory_efficiency' => $this->getMemoryEfficiency(),
                'memory_pressure' => $this->getMemoryPressure(),
                'is_completed' => $this->isCompleted(),
            ],
        ];
    }

    /**
     * Get telemetry data optimized for monitoring systems
     */
    public function toTelemetryData(): array
    {
        $base = [
            'operation_id' => $this->operationId,
            'category' => $this->category->value,
            'timestamp' => $this->endTime?->toFloat() ?? $this->startTime->toFloat(),
            'duration_ms' => $this->duration?->toMilliseconds() ?? 0,
            'memory_peak_mb' => $this->peakMemory->toMegabytes(),
            'items_processed' => $this->itemsProcessed,
            'throughput' => $this->getThroughput(),
            'cache_hit_rate' => $this->getCacheHitRate(),
            'error_rate' => $this->getErrorRate(),
            'memory_pressure' => $this->getMemoryPressure(),
        ];

        // Add context data with telemetry prefix
        foreach ($this->contextData as $key => $value) {
            $base["ctx_{$key}"] = $value;
        }

        // Add custom metrics with metric prefix
        foreach ($this->customMetrics as $key => $value) {
            $base["metric_{$key}"] = $value;
        }

        return $base;
    }
}
