<?php

declare(strict_types=1);

namespace App\Framework\Metrics\Formatters;

use App\Framework\Metrics\Metric;
use App\Framework\Metrics\MetricsCollection;
use App\Framework\Metrics\MetricSuffix;
use App\Framework\Metrics\MetricType;

/**
 * Formats metrics in OpenMetrics format
 * OpenMetrics is a CNCF standard that extends Prometheus format
 * @see https://openmetrics.io/
 */
final readonly class OpenMetricsFormatter implements MetricsFormatter
{
    public function format(MetricsCollection $metrics): string
    {
        $output = [];
        $processedMetrics = [];

        foreach ($metrics->getMetrics() as $metricName => $metricGroup) {
            // Skip if already processed
            if (isset($processedMetrics[$metricName])) {
                continue;
            }

            $firstMetric = $metricGroup[0];
            $baseName = MetricSuffix::getBaseName($metricName);

            // HELP line (required in OpenMetrics)
            $help = $firstMetric->help ?? 'No description provided';
            $output[] = sprintf('# HELP %s %s', $baseName, $help);

            // TYPE line (required in OpenMetrics)
            $type = $this->determineMetricType($metricName, $firstMetric->type);
            $output[] = sprintf('# TYPE %s %s', $baseName, $type->value);

            // UNIT line (OpenMetrics specific, optional)
            if ($firstMetric->unit !== null) {
                $output[] = sprintf('# UNIT %s %s', $baseName, $firstMetric->unit);
            }

            // Add metric values
            foreach ($metricGroup as $metric) {
                $output[] = $this->formatMetric($metric);
            }

            $processedMetrics[$metricName] = true;

            // Mark related metrics as processed
            if ($type === MetricType::HISTOGRAM) {
                foreach (MetricSuffix::histogramSuffixes() as $suffix) {
                    $processedMetrics[$suffix->addTo($baseName)] = true;
                }
            }
        }

        // OpenMetrics requires EOF marker
        $output[] = '# EOF';

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

    private function formatMetric(Metric $metric): string
    {
        $labels = $metric->getFormattedLabels();
        $name = $metric->name;

        // OpenMetrics doesn't append unit to metric name (uses UNIT metadata instead)

        $line = sprintf('%s%s %s', $name, $labels, $this->formatValue($metric->value));

        // Add timestamp if present (seconds with fractional part for OpenMetrics)
        if ($metric->timestamp !== null) {
            $line .= ' ' . number_format($metric->timestamp->toFloat(), 3, '.', '');
        }

        return $line;
    }

    private function formatValue(float $value): string
    {
        // Handle special float values (OpenMetrics spec)
        if (is_infinite($value)) {
            return $value > 0 ? '+Inf' : '-Inf';
        }

        if (is_nan($value)) {
            return 'NaN';
        }

        // Use scientific notation for very large/small numbers
        if (abs($value) >= 1e10 || (abs($value) < 1e-10 && $value != 0)) {
            return sprintf('%e', $value);
        }

        // Regular formatting
        $formatted = sprintf('%.10f', $value);
        $formatted = rtrim($formatted, '0');
        $formatted = rtrim($formatted, '.');

        return $formatted;
    }

    private function determineMetricType(string $name, MetricType $type): MetricType
    {
        if (MetricSuffix::hasHistogramSuffix($name)) {
            return MetricType::HISTOGRAM;
        }

        return $type;
    }

    public function getContentType(): string
    {
        return 'application/openmetrics-text; version=1.0.0; charset=utf-8';
    }

    public function getName(): string
    {
        return 'openmetrics';
    }
}
