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)
{
    public static readonly VexPolicySnapshot Default = new(
        VexConsensusPolicyOptions.BaselineVersion,
        new VexConsensusPolicyOptions(),
        new BaselineVexConsensusPolicy(),
        ImmutableArray<VexPolicyIssue>.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;

    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 issues = ImmutableArray.CreateBuilder<VexPolicyIssue>();

        if (!TryNormalizeWeights(options.Weights, out var weightOptions, issues))
        {
            issues.Add(new VexPolicyIssue(
                "weights.invalid",
                "Weight configuration is invalid; falling back to defaults.",
                VexPolicyIssueSeverity.Warning));
            weightOptions = new VexConsensusPolicyOptions();
        }

        var overrides = NormalizeOverrides(options.ProviderOverrides, issues);

        var consensusOptions = new VexConsensusPolicyOptions(
            options.Version ?? VexConsensusPolicyOptions.BaselineVersion,
            weightOptions.VendorWeight,
            weightOptions.DistroWeight,
            weightOptions.PlatformWeight,
            weightOptions.HubWeight,
            weightOptions.AttestationWeight,
            overrides);

        var policy = new BaselineVexConsensusPolicy(consensusOptions);
        var snapshot = new VexPolicySnapshot(
            consensusOptions.Version,
            consensusOptions,
            policy,
            issues.ToImmutable());

        if (snapshot.Issues.Length > 0)
        {
            foreach (var issue in snapshot.Issues)
            {
                _logger.LogWarning("Policy issue {Code}: {Message}", issue.Code, issue.Message);
            }
        }

        return snapshot;
    }

    private static bool TryNormalizeWeights(
        VexPolicyWeightOptions options,
        out VexConsensusPolicyOptions normalized,
        ImmutableArray<VexPolicyIssue>.Builder issues)
    {
        var hasAny = options is not null &&
                     (options.Vendor.HasValue || options.Distro.HasValue ||
                      options.Platform.HasValue || options.Hub.HasValue || options.Attestation.HasValue);

        if (!hasAny)
        {
            normalized = new VexConsensusPolicyOptions();
            return true;
        }

        var vendor = Clamp(options.Vendor, nameof(options.Vendor), issues);
        var distro = Clamp(options.Distro, nameof(options.Distro), issues);
        var platform = Clamp(options.Platform, nameof(options.Platform), issues);
        var hub = Clamp(options.Hub, nameof(options.Hub), issues);
        var attestation = Clamp(options.Attestation, nameof(options.Attestation), issues);

        normalized = new VexConsensusPolicyOptions(
            VexConsensusPolicyOptions.BaselineVersion,
            vendor ?? 1.0,
            distro ?? 0.9,
            platform ?? 0.7,
            hub ?? 0.5,
            attestation ?? 0.6);
        return true;
    }

    private static double? Clamp(double? value, string fieldName, ImmutableArray<VexPolicyIssue>.Builder issues)
    {
        if (value is null)
        {
            return null;
        }

        if (double.IsNaN(value.Value) || double.IsInfinity(value.Value))
        {
            issues.Add(new VexPolicyIssue(
                $"weights.{fieldName}.invalid",
                $"{fieldName} must be a finite number.",
                VexPolicyIssueSeverity.Warning));
            return null;
        }

        if (value.Value < 0 || value.Value > 1)
        {
            issues.Add(new VexPolicyIssue(
                $"weights.{fieldName}.range",
                $"{fieldName} must be between 0 and 1; value {value.Value.ToString(CultureInfo.InvariantCulture)} was clamped.",
                VexPolicyIssueSeverity.Warning));
            return Math.Clamp(value.Value, 0, 1);
        }

        return value.Value;
    }

    private static ImmutableDictionary<string, double> NormalizeOverrides(
        IDictionary<string, double>? overrides,
        ImmutableArray<VexPolicyIssue>.Builder issues)
    {
        if (overrides is null || overrides.Count == 0)
        {
            return ImmutableDictionary<string, double>.Empty;
        }

        var builder = ImmutableDictionary.CreateBuilder<string, double>(StringComparer.Ordinal);
        foreach (var kvp in overrides)
        {
            if (string.IsNullOrWhiteSpace(kvp.Key))
            {
                issues.Add(new VexPolicyIssue(
                    "overrides.key.missing",
                    "Encountered provider override with empty key; ignoring entry.",
                    VexPolicyIssueSeverity.Warning));
                continue;
            }

            var weight = Clamp(kvp.Value, $"overrides.{kvp.Key}", issues) ?? kvp.Value;
            builder[kvp.Key.Trim()] = weight;
        }

        return builder.ToImmutable();
    }
}

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;
    }
}
