<?php

declare(strict_types=1);

use App\Framework\LiveComponents\Observability\ComponentMetricsCollector;
use App\Framework\Core\ValueObjects\Timestamp;

describe('ComponentMetricsCollector - Prometheus Export Validation', function () {
    beforeEach(function () {
        $this->collector = new ComponentMetricsCollector();
    });

    describe('Prometheus Format Validation', function () {
        it('exports valid prometheus format with header', function () {
            $this->collector->recordRender('test-component', 50.5);

            $prometheus = $this->collector->exportPrometheus();

            expect($prometheus)->toContain('# HELP LiveComponents metrics');
            expect($prometheus)->toContain('# TYPE livecomponent_* counter/histogram');
        });

        it('exports metrics with proper line format', function () {
            $this->collector->recordRender('test-component', 50.5);

            $prometheus = $this->collector->exportPrometheus();
            $lines = explode("\n", trim($prometheus));

            // Find metric line (skip header comments)
            $metricLines = array_filter($lines, fn($line) => !str_starts_with($line, '#') && !empty($line));

            foreach ($metricLines as $line) {
                // Format: metric_name{labels} value timestamp
                expect($line)->toMatch('/^[a-z_]+\{.*\}\s+[\d.]+\s+\d+$/');
            }
        });

        it('exports counter metrics correctly', function () {
            $this->collector->recordRender('counter-test', 10.0);

            $prometheus = $this->collector->exportPrometheus();

            // Counter should be in format: livecomponent_renders_total{...} 1.00 timestamp
            expect($prometheus)->toContain('livecomponent_renders_total');
            expect($prometheus)->toMatch('/livecomponent_renders_total\{.*\}\s+1\.00\s+\d+/');
        });

        it('exports histogram metrics correctly', function () {
            $this->collector->recordRender('histogram-test', 45.75);

            $prometheus = $this->collector->exportPrometheus();

            // Histogram should be in format: livecomponent_render_duration_ms{...} 45.75 timestamp
            expect($prometheus)->toContain('livecomponent_render_duration_ms');
            expect($prometheus)->toMatch('/livecomponent_render_duration_ms\{.*\}\s+45\.75\s+\d+/');
        });
    });

    describe('Label Formatting', function () {
        it('formats labels with alphabetical sorting', function () {
            $this->collector->recordAction('component-1', 'submit', 100.0);

            $prometheus = $this->collector->exportPrometheus();

            // Labels should be sorted: action, component_id, status
            expect($prometheus)->toContain('action="submit"');
            expect($prometheus)->toContain('component_id="component-1"');
            expect($prometheus)->toContain('status="success"');

            // Verify alphabetical order in label string
            $metricLines = array_filter(
                explode("\n", $prometheus),
                fn($line) => str_contains($line, 'livecomponent_actions_total')
            );

            foreach ($metricLines as $line) {
                // Extract label section between {}
                preg_match('/\{(.*?)\}/', $line, $matches);
                if (isset($matches[1])) {
                    $labels = explode(',', $matches[1]);
                    $labelKeys = array_map(fn($label) => explode('=', $label)[0], $labels);

                    // Verify alphabetical order
                    $sortedKeys = $labelKeys;
                    sort($sortedKeys);
                    expect($labelKeys)->toBe($sortedKeys);
                }
            }
        });

        it('escapes special characters in label values', function () {
            // Component with special characters: quotes, backslashes, newlines
            $this->collector->recordRender('test"component', 50.0);
            $this->collector->recordAction('comp\\path', "action\nname", 10.0); // Use double quotes for actual newline

            $prometheus = $this->collector->exportPrometheus();

            // Quotes should be escaped as \"
            expect($prometheus)->toContain('test\\"component');

            // Backslashes should be escaped as \\
            expect($prometheus)->toContain('comp\\\\path');

            // Newlines should be escaped as \n in Prometheus format
            // The actual newline character (\n) should appear as backslash-n in the output
            expect($prometheus)->toMatch('/action\\\\nname/'); // Regex to find literal \n
        });

        it('handles empty labels correctly', function () {
            // recordHistogram with no labels (internal method tested via batch)
            $this->collector->recordBatch(10, 500.0, 8, 2);

            $prometheus = $this->collector->exportPrometheus();

            // Some metrics should have empty labels {}
            expect($prometheus)->toContain('livecomponent_batch_size');
            expect($prometheus)->toContain('livecomponent_batch_duration_ms');

            // These metrics have labels
            expect($prometheus)->toContain('livecomponent_batch_operations_total{status="executed"}');
        });

        it('formats boolean labels as strings', function () {
            $this->collector->recordRender('component-1', 50.0, cached: false);
            $this->collector->recordRender('component-2', 25.0, cached: true);

            $prometheus = $this->collector->exportPrometheus();

            // Boolean should be formatted as "true" or "false" strings
            expect($prometheus)->toContain('cached="false"');
            expect($prometheus)->toContain('cached="true"');
            expect($prometheus)->not->toContain('cached=true');
            expect($prometheus)->not->toContain('cached=false');
        });
    });

    describe('Value Formatting', function () {
        it('formats float values with 2 decimal places', function () {
            $this->collector->recordRender('test', 123.456);

            $prometheus = $this->collector->exportPrometheus();

            // Values should have .2f format
            expect($prometheus)->toContain('123.46'); // Rounded to 2 decimals
        });

        it('formats integer values with .00', function () {
            $this->collector->recordAction('test', 'click', 100.0); // Creates counter = 1

            $prometheus = $this->collector->exportPrometheus();

            // Counter value should be 1.00
            expect($prometheus)->toMatch('/livecomponent_actions_total\{.*\}\s+1\.00/');
        });

        it('handles zero values correctly', function () {
            // Create metric and then subtract to get 0 (through reset and re-create)
            $this->collector->recordRender('test', 0.0);

            $prometheus = $this->collector->exportPrometheus();

            expect($prometheus)->toContain('0.00');
        });
    });

    describe('Timestamp Formatting', function () {
        it('includes unix timestamp for each metric', function () {
            $beforeTime = time();
            $this->collector->recordRender('test', 50.0);
            $afterTime = time();

            $prometheus = $this->collector->exportPrometheus();

            // Extract timestamp from metric line
            preg_match('/livecomponent_renders_total\{.*\}\s+[\d.]+\s+(\d+)/', $prometheus, $matches);

            expect($matches)->toHaveKey(1);
            $timestamp = (int) $matches[1];

            // Timestamp should be within test execution time range
            expect($timestamp)->toBeGreaterThanOrEqual($beforeTime);
            expect($timestamp)->toBeLessThanOrEqual($afterTime + 1); // +1 for race condition
        });

        it('uses metric timestamp if available', function () {
            $this->collector->recordRender('test', 50.0);

            $prometheus = $this->collector->exportPrometheus();

            // Timestamp should be present in all metric lines
            $metricLines = array_filter(
                explode("\n", $prometheus),
                fn($line) => !str_starts_with($line, '#') && !empty($line)
            );

            foreach ($metricLines as $line) {
                expect($line)->toMatch('/\d+$/'); // Line ends with timestamp
            }
        });
    });

    describe('Multiple Metrics Export', function () {
        it('exports multiple metrics with different labels', function () {
            $this->collector->recordRender('component-1', 50.0, cached: false);
            $this->collector->recordRender('component-2', 30.0, cached: true);
            $this->collector->recordAction('component-1', 'submit', 100.0);
            $this->collector->recordAction('component-2', 'click', 75.0);

            $prometheus = $this->collector->exportPrometheus();

            // Should have multiple metric lines
            $metricLines = array_filter(
                explode("\n", $prometheus),
                fn($line) => !str_starts_with($line, '#') && !empty($line)
            );

            expect(count($metricLines))->toBeGreaterThan(4); // At least 2 renders + 2 actions (each creates 2 metrics)
        });

        it('exports metrics in consistent format', function () {
            $this->collector->recordRender('comp-1', 10.0);
            $this->collector->recordAction('comp-2', 'test', 20.0);
            $this->collector->recordCacheHit('comp-3', true);
            $this->collector->recordEventDispatched('comp-4', 'updated');

            $prometheus = $this->collector->exportPrometheus();

            $metricLines = array_filter(
                explode("\n", $prometheus),
                fn($line) => !str_starts_with($line, '#') && !empty($line)
            );

            // All lines should match Prometheus format
            foreach ($metricLines as $line) {
                // Format: metric_name{labels} value timestamp
                expect($line)->toMatch('/^[a-z_]+(\{.*?\})?\s+[\d.]+\s+\d+$/');
            }
        });
    });

    describe('Special Cases', function () {
        it('handles empty metrics collection', function () {
            $prometheus = $this->collector->exportPrometheus();

            // Should still have headers even with no metrics
            expect($prometheus)->toContain('# HELP LiveComponents metrics');
            expect($prometheus)->toContain('# TYPE livecomponent_* counter/histogram');

            // Should only have headers (2 lines + empty line)
            $lines = explode("\n", $prometheus);
            $metricLines = array_filter($lines, fn($line) => !str_starts_with($line, '#') && !empty($line));

            expect(count($metricLines))->toBe(0);
        });

        it('handles metrics with complex label combinations', function () {
            $this->collector->recordAction('cart-component', 'add-to-cart', 150.0, success: true);
            $this->collector->recordAction('cart-component', 'add-to-cart', 200.0, success: false);
            $this->collector->recordAction('checkout-component', 'submit-payment', 300.0, success: true);

            $prometheus = $this->collector->exportPrometheus();

            // Should have multiple metrics with different label combinations
            expect($prometheus)->toContain('component_id="cart-component"');
            expect($prometheus)->toContain('action="add-to-cart"');
            expect($prometheus)->toContain('status="success"');
            expect($prometheus)->toContain('status="error"');
            expect($prometheus)->toContain('component_id="checkout-component"');
            expect($prometheus)->toContain('action="submit-payment"');
        });

        it('handles incremental counter updates', function () {
            // Record same action 3 times
            $this->collector->recordAction('test', 'click', 10.0);
            $this->collector->recordAction('test', 'click', 15.0);
            $this->collector->recordAction('test', 'click', 20.0);

            $prometheus = $this->collector->exportPrometheus();

            // Counter should be incremented to 3
            preg_match('/livecomponent_actions_total\{.*\}\s+([\d.]+)/', $prometheus, $matches);

            expect($matches)->toHaveKey(1);
            expect((float) $matches[1])->toBe(3.0);
        });
    });

    describe('Integration with Metric Types', function () {
        it('exports all LiveComponent metric types', function () {
            // Render metrics
            $this->collector->recordRender('comp', 50.0);

            // Action metrics
            $this->collector->recordAction('comp', 'submit', 100.0);

            // Cache metrics
            $this->collector->recordCacheHit('comp', true);

            // Event metrics
            $this->collector->recordEventDispatched('comp', 'updated');
            $this->collector->recordEventReceived('comp', 'refresh');

            // Hydration metrics
            $this->collector->recordHydration('comp', 30.0);

            // Batch metrics
            $this->collector->recordBatch(10, 500.0, 8, 2);

            // Fragment metrics
            $this->collector->recordFragmentUpdate('comp', 5, 75.0);

            // Upload metrics
            $this->collector->recordUploadChunk('session-1', 1, 200.0);
            $this->collector->recordUploadComplete('session-1', 2000.0, 10);

            $prometheus = $this->collector->exportPrometheus();

            // Verify all metric types are present
            expect($prometheus)->toContain('livecomponent_renders_total');
            expect($prometheus)->toContain('livecomponent_render_duration_ms');
            expect($prometheus)->toContain('livecomponent_actions_total');
            expect($prometheus)->toContain('livecomponent_action_duration_ms');
            expect($prometheus)->toContain('livecomponent_cache_hits_total');
            expect($prometheus)->toContain('livecomponent_events_dispatched_total');
            expect($prometheus)->toContain('livecomponent_events_received_total');
            expect($prometheus)->toContain('livecomponent_hydration_duration_ms');
            expect($prometheus)->toContain('livecomponent_batch_operations_total');
            expect($prometheus)->toContain('livecomponent_fragment_updates_total');
            expect($prometheus)->toContain('livecomponent_upload_chunks_total');
            expect($prometheus)->toContain('livecomponent_uploads_completed_total');
        });
    });

    describe('Prometheus Format Compliance', function () {
        it('follows Prometheus metric naming conventions', function () {
            $this->collector->recordRender('test', 50.0);
            $this->collector->recordAction('test', 'click', 100.0);

            $prometheus = $this->collector->exportPrometheus();

            // Metric names should be lowercase with underscores
            // Should end with _total for counters or _ms for duration histograms
            $metricLines = array_filter(
                explode("\n", $prometheus),
                fn($line) => !str_starts_with($line, '#') && !empty($line)
            );

            foreach ($metricLines as $line) {
                preg_match('/^([a-z_]+)/', $line, $matches);
                $metricName = $matches[1] ?? '';

                expect($metricName)->toMatch('/^[a-z][a-z0-9_]*$/');

                // Counters should end with _total
                if (str_contains($line, 'renders_total') || str_contains($line, 'actions_total')) {
                    expect($metricName)->toMatch('/_total$/');
                }

                // Duration metrics should end with _ms
                if (str_contains($line, 'duration')) {
                    expect($metricName)->toMatch('/_ms$/');
                }
            }
        });

        it('uses valid label key naming', function () {
            $this->collector->recordAction('test', 'submit', 100.0);

            $prometheus = $this->collector->exportPrometheus();

            // Extract label keys
            preg_match_all('/([a-z_]+)="[^"]*"/', $prometheus, $matches);
            $labelKeys = $matches[1] ?? [];

            // Label keys should be lowercase with underscores
            foreach ($labelKeys as $key) {
                expect($key)->toMatch('/^[a-z][a-z0-9_]*$/');
            }
        });

        it('produces parseable prometheus output', function () {
            $this->collector->recordRender('test', 50.0);
            $this->collector->recordAction('test', 'click', 100.0);

            $prometheus = $this->collector->exportPrometheus();

            // Basic structure validation
            $lines = explode("\n", $prometheus);

            foreach ($lines as $line) {
                if (empty($line)) {
                    continue;
                }

                // Either comment or metric line
                if (str_starts_with($line, '#')) {
                    // Comment line - should start with # HELP or # TYPE
                    expect($line)->toMatch('/^#\s+(HELP|TYPE)\s+/');
                } else {
                    // Metric line - should have metric name, optional labels, value, timestamp
                    expect($line)->toMatch('/^[a-z_]+(\{.*?\})?\s+[\d.]+\s+\d+$/');
                }
            }
        });
    });
});
