using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Vexer.Core;
using StellaOps.Vexer.Policy;
using System.Diagnostics.Metrics;

namespace StellaOps.Vexer.Core.Tests;

public class VexPolicyDiagnosticsTests
{
    [Fact]
    public void GetDiagnostics_ReportsCountsRecommendationsAndOverrides()
    {
        var overrides = new[]
        {
            new KeyValuePair<string, double>("provider-a", 0.8),
            new KeyValuePair<string, double>("provider-b", 0.6),
        };

        var snapshot = new VexPolicySnapshot(
            "custom/v1",
            new VexConsensusPolicyOptions(
                version: "custom/v1",
                providerOverrides: overrides),
            new BaselineVexConsensusPolicy(),
            ImmutableArray.Create(
                new VexPolicyIssue("sample.error", "Blocking issue.", VexPolicyIssueSeverity.Error),
                new VexPolicyIssue("sample.warning", "Non-blocking issue.", VexPolicyIssueSeverity.Warning)),
            "rev-test",
            "ABCDEF");

        var fakeProvider = new FakePolicyProvider(snapshot);
        var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 16, 17, 0, 0, TimeSpan.Zero));
        var diagnostics = new VexPolicyDiagnostics(fakeProvider, fakeTime);

        var report = diagnostics.GetDiagnostics();

        Assert.Equal("custom/v1", report.Version);
        Assert.Equal("rev-test", report.RevisionId);
        Assert.Equal("ABCDEF", report.Digest);
        Assert.Equal(1, report.ErrorCount);
        Assert.Equal(1, report.WarningCount);
        Assert.Equal(fakeTime.GetUtcNow(), report.GeneratedAt);
        Assert.Collection(report.Issues,
            issue => Assert.Equal("sample.error", issue.Code),
            issue => Assert.Equal("sample.warning", issue.Code));
        Assert.Equal(new[] { "provider-a", "provider-b" }, report.ActiveOverrides.Keys.OrderBy(static key => key, StringComparer.Ordinal));
        Assert.Contains(report.Recommendations, message => message.Contains("Resolve policy errors", StringComparison.OrdinalIgnoreCase));
        Assert.Contains(report.Recommendations, message => message.Contains("provider-a", StringComparison.OrdinalIgnoreCase));
        Assert.Contains(report.Recommendations, message => message.Contains("docs/ARCHITECTURE_VEXER.md", StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public void GetDiagnostics_WhenNoIssues_StillReturnsDefaultRecommendation()
    {
        var fakeProvider = new FakePolicyProvider(VexPolicySnapshot.Default);
        var fakeTime = new FakeTimeProvider(new DateTimeOffset(2025, 10, 16, 17, 0, 0, TimeSpan.Zero));
        var diagnostics = new VexPolicyDiagnostics(fakeProvider, fakeTime);

        var report = diagnostics.GetDiagnostics();

        Assert.Equal(0, report.ErrorCount);
        Assert.Equal(0, report.WarningCount);
        Assert.Empty(report.ActiveOverrides);
        Assert.Single(report.Recommendations);
    }

    [Fact]
    public void PolicyProvider_ComputesRevisionAndDigest_AndEmitsTelemetry()
    {
        using var listener = new MeterListener();
        var reloadMeasurements = 0;
        string? lastRevision = null;
        listener.InstrumentPublished += (instrument, _) =>
        {
            if (instrument.Meter.Name == "StellaOps.Vexer.Policy" &&
                instrument.Name == "vex.policy.reloads")
            {
                listener.EnableMeasurementEvents(instrument);
            }
        };

        listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
        {
            reloadMeasurements++;
            foreach (var tag in tags)
            {
                if (tag.Key is "revision" && tag.Value is string revision)
                {
                    lastRevision = revision;
                    break;
                }
            }
        });

        listener.Start();

        var optionsMonitor = new MutableOptionsMonitor<VexPolicyOptions>(new VexPolicyOptions());
        var provider = new VexPolicyProvider(optionsMonitor, NullLogger<VexPolicyProvider>.Instance);

        var snapshot1 = provider.GetSnapshot();
        Assert.Equal("rev-1", snapshot1.RevisionId);
        Assert.False(string.IsNullOrWhiteSpace(snapshot1.Digest));

        var snapshot2 = provider.GetSnapshot();
        Assert.Equal("rev-1", snapshot2.RevisionId);
        Assert.Equal(snapshot1.Digest, snapshot2.Digest);

        optionsMonitor.Update(new VexPolicyOptions
        {
            ProviderOverrides = new Dictionary<string, double>
            {
                ["provider-a"] = 0.4
            }
        });

        var snapshot3 = provider.GetSnapshot();
        Assert.Equal("rev-2", snapshot3.RevisionId);
        Assert.NotEqual(snapshot1.Digest, snapshot3.Digest);

        listener.Dispose();

        Assert.True(reloadMeasurements >= 2);
        Assert.Equal("rev-2", lastRevision);
    }

    private sealed class FakePolicyProvider : IVexPolicyProvider
    {
        private readonly VexPolicySnapshot _snapshot;

        public FakePolicyProvider(VexPolicySnapshot snapshot)
        {
            _snapshot = snapshot;
        }

        public VexPolicySnapshot GetSnapshot() => _snapshot;
    }

    private sealed class MutableOptionsMonitor<T> : IOptionsMonitor<T>
    {
        private T _value;

        public MutableOptionsMonitor(T value)
        {
            _value = value;
        }

        public T CurrentValue => _value;

        public T Get(string? name) => _value;

        public void Update(T newValue) => _value = newValue;

        public IDisposable OnChange(Action<T, string?> listener) => NullDisposable.Instance;

        private sealed class NullDisposable : IDisposable
        {
            public static readonly NullDisposable Instance = new();
            public void Dispose()
            {
            }
        }
    }
}
