<?php

declare(strict_types=1);

namespace App\Framework\Mcp\Core\Services;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Mcp\Core\ValueObjects\PerformanceMetrics;
use App\Framework\Mcp\Core\ValueObjects\PerformanceThreshold;

/**
 * MCP Performance Monitor
 *
 * Comprehensive performance monitoring system for MCP tools including
 * execution time tracking, memory usage monitoring, throughput analysis,
 * and automated alerting for performance degradation.
 */
final class McpPerformanceMonitor
{
    private const METRICS_CACHE_TTL = 3600; // 1 hour
    private const HISTORY_RETENTION_DAYS = 30;
    private const ALERT_COOLDOWN_SECONDS = 300; // 5 minutes

    private array $activeExecutions = [];

    private array $recentMetrics = [];

    private array $alertHistory = [];

    public function __construct(
        private readonly Cache $cache,
        private readonly array $defaultThresholds = []
    ) {
    }

    /**
     * Start monitoring a tool execution
     */
    public function startExecution(
        string $toolName,
        string $methodName,
        array $parameters = [],
        array $context = []
    ): string {
        $executionId = $this->generateExecutionId($toolName, $methodName);

        $this->activeExecutions[$executionId] = [
            'tool_name' => $toolName,
            'method_name' => $methodName,
            'parameters' => $parameters,
            'context' => $context,
            'start_time' => microtime(true),
            'start_memory' => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true),
            'cpu_start' => $this->getCpuUsage(),
        ];

        return $executionId;
    }

    /**
     * End monitoring and collect metrics
     */
    public function endExecution(
        string $executionId,
        mixed $result = null,
        ?string $error = null
    ): PerformanceMetrics {
        if (! isset($this->activeExecutions[$executionId])) {
            throw new \InvalidArgumentException("Execution ID not found: {$executionId}");
        }

        $execution = $this->activeExecutions[$executionId];
        $endTime = microtime(true);
        $endMemory = memory_get_usage(true);
        $peakMemory = memory_get_peak_usage(true);
        $cpuEnd = $this->getCpuUsage();

        $metrics = new PerformanceMetrics(
            toolName: $execution['tool_name'],
            methodName: $execution['method_name'],
            executionId: $executionId,
            startTime: $execution['start_time'],
            endTime: $endTime,
            executionTime: $endTime - $execution['start_time'],
            memoryStart: $execution['start_memory'],
            memoryEnd: $endMemory,
            memoryPeak: $peakMemory,
            memoryUsage: $endMemory - $execution['start_memory'],
            cpuUsage: $cpuEnd - $execution['cpu_start'],
            resultSize: $this->calculateResultSize($result),
            success: $error === null,
            error: $error,
            parameters: $execution['parameters'],
            context: $execution['context']
        );

        // Store metrics
        $this->storeMetrics($metrics);

        // Check for performance issues
        $this->checkThresholds($metrics);

        // Update recent metrics
        $this->addToRecentMetrics($metrics);

        // Cleanup
        unset($this->activeExecutions[$executionId]);

        return $metrics;
    }

    /**
     * Monitor execution automatically using closure
     */
    public function monitor(
        string $toolName,
        string $methodName,
        callable $callback,
        array $parameters = [],
        array $context = []
    ): mixed {
        $executionId = $this->startExecution($toolName, $methodName, $parameters, $context);

        try {
            $result = $callback();
            $this->endExecution($executionId, $result);

            return $result;
        } catch (\Throwable $e) {
            $this->endExecution($executionId, null, $e->getMessage());

            throw $e;
        }
    }

    /**
     * Get performance metrics for a tool
     */
    public function getToolMetrics(
        string $toolName,
        ?string $methodName = null,
        int $limit = 100
    ): array {
        $cacheKey = CacheKey::fromString("mcp_metrics:{$toolName}" . ($methodName ? ":{$methodName}" : ''));

        $cached = $this->cache->get($cacheKey);
        if ($cached !== null) {
            return $cached;
        }

        $metrics = $this->loadMetricsFromStorage($toolName, $methodName, $limit);

        $cacheItem = CacheItem::forSet(
            key: $cacheKey,
            value: $metrics,
            ttl: Duration::fromSeconds(self::METRICS_CACHE_TTL)
        );

        $this->cache->set($cacheItem);

        return $metrics;
    }

    /**
     * Get aggregated performance statistics
     */
    public function getAggregatedStats(
        ?string $toolName = null,
        ?string $period = null
    ): array {
        $stats = [
            'total_executions' => 0,
            'successful_executions' => 0,
            'failed_executions' => 0,
            'success_rate' => 0.0,
            'average_execution_time' => 0.0,
            'median_execution_time' => 0.0,
            'p95_execution_time' => 0.0,
            'average_memory_usage' => 0,
            'peak_memory_usage' => 0,
            'total_cpu_time' => 0.0,
            'throughput_per_minute' => 0.0,
            'error_rate' => 0.0,
            'top_errors' => [],
            'slowest_executions' => [],
            'memory_intensive_executions' => [],
        ];

        $metrics = $this->getRelevantMetrics($toolName, $period);

        if (empty($metrics)) {
            return $stats;
        }

        $stats['total_executions'] = count($metrics);
        $stats['successful_executions'] = count(array_filter($metrics, fn ($m) => $m['success']));
        $stats['failed_executions'] = $stats['total_executions'] - $stats['successful_executions'];
        $stats['success_rate'] = $stats['total_executions'] > 0
            ? $stats['successful_executions'] / $stats['total_executions']
            : 0.0;

        // Execution time statistics
        $executionTimes = array_column($metrics, 'execution_time');
        if (! empty($executionTimes)) {
            $stats['average_execution_time'] = array_sum($executionTimes) / count($executionTimes);
            sort($executionTimes);
            $stats['median_execution_time'] = $this->calculateMedian($executionTimes);
            $stats['p95_execution_time'] = $this->calculatePercentile($executionTimes, 95);
        }

        // Memory statistics
        $memoryUsages = array_column($metrics, 'memory_usage');
        if (! empty($memoryUsages)) {
            $stats['average_memory_usage'] = (int) (array_sum($memoryUsages) / count($memoryUsages));
            $stats['peak_memory_usage'] = max(array_column($metrics, 'memory_peak'));
        }

        // CPU statistics
        $stats['total_cpu_time'] = array_sum(array_column($metrics, 'cpu_usage'));

        // Throughput calculation
        $timespan = $this->calculateTimespan($metrics);
        $stats['throughput_per_minute'] = $timespan > 0
            ? ($stats['total_executions'] / $timespan) * 60
            : 0.0;

        // Error analysis
        $stats['error_rate'] = 1.0 - $stats['success_rate'];
        $stats['top_errors'] = $this->getTopErrors($metrics);

        // Performance analysis
        $stats['slowest_executions'] = $this->getSlowExecutions($metrics, 5);
        $stats['memory_intensive_executions'] = $this->getMemoryIntensiveExecutions($metrics, 5);

        return $stats;
    }

    /**
     * Get performance alerts
     */
    public function getActiveAlerts(): array
    {
        return array_filter(
            $this->alertHistory,
            fn ($alert) => ! $alert['resolved'] && $alert['timestamp'] > (time() - 3600) // Last hour
        );
    }

    /**
     * Set performance thresholds for a tool
     */
    public function setThresholds(string $toolName, PerformanceThreshold $threshold): void
    {
        $cacheKey = CacheKey::fromString("mcp_thresholds:{$toolName}");

        $cacheItem = CacheItem::forSet(
            key: $cacheKey,
            value: $threshold,
            ttl: Duration::fromDays(1)
        );

        $this->cache->set($cacheItem);
    }

    /**
     * Get performance thresholds for a tool
     */
    public function getThresholds(string $toolName): PerformanceThreshold
    {
        $cacheKey = CacheKey::fromString("mcp_thresholds:{$toolName}");
        $cached = $this->cache->get($cacheKey);

        if ($cached instanceof PerformanceThreshold) {
            return $cached;
        }

        return $this->getDefaultThresholds($toolName);
    }

    /**
     * Generate performance report
     */
    public function generateReport(
        ?string $toolName = null,
        string $period = '24h'
    ): array {
        $stats = $this->getAggregatedStats($toolName, $period);
        $alerts = $this->getActiveAlerts();
        $trends = $this->calculateTrends($toolName, $period);

        return [
            'summary' => $stats,
            'alerts' => $alerts,
            'trends' => $trends,
            'recommendations' => $this->generateRecommendations($stats),
            'health_score' => $this->calculateHealthScore($stats),
            'generated_at' => date('Y-m-d H:i:s'),
            'period' => $period,
            'tool_name' => $toolName,
        ];
    }

    /**
     * Clean up old metrics
     */
    public function cleanup(): int
    {
        $cutoff = time() - (self::HISTORY_RETENTION_DAYS * 24 * 3600);
        $removed = 0;

        // This would clean up old metrics from storage
        // Implementation depends on the storage backend

        return $removed;
    }

    private function generateExecutionId(string $toolName, string $methodName): string
    {
        return uniqid("{$toolName}_{$methodName}_", true);
    }

    private function getCpuUsage(): float
    {
        $usage = getrusage();

        return ($usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1000000) +
               ($usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1000000);
    }

    private function calculateResultSize(mixed $result): int
    {
        if ($result === null) {
            return 0;
        }

        return strlen(serialize($result));
    }

    private function storeMetrics(PerformanceMetrics $metrics): void
    {
        // Store metrics for long-term analysis
        // This would typically write to a database or file storage
        $cacheKey = CacheKey::fromString("mcp_metric:{$metrics->executionId}");

        $cacheItem = CacheItem::forSet(
            key: $cacheKey,
            value: $metrics->toArray(),
            ttl: Duration::fromDays(self::HISTORY_RETENTION_DAYS)
        );

        $this->cache->set($cacheItem);
    }

    private function checkThresholds(PerformanceMetrics $metrics): void
    {
        $thresholds = $this->getThresholds($metrics->toolName);

        $violations = [];

        if ($metrics->executionTime > $thresholds->maxExecutionTime) {
            $violations[] = [
                'type' => 'execution_time',
                'threshold' => $thresholds->maxExecutionTime,
                'actual' => $metrics->executionTime,
                'severity' => 'high',
            ];
        }

        if ($metrics->memoryUsage > $thresholds->maxMemoryUsage) {
            $violations[] = [
                'type' => 'memory_usage',
                'threshold' => $thresholds->maxMemoryUsage,
                'actual' => $metrics->memoryUsage,
                'severity' => 'medium',
            ];
        }

        if ($metrics->cpuUsage > $thresholds->maxCpuUsage) {
            $violations[] = [
                'type' => 'cpu_usage',
                'threshold' => $thresholds->maxCpuUsage,
                'actual' => $metrics->cpuUsage,
                'severity' => 'medium',
            ];
        }

        foreach ($violations as $violation) {
            $this->createAlert($metrics, $violation);
        }
    }

    private function createAlert(PerformanceMetrics $metrics, array $violation): void
    {
        $alertKey = "{$metrics->toolName}_{$violation['type']}";

        // Check cooldown
        $lastAlert = $this->alertHistory[$alertKey] ?? null;
        if ($lastAlert && ($lastAlert['timestamp'] > (time() - self::ALERT_COOLDOWN_SECONDS))) {
            return;
        }

        $alert = [
            'id' => uniqid('alert_', true),
            'tool_name' => $metrics->toolName,
            'method_name' => $metrics->methodName,
            'execution_id' => $metrics->executionId,
            'type' => $violation['type'],
            'severity' => $violation['severity'],
            'threshold' => $violation['threshold'],
            'actual_value' => $violation['actual'],
            'timestamp' => time(),
            'resolved' => false,
            'message' => $this->generateAlertMessage($metrics, $violation),
        ];

        $this->alertHistory[$alertKey] = $alert;
    }

    private function generateAlertMessage(PerformanceMetrics $metrics, array $violation): string
    {
        return match ($violation['type']) {
            'execution_time' => "Tool {$metrics->toolName}::{$metrics->methodName} exceeded execution time threshold: {$violation['actual']}s > {$violation['threshold']}s",
            'memory_usage' => "Tool {$metrics->toolName}::{$metrics->methodName} exceeded memory usage threshold: " . $this->formatBytes($violation['actual']) . " > " . $this->formatBytes($violation['threshold']),
            'cpu_usage' => "Tool {$metrics->toolName}::{$metrics->methodName} exceeded CPU usage threshold: {$violation['actual']}s > {$violation['threshold']}s",
            default => "Performance threshold violated for {$metrics->toolName}::{$metrics->methodName}"
        };
    }

    private function addToRecentMetrics(PerformanceMetrics $metrics): void
    {
        $this->recentMetrics[] = $metrics->toArray();

        // Keep only recent metrics (last 100)
        if (count($this->recentMetrics) > 100) {
            $this->recentMetrics = array_slice($this->recentMetrics, -100);
        }
    }

    private function getDefaultThresholds(string $toolName): PerformanceThreshold
    {
        // Default thresholds based on tool type
        $defaults = match (true) {
            str_contains(strtolower($toolName), 'health') => [
                'max_execution_time' => 1.0,
                'max_memory_usage' => 10 * 1024 * 1024,
                'max_cpu_usage' => 0.5,
            ],
            str_contains(strtolower($toolName), 'file') => [
                'max_execution_time' => 5.0,
                'max_memory_usage' => 50 * 1024 * 1024,
                'max_cpu_usage' => 2.0,
            ],
            str_contains(strtolower($toolName), 'analyz') => [
                'max_execution_time' => 10.0,
                'max_memory_usage' => 100 * 1024 * 1024,
                'max_cpu_usage' => 5.0,
            ],
            default => [
                'max_execution_time' => 3.0,
                'max_memory_usage' => 25 * 1024 * 1024,
                'max_cpu_usage' => 1.0,
            ]
        };

        return new PerformanceThreshold(
            maxExecutionTime: $defaults['max_execution_time'],
            maxMemoryUsage: $defaults['max_memory_usage'],
            maxCpuUsage: $defaults['max_cpu_usage']
        );
    }

    private function loadMetricsFromStorage(string $toolName, ?string $methodName, int $limit): array
    {
        // This would load metrics from persistent storage
        // For now, return recent metrics filtered by tool name
        return array_filter(
            $this->recentMetrics,
            fn ($metric) => $metric['tool_name'] === $toolName &&
                          ($methodName === null || $metric['method_name'] === $methodName)
        );
    }

    private function getRelevantMetrics(?string $toolName, ?string $period): array
    {
        $cutoff = $this->getPeriodCutoff($period);

        return array_filter(
            $this->recentMetrics,
            fn ($metric) => ($toolName === null || $metric['tool_name'] === $toolName) &&
                          $metric['start_time'] >= $cutoff
        );
    }

    private function getPeriodCutoff(?string $period): float
    {
        $now = microtime(true);

        return match ($period) {
            '1h' => $now - 3600,
            '24h' => $now - (24 * 3600),
            '7d' => $now - (7 * 24 * 3600),
            '30d' => $now - (30 * 24 * 3600),
            default => $now - (24 * 3600) // Default to 24h
        };
    }

    private function calculateMedian(array $values): float
    {
        $count = count($values);
        if ($count === 0) {
            return 0.0;
        }

        if ($count % 2 === 0) {
            return ($values[$count / 2 - 1] + $values[$count / 2]) / 2;
        }

        return $values[intval($count / 2)];
    }

    private function calculatePercentile(array $values, int $percentile): float
    {
        $count = count($values);
        if ($count === 0) {
            return 0.0;
        }

        $index = ceil(($percentile / 100) * $count) - 1;

        return $values[max(0, min($index, $count - 1))];
    }

    private function calculateTimespan(array $metrics): float
    {
        if (empty($metrics)) {
            return 0.0;
        }

        $startTimes = array_column($metrics, 'start_time');
        $endTimes = array_column($metrics, 'end_time');

        return max($endTimes) - min($startTimes);
    }

    private function getTopErrors(array $metrics): array
    {
        $errors = [];

        foreach ($metrics as $metric) {
            if (! $metric['success'] && ! empty($metric['error'])) {
                $error = $metric['error'];
                $errors[$error] = ($errors[$error] ?? 0) + 1;
            }
        }

        arsort($errors);

        return array_slice($errors, 0, 5, true);
    }

    private function getSlowExecutions(array $metrics, int $limit): array
    {
        usort($metrics, fn ($a, $b) => $b['execution_time'] <=> $a['execution_time']);

        return array_slice(
            array_map(fn ($m) => [
                'tool' => $m['tool_name'],
                'method' => $m['method_name'],
                'execution_time' => $m['execution_time'],
                'execution_id' => $m['execution_id'],
            ], $metrics),
            0,
            $limit
        );
    }

    private function getMemoryIntensiveExecutions(array $metrics, int $limit): array
    {
        usort($metrics, fn ($a, $b) => $b['memory_usage'] <=> $a['memory_usage']);

        return array_slice(
            array_map(fn ($m) => [
                'tool' => $m['tool_name'],
                'method' => $m['method_name'],
                'memory_usage' => $m['memory_usage'],
                'execution_id' => $m['execution_id'],
            ], $metrics),
            0,
            $limit
        );
    }

    private function calculateTrends(?string $toolName, string $period): array
    {
        // Calculate performance trends over time
        // This would analyze historical data to show improvements/degradation
        return [
            'execution_time_trend' => 'stable',
            'memory_usage_trend' => 'improving',
            'error_rate_trend' => 'stable',
            'throughput_trend' => 'improving',
        ];
    }

    private function generateRecommendations(array $stats): array
    {
        $recommendations = [];

        if ($stats['error_rate'] > 0.1) {
            $recommendations[] = [
                'type' => 'reliability',
                'priority' => 'high',
                'message' => 'Error rate is above 10%. Investigate common failure causes.',
                'current_value' => round($stats['error_rate'] * 100, 1) . '%',
            ];
        }

        if ($stats['average_execution_time'] > 5.0) {
            $recommendations[] = [
                'type' => 'performance',
                'priority' => 'medium',
                'message' => 'Average execution time is high. Consider optimization or caching.',
                'current_value' => round($stats['average_execution_time'], 2) . 's',
            ];
        }

        return $recommendations;
    }

    private function calculateHealthScore(array $stats): int
    {
        $score = 100;

        // Deduct for high error rate
        $score -= ($stats['error_rate'] * 50);

        // Deduct for slow execution
        if ($stats['average_execution_time'] > 5.0) {
            $score -= 20;
        } elseif ($stats['average_execution_time'] > 2.0) {
            $score -= 10;
        }

        // Deduct for high memory usage
        if ($stats['average_memory_usage'] > 100 * 1024 * 1024) {
            $score -= 15;
        }

        return max(0, (int) $score);
    }

    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $unitIndex = 0;
        $value = $bytes;

        while ($value >= 1024 && $unitIndex < count($units) - 1) {
            $value /= 1024;
            $unitIndex++;
        }

        return round($value, 2) . ' ' . $units[$unitIndex];
    }
}
