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

namespace StellaOps.Feedser.Models;

/// <summary>
/// Canonical affected package descriptor with deterministic ordering of ranges and provenance.
/// </summary>
public sealed record AffectedPackage
{
    public static AffectedPackage Empty { get; } = new(
        AffectedPackageTypes.SemVer,
        identifier: "unknown",
        platform: null,
        versionRanges: Array.Empty<AffectedVersionRange>(),
        statuses: Array.Empty<AffectedPackageStatus>(),
        provenance: Array.Empty<AdvisoryProvenance>(),
        normalizedVersions: Array.Empty<NormalizedVersionRule>());

    [JsonConstructor]
    public AffectedPackage(
        string type,
        string identifier,
        string? platform = null,
        IEnumerable<AffectedVersionRange>? versionRanges = null,
        IEnumerable<AffectedPackageStatus>? statuses = null,
        IEnumerable<AdvisoryProvenance>? provenance = null,
        IEnumerable<NormalizedVersionRule>? normalizedVersions = null)
    {
        Type = Validation.EnsureNotNullOrWhiteSpace(type, nameof(type)).ToLowerInvariant();
        Identifier = Validation.EnsureNotNullOrWhiteSpace(identifier, nameof(identifier));
        Platform = Validation.TrimToNull(platform);

        VersionRanges = (versionRanges ?? Array.Empty<AffectedVersionRange>())
            .Distinct(AffectedVersionRangeEqualityComparer.Instance)
            .OrderBy(static range => range, AffectedVersionRangeComparer.Instance)
            .ToImmutableArray();

        Statuses = (statuses ?? Array.Empty<AffectedPackageStatus>())
            .Where(static status => status is not null)
            .Distinct(AffectedPackageStatusEqualityComparer.Instance)
            .OrderBy(static status => status.Status, StringComparer.Ordinal)
            .ThenBy(static status => status.Provenance.Source, StringComparer.Ordinal)
            .ThenBy(static status => status.Provenance.Kind, StringComparer.Ordinal)
            .ThenBy(static status => status.Provenance.RecordedAt)
            .ToImmutableArray();

        NormalizedVersions = (normalizedVersions ?? Array.Empty<NormalizedVersionRule>())
            .Where(static rule => rule is not null)
            .Distinct(NormalizedVersionRuleEqualityComparer.Instance)
            .OrderBy(static rule => rule, NormalizedVersionRuleComparer.Instance)
            .ToImmutableArray();

        Provenance = (provenance ?? Array.Empty<AdvisoryProvenance>())
            .Where(static p => p is not null)
            .OrderBy(static p => p.Source, StringComparer.Ordinal)
            .ThenBy(static p => p.Kind, StringComparer.Ordinal)
            .ThenBy(static p => p.RecordedAt)
            .ToImmutableArray();
    }

    /// <summary>
    /// Semantic type of the coordinates (rpm, deb, cpe, semver, vendor, ics-vendor).
    /// </summary>
    public string Type { get; }

    /// <summary>
    /// Canonical identifier for the package (NEVRA, PackageURL, CPE string, vendor slug, etc.).
    /// </summary>
    public string Identifier { get; }

    public string? Platform { get; }

    public ImmutableArray<AffectedVersionRange> VersionRanges { get; }

    public ImmutableArray<AffectedPackageStatus> Statuses { get; }

    public ImmutableArray<NormalizedVersionRule> NormalizedVersions { get; }

    public ImmutableArray<AdvisoryProvenance> Provenance { get; }
}

/// <summary>
/// Known values for <see cref="AffectedPackage.Type"/>.
/// </summary>
public static class AffectedPackageTypes
{
    public const string Rpm = "rpm";
    public const string Deb = "deb";
    public const string Cpe = "cpe";
    public const string SemVer = "semver";
    public const string Vendor = "vendor";
    public const string IcsVendor = "ics-vendor";
}
