<?php

declare(strict_types=1);

namespace App\Framework\Telemetry\Exporters;

use App\Framework\Telemetry\Config\ExporterConfig;
use App\Framework\Telemetry\ValueObjects\Event;
use App\Framework\Telemetry\ValueObjects\Metric;
use App\Framework\Telemetry\ValueObjects\Operation;

/**
 * Exports telemetry data in Prometheus format
 */
final class PrometheusExporter implements TelemetryExporterInterface
{
    /**
     * @var array<string, array{type: string, help: string, values: array<string, float>}>
     */
    private array $metrics = [];

    /**
     * @param string $endpoint Endpoint for the Prometheus metrics (not used directly)
     */
    public function __construct(
        private readonly string $endpoint = ''
    ) {
    }

    /**
     * Create from exporter config
     */
    public static function fromConfig(ExporterConfig $config): self
    {
        return new self($config->getEndpoint());
    }

    /**
     * Export an operation
     */
    public function exportOperation(Operation $operation): void
    {
        if (! $operation->isCompleted()) {
            return; // Only export completed operations
        }

        // Record operation duration as histogram
        $duration = $operation->getDuration() ?? 0;
        $this->recordHistogram(
            name: 'operation_duration_milliseconds',
            value: $duration,
            labels: [
                'name' => $operation->name,
                'type' => $operation->type,
                'status' => $operation->status,
            ],
            help: 'Duration of operations in milliseconds'
        );

        // Record operation count
        $this->incrementCounter(
            name: 'operations_total',
            labels: [
                'name' => $operation->name,
                'type' => $operation->type,
                'status' => $operation->status,
            ],
            help: 'Total number of operations'
        );
    }

    /**
     * Export a metric
     */
    public function exportMetric(Metric $metric): void
    {
        $name = $metric->getPrometheusName();
        $type = $metric->getPrometheusType();
        $help = $metric->getPrometheusHelp();
        $labels = $this->formatLabels($metric->attributes);

        switch ($type) {
            case 'counter':
                $this->recordCounter($name, $metric->value, $labels, $help);

                break;
            case 'gauge':
                $this->recordGauge($name, $metric->value, $labels, $help);

                break;
            case 'histogram':
                $this->recordHistogram($name, $metric->value, $labels, $help);

                break;
            default:
                $this->recordGauge($name, $metric->value, $labels, $help);
        }
    }

    /**
     * Export an event
     */
    public function exportEvent(Event $event): void
    {
        // Events are exported as counters with the event name as a label
        $this->incrementCounter(
            name: "events_{$event->severity}_total",
            labels: array_merge(
                ['event_name' => $event->name],
                $this->extractScalarAttributes($event->attributes)
            ),
            help: "Total number of {$event->severity} events"
        );
    }

    /**
     * Flush any buffered telemetry data
     */
    public function flush(): void
    {
        // No buffering in this implementation
    }

    /**
     * Get the metrics output in Prometheus format
     */
    public function getMetricsOutput(): string
    {
        $output = [];

        foreach ($this->metrics as $name => $metric) {
            $output[] = "# HELP {$name} {$metric['help']}";
            $output[] = "# TYPE {$name} {$metric['type']}";

            foreach ($metric['values'] as $labels => $value) {
                $output[] = "{$name}{$labels} {$value}";
            }

            // Add an empty line between metrics
            $output[] = "";
        }

        return implode("\n", $output);
    }

    /**
     * Record a counter metric
     *
     * @param string $name Metric name
     * @param float $value Metric value
     * @param array<string, mixed> $labels Metric labels
     * @param string $help Help text for the metric
     */
    public function recordCounter(string $name, float $value, array $labels = [], string $help = ''): void
    {
        $this->ensureMetricExists($name, 'counter', $help);
        $labelString = $this->formatLabels($labels);
        $this->metrics[$name]['values'][$labelString] = $value;
    }

    /**
     * Increment a counter metric
     *
     * @param string $name Metric name
     * @param array<string, mixed> $labels Metric labels
     * @param string $help Help text for the metric
     * @param float $increment Amount to increment by (default: 1)
     */
    public function incrementCounter(string $name, array $labels = [], string $help = '', float $increment = 1.0): void
    {
        $this->ensureMetricExists($name, 'counter', $help);
        $labelString = $this->formatLabels($labels);

        if (! isset($this->metrics[$name]['values'][$labelString])) {
            $this->metrics[$name]['values'][$labelString] = 0;
        }

        $this->metrics[$name]['values'][$labelString] += $increment;
    }

    /**
     * Record a gauge metric
     *
     * @param string $name Metric name
     * @param float $value Metric value
     * @param array<string, mixed> $labels Metric labels
     * @param string $help Help text for the metric
     */
    public function recordGauge(string $name, float $value, array $labels = [], string $help = ''): void
    {
        $this->ensureMetricExists($name, 'gauge', $help);
        $labelString = $this->formatLabels($labels);
        $this->metrics[$name]['values'][$labelString] = $value;
    }

    /**
     * Record a histogram metric
     *
     * @param string $name Metric name
     * @param float $value Metric value
     * @param array<string, mixed> $labels Metric labels
     * @param string $help Help text for the metric
     */
    public function recordHistogram(string $name, float $value, array $labels = [], string $help = ''): void
    {
        // For simplicity, we're just recording the value as a gauge
        // In a real implementation, we would maintain buckets for histograms
        $this->ensureMetricExists($name, 'histogram', $help);
        $labelString = $this->formatLabels($labels);
        $this->metrics[$name]['values'][$labelString] = $value;
    }

    /**
     * Ensure a metric exists in the metrics array
     */
    private function ensureMetricExists(string $name, string $type, string $help): void
    {
        if (! isset($this->metrics[$name])) {
            $this->metrics[$name] = [
                'type' => $type,
                'help' => $help ?: $name,
                'values' => [],
            ];
        }
    }

    /**
     * Format labels as a Prometheus label string
     *
     * @param array<string, mixed> $labels
     */
    private function formatLabels(array $labels): string
    {
        if (empty($labels)) {
            return '';
        }

        $formattedLabels = [];
        foreach ($labels as $key => $value) {
            // Skip non-scalar values
            if (is_scalar($value)) {
                $escapedValue = str_replace(['\\', '"', "\n"], ['\\\\', '\\"', '\\n'], (string)$value);
                $formattedLabels[] = "{$key}=\"{$escapedValue}\"";
            }
        }

        if (empty($formattedLabels)) {
            return '';
        }

        return '{' . implode(',', $formattedLabels) . '}';
    }

    /**
     * Extract scalar attributes from an array
     *
     * @param array<string, mixed> $attributes
     * @return array<string, scalar>
     */
    private function extractScalarAttributes(array $attributes): array
    {
        $result = [];
        foreach ($attributes as $key => $value) {
            if (is_scalar($value)) {
                $result[$key] = $value;
            }
        }

        return $result;
    }
}
