using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;

namespace StellaOps.Feedser.Models;

/// <summary>
/// Canonical acknowledgement/credit metadata associated with an advisory.
/// </summary>
public sealed record AdvisoryCredit
{
    public static AdvisoryCredit Empty { get; } = new("unknown", role: null, contacts: Array.Empty<string>(), AdvisoryProvenance.Empty);

    [JsonConstructor]
    public AdvisoryCredit(string displayName, string? role, ImmutableArray<string> contacts, AdvisoryProvenance provenance)
        : this(displayName, role, contacts.IsDefault ? null : contacts.AsEnumerable(), provenance)
    {
    }

    public AdvisoryCredit(string displayName, string? role, IEnumerable<string>? contacts, AdvisoryProvenance provenance)
    {
        DisplayName = Validation.EnsureNotNullOrWhiteSpace(displayName, nameof(displayName));
        Role = NormalizeRole(role);
        Contacts = NormalizeContacts(contacts);
        Provenance = provenance ?? AdvisoryProvenance.Empty;
    }

    public string DisplayName { get; }

    public string? Role { get; }

    public ImmutableArray<string> Contacts { get; }

    public AdvisoryProvenance Provenance { get; }

    private static string? NormalizeRole(string? role)
    {
        if (string.IsNullOrWhiteSpace(role))
        {
            return null;
        }

        var span = role.AsSpan();
        var buffer = new StringBuilder(span.Length);

        foreach (var ch in span)
        {
            if (char.IsLetterOrDigit(ch))
            {
                buffer.Append(char.ToLowerInvariant(ch));
                continue;
            }

            if (ch is '-' or '_' or ' ')
            {
                if (buffer.Length > 0 && buffer[^1] != '_')
                {
                    buffer.Append('_');
                }

                continue;
            }
        }

        while (buffer.Length > 0 && buffer[^1] == '_')
        {
            buffer.Length--;
        }

        return buffer.Length == 0 ? null : buffer.ToString();
    }

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

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

            var trimmed = contact.Trim();
            if (trimmed.Length == 0)
            {
                continue;
            }

            set.Add(trimmed);
        }

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