using System.Collections.Immutable;
using System.Runtime.Serialization;

namespace StellaOps.Vexer.Core;

/// <summary>
/// Metadata describing a VEX provider (vendor, distro, hub, platform).
/// </summary>
public sealed record VexProvider
{
    public VexProvider(
        string id,
        string displayName,
        VexProviderKind kind,
        IEnumerable<Uri>? baseUris = null,
        VexProviderDiscovery? discovery = null,
        VexProviderTrust? trust = null,
        bool enabled = true)
    {
        if (string.IsNullOrWhiteSpace(id))
        {
            throw new ArgumentException("Provider id must be non-empty.", nameof(id));
        }

        if (string.IsNullOrWhiteSpace(displayName))
        {
            throw new ArgumentException("Provider display name must be non-empty.", nameof(displayName));
        }

        Id = id.Trim();
        DisplayName = displayName.Trim();
        Kind = kind;
        BaseUris = NormalizeUris(baseUris);
        Discovery = discovery ?? VexProviderDiscovery.Empty;
        Trust = trust ?? VexProviderTrust.Default;
        Enabled = enabled;
    }

    public string Id { get; }

    public string DisplayName { get; }

    public VexProviderKind Kind { get; }

    public ImmutableArray<Uri> BaseUris { get; }

    public VexProviderDiscovery Discovery { get; }

    public VexProviderTrust Trust { get; }

    public bool Enabled { get; }

    private static ImmutableArray<Uri> NormalizeUris(IEnumerable<Uri>? baseUris)
    {
        if (baseUris is null)
        {
            return ImmutableArray<Uri>.Empty;
        }

        var distinct = new HashSet<string>(StringComparer.Ordinal);
        var builder = ImmutableArray.CreateBuilder<Uri>();
        foreach (var uri in baseUris)
        {
            if (uri is null)
            {
                continue;
            }

            var canonical = uri.ToString();
            if (distinct.Add(canonical))
            {
                builder.Add(uri);
            }
        }

        if (builder.Count == 0)
        {
            return ImmutableArray<Uri>.Empty;
        }

        return builder
            .OrderBy(static x => x.ToString(), StringComparer.Ordinal)
            .ToImmutableArray();
    }
}

public sealed record VexProviderDiscovery
{
    public static readonly VexProviderDiscovery Empty = new(null, null);

    public VexProviderDiscovery(Uri? wellKnownMetadata, Uri? rolieService)
    {
        WellKnownMetadata = wellKnownMetadata;
        RolIeService = rolieService;
    }

    public Uri? WellKnownMetadata { get; }

    public Uri? RolIeService { get; }
}

public sealed record VexProviderTrust
{
    public static readonly VexProviderTrust Default = new(1.0, null, ImmutableArray<string>.Empty);

    public VexProviderTrust(
        double weight,
        VexCosignTrust? cosign,
        IEnumerable<string>? pgpFingerprints = null)
    {
        Weight = NormalizeWeight(weight);
        Cosign = cosign;
        PgpFingerprints = NormalizeFingerprints(pgpFingerprints);
    }

    public double Weight { get; }

    public VexCosignTrust? Cosign { get; }

    public ImmutableArray<string> PgpFingerprints { get; }

    private static double NormalizeWeight(double weight)
    {
        if (double.IsNaN(weight) || double.IsInfinity(weight))
        {
            throw new ArgumentOutOfRangeException(nameof(weight), "Weight must be a finite number.");
        }

        if (weight <= 0)
        {
            return 0.0;
        }

        if (weight >= 1.0)
        {
            return 1.0;
        }

        return weight;
    }

    private static ImmutableArray<string> NormalizeFingerprints(IEnumerable<string>? values)
    {
        if (values is null)
        {
            return ImmutableArray<string>.Empty;
        }

        var set = new SortedSet<string>(StringComparer.Ordinal);
        foreach (var value in values)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                continue;
            }

            set.Add(value.Trim());
        }

        return set.Count == 0
            ? ImmutableArray<string>.Empty
            : set.ToImmutableArray();
    }
}

public sealed record VexCosignTrust
{
    public VexCosignTrust(string issuer, string identityPattern)
    {
        if (string.IsNullOrWhiteSpace(issuer))
        {
            throw new ArgumentException("Issuer must be provided for cosign trust metadata.", nameof(issuer));
        }

        if (string.IsNullOrWhiteSpace(identityPattern))
        {
            throw new ArgumentException("Identity pattern must be provided for cosign trust metadata.", nameof(identityPattern));
        }

        Issuer = issuer.Trim();
        IdentityPattern = identityPattern.Trim();
    }

    public string Issuer { get; }

    public string IdentityPattern { get; }
}

[DataContract]
public enum VexProviderKind
{
    [EnumMember(Value = "vendor")]
    Vendor,

    [EnumMember(Value = "distro")]
    Distro,

    [EnumMember(Value = "hub")]
    Hub,

    [EnumMember(Value = "platform")]
    Platform,

    [EnumMember(Value = "attestation")]
    Attestation,
}
