<?php

declare(strict_types=1);

namespace App\Framework\Performance;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Performance\ValueObjects\Measurement;
use App\Framework\Performance\ValueObjects\MeasurementCollection;
use App\Framework\Performance\ValueObjects\MetricContext;

final class PerformanceMetric
{
    private MeasurementCollection $measurements;

    /** @var array<float> */
    private array $values = [];

    private int $count = 0;

    public function __construct(
        private readonly string $key,
        private readonly PerformanceCategory $category,
        private readonly MetricContext $context
    ) {
        $this->measurements = new MeasurementCollection();
    }

    public static function create(string $key, PerformanceCategory $category, array $context = []): self
    {
        return new self($key, $category, new MetricContext($context));
    }

    public function addMeasurementObject(Measurement $measurement): void
    {
        $this->measurements->add($measurement);
        $this->count++;
    }

    public function addValue(float $value): void
    {
        $this->values[] = $value;
        $this->count++;
    }

    public function increment(int $amount = 1): void
    {
        $this->count += $amount;
    }

    public function getKey(): string
    {
        return $this->key;
    }

    public function getCategory(): PerformanceCategory
    {
        return $this->category;
    }

    public function getContext(): array
    {
        return $this->context->toArray();
    }

    public function getContextObject(): MetricContext
    {
        return $this->context;
    }

    public function getCount(): int
    {
        return $this->count;
    }

    public function getMeasurements(): MeasurementCollection
    {
        return $this->measurements;
    }

    public function getTotalDuration(): float
    {
        return $this->measurements->getTotalDuration()->toMilliseconds();
    }

    public function getTotalDurationObject(): Duration
    {
        return $this->measurements->getTotalDuration();
    }

    public function getAverageDuration(): Duration
    {
        return $this->measurements->getAverageDuration();
    }

    public function getAverageDurationMilliseconds(): float
    {
        return $this->measurements->getAverageDuration()->toMilliseconds();
    }

    public function getMinDuration(): float
    {
        $min = $this->measurements->getMinDuration();

        return $min ? $min->toMilliseconds() : 0.0;
    }

    public function getMinDurationObject(): ?Duration
    {
        return $this->measurements->getMinDuration();
    }

    public function getMaxDuration(): float
    {
        $max = $this->measurements->getMaxDuration();

        return $max ? $max->toMilliseconds() : 0.0;
    }

    public function getMaxDurationObject(): ?Duration
    {
        return $this->measurements->getMaxDuration();
    }

    public function getTotalMemory(): int
    {
        return $this->measurements->getTotalMemory()->toBytes();
    }

    public function getTotalMemoryAsBytes(): Byte
    {
        return $this->measurements->getTotalMemory();
    }

    public function getAverageMemory(): float
    {
        return (float) $this->measurements->getAverageMemory()->toBytes();
    }

    public function getAverageMemoryAsBytes(): Byte
    {
        return $this->measurements->getAverageMemory();
    }

    public function getMinMemory(): int
    {
        $min = $this->measurements->getMinMemory();

        return $min ? $min->toBytes() : 0;
    }

    public function getMinMemoryAsBytes(): Byte
    {
        return $this->measurements->getMinMemory() ?? Byte::zero();
    }

    public function getMaxMemory(): int
    {
        $max = $this->measurements->getMaxMemory();

        return $max ? $max->toBytes() : 0;
    }

    public function getMaxMemoryAsBytes(): Byte
    {
        return $this->measurements->getMaxMemory() ?? Byte::zero();
    }

    public function getValues(): array
    {
        return $this->values;
    }

    public function getAverageValue(): float
    {
        if (empty($this->values)) {
            return 0.0;
        }

        return array_sum($this->values) / count($this->values);
    }

    public function toArray(): array
    {
        return [
            'key' => $this->key,
            'category' => $this->category->value,
            'context' => $this->context->toArray(),
            'count' => $this->count,
            'measurements' => [
                'total_duration_ms' => round($this->getTotalDuration(), 2),
                'avg_duration_ms' => round($this->getAverageDurationMilliseconds(), 2),
                'min_duration_ms' => round($this->getMinDuration(), 2),
                'max_duration_ms' => round($this->getMaxDuration(), 2),
                'total_memory_bytes' => $this->getTotalMemory(),
                'total_memory_human' => $this->getTotalMemoryAsBytes()->toHumanReadable(),
                'avg_memory_bytes' => round($this->getAverageMemory(), 2),
                'avg_memory_human' => $this->getAverageMemoryAsBytes()->toHumanReadable(),
                'min_memory_bytes' => $this->getMinMemory(),
                'min_memory_human' => $this->getMinMemoryAsBytes()->toHumanReadable(),
                'max_memory_bytes' => $this->getMaxMemory(),
                'max_memory_human' => $this->getMaxMemoryAsBytes()->toHumanReadable(),
            ],
            'values' => [
                'count' => count($this->values),
                'total' => round(array_sum($this->values), 2),
                'average' => round($this->getAverageValue(), 2),
            ],
        ];
    }
}
