using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Policy;

public interface IVexPolicyProvider
{
    VexPolicySnapshot GetSnapshot();
}

public interface IVexPolicyEvaluator
{
    string Version { get; }

    VexPolicySnapshot Snapshot { get; }

    double GetProviderWeight(VexProvider provider);

    bool IsClaimEligible(VexClaim claim, VexProvider provider, out string? rejectionReason);
}

public sealed record VexPolicySnapshot(
    string Version,
    VexConsensusPolicyOptions ConsensusOptions,
    IVexConsensusPolicy ConsensusPolicy,
    ImmutableArray<VexPolicyIssue> Issues,
    string RevisionId,
    string Digest)
{
    public static readonly VexPolicySnapshot Default = new(
        VexConsensusPolicyOptions.BaselineVersion,
        new VexConsensusPolicyOptions(),
        new BaselineVexConsensusPolicy(),
        ImmutableArray<VexPolicyIssue>.Empty,
        "rev-0",
        string.Empty);
}

public sealed record VexPolicyIssue(
    string Code,
    string Message,
    VexPolicyIssueSeverity Severity);

public enum VexPolicyIssueSeverity
{
    Warning,
    Error,
}

public sealed class VexPolicyProvider : IVexPolicyProvider
{
    private readonly IOptionsMonitor<VexPolicyOptions> _options;
    private readonly ILogger<VexPolicyProvider> _logger;
    private readonly object _sync = new();
    private long _revisionCounter;
    private string? _currentRevisionId;
    private string? _currentDigest;

    public VexPolicyProvider(
        IOptionsMonitor<VexPolicyOptions> options,
        ILogger<VexPolicyProvider> logger)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public VexPolicySnapshot GetSnapshot()
    {
        var options = _options.CurrentValue ?? new VexPolicyOptions();
        return BuildSnapshot(options);
    }

    private VexPolicySnapshot BuildSnapshot(VexPolicyOptions options)
    {
        var normalization = VexPolicyProcessing.Normalize(options);
        var digest = VexPolicyDigest.Compute(normalization.ConsensusOptions);
        string revisionId;
        bool isNewRevision;

        lock (_sync)
        {
            if (!string.Equals(_currentDigest, digest, StringComparison.Ordinal))
            {
                _revisionCounter++;
                revisionId = $"rev-{_revisionCounter}";
                _currentDigest = digest;
                _currentRevisionId = revisionId;
                isNewRevision = true;
            }
            else
            {
                revisionId = _currentRevisionId ?? "rev-0";
                isNewRevision = false;
            }
        }

        var policy = new BaselineVexConsensusPolicy(normalization.ConsensusOptions);
        var snapshot = new VexPolicySnapshot(
            normalization.ConsensusOptions.Version,
            normalization.ConsensusOptions,
            policy,
            normalization.Issues,
            revisionId,
            digest);

        if (isNewRevision)
        {
            _logger.LogInformation(
                "Policy snapshot updated: revision {RevisionId}, version {Version}, digest {Digest}, issues {IssueCount}",
                snapshot.RevisionId,
                snapshot.Version,
                snapshot.Digest,
                snapshot.Issues.Length);
            VexPolicyTelemetry.RecordReload(snapshot.RevisionId, snapshot.Version, snapshot.Issues.Length);
        }
        else if (snapshot.Issues.Length > 0)
        {
            foreach (var issue in snapshot.Issues)
            {
                _logger.LogWarning("Policy issue {Code}: {Message}", issue.Code, issue.Message);
            }
        }

        return snapshot;
    }
}

public sealed class VexPolicyEvaluator : IVexPolicyEvaluator
{
    private readonly IVexPolicyProvider _provider;

    public VexPolicyEvaluator(IVexPolicyProvider provider)
    {
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
    }

    public string Version => Snapshot.Version;

    public VexPolicySnapshot Snapshot => _provider.GetSnapshot();

    public double GetProviderWeight(VexProvider provider)
        => Snapshot.ConsensusPolicy.GetProviderWeight(provider);

    public bool IsClaimEligible(VexClaim claim, VexProvider provider, out string? rejectionReason)
        => Snapshot.ConsensusPolicy.IsClaimEligible(claim, provider, out rejectionReason);
}

public static class VexPolicyServiceCollectionExtensions
{
    public static IServiceCollection AddVexPolicy(this IServiceCollection services)
    {
        services.AddSingleton<IVexPolicyProvider, VexPolicyProvider>();
        services.AddSingleton<IVexPolicyEvaluator, VexPolicyEvaluator>();
        return services;
    }
}
