<?php

declare(strict_types=1);

namespace App\Framework\Waf;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;

/**
 * WAF performance monitoring and optimization service
 */
final class PerformanceService
{
    private array $timings = [];

    private array $metrics = [];

    public function __construct(
        private readonly WafConfig $config
    ) {
    }

    /**
     * Start timing measurement
     */
    public function startTiming(string $operation): void
    {
        $this->timings[$operation] = [
            'start' => Timestamp::now(),
            'end' => null,
            'duration' => null,
        ];
    }

    /**
     * End timing measurement
     */
    public function endTiming(string $operation): Duration
    {
        if (! isset($this->timings[$operation])) {
            return Duration::fromMilliseconds(0);
        }

        $timing = $this->timings[$operation];
        $endTime = Timestamp::now();
        $duration = $timing['start']->diff($endTime);

        $this->timings[$operation]['end'] = $endTime;
        $this->timings[$operation]['duration'] = $duration;

        return $duration;
    }

    /**
     * Get timing for operation
     */
    public function getTiming(string $operation): ?Duration
    {
        return $this->timings[$operation]['duration'] ?? null;
    }

    /**
     * Get all timings
     */
    public function getAllTimings(): array
    {
        $result = [];

        foreach ($this->timings as $operation => $timing) {
            if ($timing['duration'] !== null) {
                $result[$operation] = $timing['duration'];
            }
        }

        return $result;
    }

    /**
     * Record metric
     */
    public function recordMetric(string $name, mixed $value): void
    {
        if (! isset($this->metrics[$name])) {
            $this->metrics[$name] = [];
        }

        $this->metrics[$name][] = [
            'value' => $value,
            'timestamp' => Timestamp::now(),
        ];

        // Keep only last 100 measurements per metric
        if (count($this->metrics[$name]) > 100) {
            $this->metrics[$name] = array_slice($this->metrics[$name], -100);
        }
    }

    /**
     * Get metric values
     */
    public function getMetric(string $name): array
    {
        return $this->metrics[$name] ?? [];
    }

    /**
     * Get latest metric value
     */
    public function getLatestMetric(string $name): mixed
    {
        $values = $this->getMetric($name);

        if (empty($values)) {
            return null;
        }

        return end($values)['value'];
    }

    /**
     * Get average metric value
     */
    public function getAverageMetric(string $name): ?float
    {
        $values = $this->getMetric($name);

        if (empty($values)) {
            return null;
        }

        $numericValues = array_filter(
            array_map(fn ($item) => $item['value'], $values),
            fn ($value) => is_numeric($value)
        );

        if (empty($numericValues)) {
            return null;
        }

        return array_sum($numericValues) / count($numericValues);
    }

    /**
     * Check if operation exceeded timeout
     */
    public function hasExceededTimeout(string $operation): bool
    {
        $timing = $this->getTiming($operation);

        if ($timing === null) {
            return false;
        }

        return $timing->isGreaterThan($this->config->globalTimeout);
    }

    /**
     * Get total processing time
     */
    public function getTotalProcessingTime(): Duration
    {
        $totalMs = 0;

        foreach ($this->timings as $timing) {
            if ($timing['duration'] !== null) {
                $totalMs += $timing['duration']->toMilliseconds();
            }
        }

        return Duration::fromMilliseconds($totalMs);
    }

    /**
     * Get processing efficiency score
     */
    public function getEfficiencyScore(): Percentage
    {
        $totalTime = $this->getTotalProcessingTime();
        $targetTime = $this->config->globalTimeout;

        if ($totalTime->toMilliseconds() === 0) {
            return Percentage::from(100.0);
        }

        // Calculate efficiency: lower time = higher efficiency
        $efficiency = max(0, 100 - (($totalTime->toMilliseconds() / $targetTime->toMilliseconds()) * 100));

        return Percentage::from($efficiency);
    }

    /**
     * Check if performance is acceptable
     */
    public function isPerformanceAcceptable(): bool
    {
        $totalTime = $this->getTotalProcessingTime();

        return ! $totalTime->isGreaterThan($this->config->globalTimeout);
    }

    /**
     * Get performance warnings
     */
    public function getWarnings(): array
    {
        $warnings = [];

        // Check total time
        $totalTime = $this->getTotalProcessingTime();
        if ($totalTime->isGreaterThan($this->config->globalTimeout)) {
            $warnings[] = sprintf(
                'Total processing time (%dms) exceeded global timeout (%dms)',
                $totalTime->toMilliseconds(),
                $this->config->globalTimeout->toMilliseconds()
            );
        }

        // Check individual operations
        foreach ($this->timings as $operation => $timing) {
            if ($timing['duration'] !== null && $this->hasExceededTimeout($operation)) {
                $warnings[] = sprintf(
                    'Operation "%s" exceeded timeout (%dms)',
                    $operation,
                    $timing['duration']->toMilliseconds()
                );
            }
        }

        return $warnings;
    }

    /**
     * Reset all measurements
     */
    public function reset(): void
    {
        $this->timings = [];
        $this->metrics = [];
    }

    /**
     * Get performance summary
     */
    public function getSummary(): array
    {
        return [
            'total_processing_time_ms' => $this->getTotalProcessingTime()->toMilliseconds(),
            'efficiency_score' => $this->getEfficiencyScore()->getValue(),
            'is_acceptable' => $this->isPerformanceAcceptable(),
            'operation_count' => count($this->timings),
            'completed_operations' => count(array_filter($this->timings, fn ($t) => $t['duration'] !== null)),
            'warnings' => $this->getWarnings(),
            'individual_timings' => array_map(
                fn ($duration) => $duration->toMilliseconds(),
                $this->getAllTimings()
            ),
        ];
    }
}
