<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Observability;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Metrics\Metric;
use App\Framework\Metrics\MetricType;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\Performance\PerformanceCollector;

/**
 * Component Metrics Collector
 *
 * Spezialisierte Metrics für LiveComponents:
 * - Render-Zeiten (hydrate_ms, render_ms, action_ms)
 * - Cache-Effizienz (cache_hit_rate, cache_miss_total)
 * - Action-Performance (action_latency_ms, action_error_rate)
 * - Event-Tracking (events_dispatched_total, events_received_total)
 * - Upload-Performance (upload_chunk_duration_ms, upload_success_rate)
 */
final class ComponentMetricsCollector
{
    /** @var array<string, Metric> */
    private array $metrics = [];

    public function __construct(
        private readonly ?PerformanceCollector $performanceCollector = null
    ) {
    }

    /**
     * Record component render time
     */
    public function recordRender(string $componentId, float $durationMs, bool $cached = false): void
    {
        $this->increment('livecomponent_renders_total', [
            'component_id' => $componentId,
            'cached' => $cached ? 'true' : 'false',
        ]);

        $this->recordHistogram(
            'livecomponent_render_duration_ms',
            $durationMs,
            ['component_id' => $componentId, 'cached' => $cached ? 'true' : 'false']
        );

        // Integrate with framework performance collector
        if ($this->performanceCollector !== null) {
            $this->performanceCollector->recordMetric(
                "livecomponent.render.{$componentId}",
                PerformanceCategory::RENDERING,
                $durationMs,
                ['cached' => $cached]
            );
        }
    }

    /**
     * Record action execution time
     */
    public function recordAction(
        string $componentId,
        string $actionName,
        float $durationMs,
        bool $success = true
    ): void {
        $this->increment('livecomponent_actions_total', [
            'component_id' => $componentId,
            'action' => $actionName,
            'status' => $success ? 'success' : 'error',
        ]);

        $this->recordHistogram(
            'livecomponent_action_duration_ms',
            $durationMs,
            ['component_id' => $componentId, 'action' => $actionName]
        );

        if (!$success) {
            $this->increment('livecomponent_action_errors_total', [
                'component_id' => $componentId,
                'action' => $actionName,
            ]);
        }

        // Framework integration
        if ($this->performanceCollector !== null) {
            $this->performanceCollector->recordMetric(
                "livecomponent.action.{$componentId}.{$actionName}",
                PerformanceCategory::APPLICATION,
                $durationMs,
                ['success' => $success]
            );
        }
    }

    /**
     * Record cache hit/miss
     */
    public function recordCacheHit(string $componentId, bool $hit): void
    {
        $metricName = $hit ? 'livecomponent_cache_hits_total' : 'livecomponent_cache_misses_total';

        $this->increment($metricName, ['component_id' => $componentId]);
    }

    /**
     * Record event dispatch
     */
    public function recordEventDispatched(string $componentId, string $eventName): void
    {
        $this->increment('livecomponent_events_dispatched_total', [
            'component_id' => $componentId,
            'event' => $eventName,
        ]);
    }

    /**
     * Record event received
     */
    public function recordEventReceived(string $componentId, string $eventName): void
    {
        $this->increment('livecomponent_events_received_total', [
            'component_id' => $componentId,
            'event' => $eventName,
        ]);
    }

    /**
     * Record hydration time
     */
    public function recordHydration(string $componentId, float $durationMs): void
    {
        $this->recordHistogram(
            'livecomponent_hydration_duration_ms',
            $durationMs,
            ['component_id' => $componentId]
        );
    }

    /**
     * Record batch operation
     */
    public function recordBatch(int $operationCount, float $durationMs, int $successCount, int $failureCount): void
    {
        $this->increment('livecomponent_batch_operations_total', ['status' => 'executed']);

        $this->recordHistogram('livecomponent_batch_size', (float) $operationCount);
        $this->recordHistogram('livecomponent_batch_duration_ms', $durationMs);

        if ($successCount > 0) {
            $this->increment('livecomponent_batch_success_total', [], $successCount);
        }

        if ($failureCount > 0) {
            $this->increment('livecomponent_batch_failure_total', [], $failureCount);
        }
    }

    /**
     * Record fragment update
     */
    public function recordFragmentUpdate(string $componentId, int $fragmentCount, float $durationMs): void
    {
        $this->increment('livecomponent_fragment_updates_total', [
            'component_id' => $componentId,
        ]);

        $this->recordHistogram('livecomponent_fragment_count', (float) $fragmentCount);
        $this->recordHistogram('livecomponent_fragment_duration_ms', $durationMs);
    }

    /**
     * Record upload chunk
     */
    public function recordUploadChunk(
        string $sessionId,
        int $chunkIndex,
        float $durationMs,
        bool $success = true
    ): void {
        $this->increment('livecomponent_upload_chunks_total', [
            'session_id' => $sessionId,
            'status' => $success ? 'success' : 'error',
        ]);

        $this->recordHistogram(
            'livecomponent_upload_chunk_duration_ms',
            $durationMs,
            ['session_id' => $sessionId]
        );
    }

    /**
     * Record upload completion
     */
    public function recordUploadComplete(string $sessionId, float $totalDurationMs, int $totalChunks): void
    {
        $this->increment('livecomponent_uploads_completed_total', ['session_id' => $sessionId]);

        $this->recordHistogram('livecomponent_upload_total_duration_ms', $totalDurationMs);
        $this->recordHistogram('livecomponent_upload_chunk_count', (float) $totalChunks);
    }

    /**
     * Get all metrics
     *
     * @return array<string, Metric>
     */
    public function getMetrics(): array
    {
        return $this->metrics;
    }

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

    /**
     * Get metrics summary for monitoring/dashboard
     */
    public function getSummary(): array
    {
        $summary = [
            'total_renders' => 0,
            'total_actions' => 0,
            'cache_hits' => 0,
            'cache_misses' => 0,
            'total_events' => 0,
            'action_errors' => 0,
            'avg_render_time_ms' => 0.0,
            'avg_action_time_ms' => 0.0,
            'cache_hit_rate' => 0.0,
        ];

        // Calculate totals
        foreach ($this->metrics as $metric) {
            if (str_contains($metric->name, 'renders_total')) {
                $summary['total_renders'] += (int) $metric->value;
            } elseif (str_contains($metric->name, 'actions_total')) {
                $summary['total_actions'] += (int) $metric->value;
            } elseif (str_contains($metric->name, 'cache_hits_total')) {
                $summary['cache_hits'] += (int) $metric->value;
            } elseif (str_contains($metric->name, 'cache_misses_total')) {
                $summary['cache_misses'] += (int) $metric->value;
            } elseif (str_contains($metric->name, 'events_dispatched_total')) {
                $summary['total_events'] += (int) $metric->value;
            } elseif (str_contains($metric->name, 'action_errors_total')) {
                $summary['action_errors'] += (int) $metric->value;
            }
        }

        // Calculate cache hit rate
        $totalCacheAccess = $summary['cache_hits'] + $summary['cache_misses'];
        if ($totalCacheAccess > 0) {
            $summary['cache_hit_rate'] = ($summary['cache_hits'] / $totalCacheAccess) * 100;
        }

        return $summary;
    }

    /**
     * Export metrics in Prometheus format
     */
    public function exportPrometheus(): string
    {
        $output = "# HELP LiveComponents metrics\n";
        $output .= "# TYPE livecomponent_* counter/histogram\n\n";

        foreach ($this->metrics as $metric) {
            $labels = $metric->getFormattedLabels();
            $timestamp = $metric->timestamp?->toTimestamp() ?? time();

            $output .= sprintf(
                "%s%s %.2f %d\n",
                $metric->name,
                $labels,
                $metric->value,
                $timestamp
            );
        }

        return $output;
    }

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

    /**
     * Increment counter metric
     */
    private function increment(string $name, array $labels = [], int $amount = 1): void
    {
        $key = $this->buildMetricKey($name, $labels);

        if (!isset($this->metrics[$key])) {
            $this->metrics[$key] = new Metric(
                name: $name,
                value: 0,
                type: MetricType::COUNTER,
                labels: $labels,
                timestamp: Timestamp::now()
            );
        }

        $currentValue = $this->metrics[$key]->value;
        $this->metrics[$key] = $this->metrics[$key]
            ->withValue($currentValue + $amount)
            ->withTimestamp(Timestamp::now());
    }

    /**
     * Record histogram metric
     */
    private function recordHistogram(string $name, float $value, array $labels = []): void
    {
        $key = $this->buildMetricKey($name, $labels);

        if (!isset($this->metrics[$key])) {
            $this->metrics[$key] = new Metric(
                name: $name,
                value: $value,
                type: MetricType::HISTOGRAM,
                labels: $labels,
                unit: 'ms',
                timestamp: Timestamp::now()
            );
        } else {
            // For histograms, we keep updating with new observations
            $this->metrics[$key] = $this->metrics[$key]
                ->withValue($value)
                ->withTimestamp(Timestamp::now());
        }
    }

    /**
     * Build unique metric key from name and labels
     */
    private function buildMetricKey(string $name, array $labels): string
    {
        if (empty($labels)) {
            return $name;
        }

        ksort($labels);
        $labelString = implode(',', array_map(
            fn($key, $value) => "{$key}={$value}",
            array_keys($labels),
            $labels
        ));

        return "{$name}{{$labelString}}";
    }
}
