using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using ZB.MOM.WW.Telemetry;

namespace ZB.MOM.WW.Telemetry.Tests;

public sealed class AddZbTelemetryTests
{
    // Fix #2: empty ServiceName must throw ArgumentException --------------------------

    [Fact]
    public void AddZbTelemetry_Throws_WhenServiceNameIsEmpty()
    {
        var builder = WebApplication.CreateBuilder();
        var ex = Assert.Throws<ArgumentException>(() =>
            builder.AddZbTelemetry(o =>
            {
                o.ServiceName = "";   // explicitly empty
            }));
        Assert.Equal("configure", ex.ParamName);
    }

    [Fact]
    public void AddZbTelemetry_Throws_WhenServiceNameIsWhitespace()
    {
        var builder = WebApplication.CreateBuilder();
        var ex = Assert.Throws<ArgumentException>(() =>
            builder.AddZbTelemetry(o =>
            {
                o.ServiceName = "   ";
            }));
        Assert.Equal("configure", ex.ParamName);
    }

    // Telemetry-006: malformed/missing OtlpEndpoint must fail fast with a clear, named error
    // instead of a late UriFormatException deep inside exporter construction.

    [Fact]
    public void AddZbTelemetry_Throws_WhenOtlpExporterHasMalformedEndpoint()
    {
        var builder = WebApplication.CreateBuilder();
        var ex = Assert.Throws<ArgumentException>(() =>
            builder.AddZbTelemetry(o =>
            {
                o.ServiceName = "telemetry-test";
                o.Exporter = ZbExporter.Otlp;
                o.OtlpEndpoint = "not a uri";   // missing scheme — not an absolute URI
            }));
        Assert.Equal("configure", ex.ParamName);
        Assert.Contains("OtlpEndpoint", ex.Message);
    }

    [Fact]
    public void AddZbTelemetry_Throws_WhenOtlpExporterHasNoEndpoint()
    {
        var builder = WebApplication.CreateBuilder();
        var ex = Assert.Throws<ArgumentException>(() =>
            builder.AddZbTelemetry(o =>
            {
                o.ServiceName = "telemetry-test";
                o.Exporter = ZbExporter.Otlp;
                // OtlpEndpoint left null
            }));
        Assert.Equal("configure", ex.ParamName);
        Assert.Contains("OtlpEndpoint", ex.Message);
    }

    [Fact]
    public void AddZbTelemetry_DoesNotValidateEndpoint_WhenExporterIsPrometheus()
    {
        // A stray (even malformed) endpoint is harmless under the Prometheus exporter and must not
        // be validated — it is ignored.
        var builder = WebApplication.CreateBuilder();
        var ex = Record.Exception(() =>
            builder.AddZbTelemetry(o =>
            {
                o.ServiceName = "telemetry-test";
                o.Exporter = ZbExporter.Prometheus;
                o.OtlpEndpoint = "not a uri";
            }));
        Assert.Null(ex);
    }

    // Fix #1: Prometheus coexists with OTLP — /metrics must still serve under Otlp exporter

    [Fact]
    public async Task AddZbTelemetry_OtlpExporter_StillServesPrometheusEndpoint()
    {
        var builder = WebApplication.CreateBuilder();
        builder.WebHost.UseUrls("http://127.0.0.1:0");
        builder.AddZbTelemetry(o =>
        {
            o.ServiceName = "telemetry-test";
            o.Exporter = ZbExporter.Otlp;
            // A well-formed endpoint is required under the Otlp exporter (Telemetry-006); the
            // exporter is registered but won't connect anywhere in the test. We are only verifying
            // Prometheus remains present.
            o.OtlpEndpoint = "http://localhost:4317";
            o.Meters = ["Test.OtlpCoexist.Meter"];
        });

        await using var app = builder.Build();
        app.MapZbMetrics();

        await app.StartAsync();

        var address = app.Urls.First();
        using var client = new HttpClient { BaseAddress = new Uri(address) };

        var response = await client.GetAsync("/metrics");

        Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("text/plain", response.Content.Headers.ContentType?.MediaType);

        await app.StopAsync();
    }

    // Existing test ---------------------------------------------------------------

    [Fact]
    public void AddZbTelemetry_ExportsAppMeter_WithSharedResource()
    {
        // 1.15.x note: AddInMemoryExporter moved out of the core OpenTelemetry assembly into a
        // separate OpenTelemetry.Exporter.InMemory package (not referenced here). We attach a
        // BaseExporter<Metric> directly instead — it both collects metric names and exposes the
        // MeterProvider Resource via ParentProvider.GetResource().
        var capture = new CapturingMetricExporter();

        var builder = WebApplication.CreateBuilder();
        builder.AddZbTelemetry(o =>
        {
            o.ServiceName = "t";
            o.SiteId = "site-test";
            o.NodeRole = "central";
            o.Meters = ["Test.Meter"];
        });

        // Compose a capturing reader onto the pipeline AddZbTelemetry already registered.
        builder.Services.ConfigureOpenTelemetryMeterProvider(b =>
            b.AddReader(new PeriodicExportingMetricReader(capture)
            {
                TemporalityPreference = MetricReaderTemporalityPreference.Cumulative,
            }));

        // Create the meter + instrument BEFORE the provider is built so the MeterProvider's
        // listener subscribes to it during construction.
        using var meter = new Meter("Test.Meter");
        var counter = meter.CreateCounter<long>("test.events.count");

        using var app = builder.Build();

        var meterProvider = app.Services.GetRequiredService<MeterProvider>();
        counter.Add(1);
        meterProvider.ForceFlush();

        // The app's meter was registered and its instrument was collected through the pipeline.
        Assert.Contains("test.events.count", capture.MetricNames);

        // The exported metric carries the shared Resource (identical to ZbResource.Build).
        Assert.NotNull(capture.CapturedResource);
        var attrs = capture.CapturedResource!.Attributes.ToDictionary(a => a.Key, a => a.Value);
        Assert.Equal("t", attrs["service.name"]);
        Assert.Equal("ZB.MOM.WW", attrs["service.namespace"]);
        Assert.Equal("site-test", attrs["site.id"]);
        Assert.Equal("central", attrs["node.role"]);
        Assert.Equal(Environment.MachineName, attrs["host.name"]);
    }

    /// <summary>
    /// Collects exported metric names and captures the MeterProvider Resource on first export so
    /// the test can assert the pipeline wired both the app meter and the shared Resource.
    /// </summary>
    private sealed class CapturingMetricExporter : BaseExporter<Metric>
    {
        public List<string> MetricNames { get; } = [];
        public Resource? CapturedResource { get; private set; }

        public override ExportResult Export(in Batch<Metric> batch)
        {
            CapturedResource ??= ParentProvider?.GetResource();
            foreach (var metric in batch)
            {
                MetricNames.Add(metric.Name);
            }

            return ExportResult.Success;
        }
    }
}
