<?php

declare(strict_types=1);

namespace App\Framework\Metrics\Formatters;

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

/**
 * Formats metrics in StatsD format for push-based metrics
 * @see https://github.com/statsd/statsd/blob/master/docs/metric_types.md
 */
final readonly class StatsDFormatter implements MetricsFormatter
{
    public function __construct(
        private string $prefix = '',
        private string $tagSeparator = ',' // Can be ',' or ';' depending on StatsD implementation
    ) {
    }

    public function format(MetricsCollection $metrics): string
    {
        $lines = [];

        foreach ($metrics->getAllMetrics() as $metric) {
            $line = $this->formatMetric($metric);
            if ($line !== null) {
                $lines[] = $line;
            }
        }

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

    private function formatMetric(Metric $metric): ?string
    {
        $name = $this->prefix . $metric->name;
        $value = $metric->value;

        // Determine StatsD metric type suffix
        $typeSuffix = match($metric->type) {
            MetricType::COUNTER => 'c',
            MetricType::GAUGE => 'g',
            MetricType::HISTOGRAM => 'h',
            MetricType::SUMMARY => 'ms', // StatsD uses timing for summary-like metrics
        };

        // Format: metric.name:value|type|@sample_rate|#tag1:value,tag2:value
        $line = sprintf('%s:%s|%s', $name, $this->formatValue($value), $typeSuffix);

        // Add tags if present
        if (! empty($metric->labels)) {
            $line .= '|#' . $this->formatTags($metric->labels);
        }

        return $line;
    }

    private function formatValue(float $value): string
    {
        // StatsD doesn't support Inf or NaN
        if (is_infinite($value) || is_nan($value)) {
            return '0';
        }

        // Format based on whether it's an integer or float
        if ($value == floor($value)) {
            return (string) (int) $value;
        }

        // Keep decimal precision but remove trailing zeros
        $formatted = sprintf('%.6f', $value);
        $formatted = rtrim($formatted, '0');
        $formatted = rtrim($formatted, '.');

        return $formatted;
    }

    private function formatTags(array $labels): string
    {
        $tags = [];

        foreach ($labels as $key => $value) {
            // Escape special characters in StatsD tags
            $escapedKey = str_replace([':', '|', '@', ','], '_', $key);
            $escapedValue = str_replace([':', '|', '@', ','], '_', $value);
            $tags[] = sprintf('%s:%s', $escapedKey, $escapedValue);
        }

        return implode($this->tagSeparator, $tags);
    }

    public function getContentType(): string
    {
        return 'text/plain; charset=utf-8';
    }

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