using System.Collections.Immutable;
using System.Globalization;
using StellaOps.Vexer.Core;

namespace StellaOps.Vexer.Policy;

internal static class VexPolicyProcessing
{
    public static VexPolicyNormalizationResult Normalize(VexPolicyOptions? options)
    {
        var issues = ImmutableArray.CreateBuilder<VexPolicyIssue>();

        var policyOptions = options ?? new VexPolicyOptions();

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

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

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

        var orderedIssues = issues.ToImmutable().Sort(IssueComparer);

        return new VexPolicyNormalizationResult(consensusOptions, orderedIssues);
    }

    public static ImmutableArray<VexPolicyIssue> SortIssues(IEnumerable<VexPolicyIssue> issues)
        => issues.ToImmutableArray().Sort(IssueComparer);

    private static bool TryNormalizeWeights(
        VexPolicyWeightOptions? options,
        out VexConsensusPolicyOptions normalized,
        ImmutableArray<VexPolicyIssue>.Builder issues)
    {
        if (options is null)
        {
            normalized = new VexConsensusPolicyOptions();
            return true;
        }

        var hasAny =
            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();
    }

    private static int CompareIssues(VexPolicyIssue left, VexPolicyIssue right)
    {
        var severityCompare = GetSeverityRank(left.Severity).CompareTo(GetSeverityRank(right.Severity));
        if (severityCompare != 0)
        {
            return severityCompare;
        }

        return string.Compare(left.Code, right.Code, StringComparison.Ordinal);
    }

    private static int GetSeverityRank(VexPolicyIssueSeverity severity)
        => severity switch
        {
            VexPolicyIssueSeverity.Error => 0,
            VexPolicyIssueSeverity.Warning => 1,
            _ => 2,
        };

    private static readonly Comparer<VexPolicyIssue> IssueComparer = Comparer<VexPolicyIssue>.Create(CompareIssues);

    internal sealed record VexPolicyNormalizationResult(
        VexConsensusPolicyOptions ConsensusOptions,
        ImmutableArray<VexPolicyIssue> Issues);
}
