using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Feedser.Merge.Options;
using StellaOps.Feedser.Models;

namespace StellaOps.Feedser.Merge.Services;

/// <summary>
/// Applies source precedence rules to affected package sets so authoritative distro ranges override generic registry data.
/// </summary>
public sealed class AffectedPackagePrecedenceResolver
{
    private readonly IReadOnlyDictionary<string, int> _precedence;
    private readonly int _fallbackRank;

    public AffectedPackagePrecedenceResolver()
        : this(AdvisoryPrecedenceDefaults.Rankings)
    {
    }

    public AffectedPackagePrecedenceResolver(AdvisoryPrecedenceOptions? options)
        : this(AdvisoryPrecedenceTable.Merge(AdvisoryPrecedenceDefaults.Rankings, options))
    {
    }

    public AffectedPackagePrecedenceResolver(IReadOnlyDictionary<string, int> precedence)
    {
        _precedence = precedence ?? throw new ArgumentNullException(nameof(precedence));
        _fallbackRank = precedence.Count == 0 ? 10 : precedence.Values.Max() + 1;
    }

    public IReadOnlyDictionary<string, int> Precedence => _precedence;

    public AffectedPackagePrecedenceResult Merge(IEnumerable<AffectedPackage> packages)
    {
        ArgumentNullException.ThrowIfNull(packages);

        var grouped = packages
            .Where(static pkg => pkg is not null)
            .GroupBy(pkg => (pkg.Type, pkg.Identifier, pkg.Platform ?? string.Empty));

        var resolved = new List<AffectedPackage>();
        var overrides = new List<AffectedPackageOverride>();

        foreach (var group in grouped)
        {
            var ordered = group
                .Select(pkg => new PackageEntry(pkg, GetPrecedence(pkg)))
                .OrderBy(static entry => entry.Rank)
                .ThenByDescending(static entry => entry.Package.Provenance.Length)
                .ThenByDescending(static entry => entry.Package.VersionRanges.Length)
                .ToList();

            var primary = ordered[0];
            var provenance = ordered
                .SelectMany(static entry => entry.Package.Provenance)
                .Where(static p => p is not null)
                .Distinct()
                .ToImmutableArray();

            var statuses = ordered
                .SelectMany(static entry => entry.Package.Statuses)
                .Distinct(AffectedPackageStatusEqualityComparer.Instance)
                .ToImmutableArray();

            var normalizedRules = ordered
                .SelectMany(static entry => entry.Package.NormalizedVersions)
                .Distinct(NormalizedVersionRuleEqualityComparer.Instance)
                .OrderBy(static rule => rule, NormalizedVersionRuleComparer.Instance)
                .ToImmutableArray();

            foreach (var candidate in ordered.Skip(1))
            {
                if (candidate.Package.VersionRanges.Length == 0)
                {
                    continue;
                }

                overrides.Add(new AffectedPackageOverride(
                    primary.Package.Type,
                    primary.Package.Identifier,
                    string.IsNullOrWhiteSpace(primary.Package.Platform) ? null : primary.Package.Platform,
                    primary.Rank,
                    candidate.Rank,
                    ExtractSources(primary.Package),
                    ExtractSources(candidate.Package),
                    primary.Package.VersionRanges.Length,
                    candidate.Package.VersionRanges.Length));
            }

            var merged = new AffectedPackage(
                primary.Type,
                primary.Identifier,
                string.IsNullOrWhiteSpace(primary.Platform) ? null : primary.Platform,
                primary.Package.VersionRanges,
                statuses,
                provenance,
                normalizedRules);

            resolved.Add(merged);
        }

        var packagesResult = resolved
            .OrderBy(static pkg => pkg.Type, StringComparer.Ordinal)
            .ThenBy(static pkg => pkg.Identifier, StringComparer.Ordinal)
            .ThenBy(static pkg => pkg.Platform, StringComparer.Ordinal)
            .ToImmutableArray();

        return new AffectedPackagePrecedenceResult(packagesResult, overrides.ToImmutableArray());
    }

    private int GetPrecedence(AffectedPackage package)
    {
        var bestRank = _fallbackRank;
        foreach (var provenance in package.Provenance)
        {
            if (provenance is null || string.IsNullOrWhiteSpace(provenance.Source))
            {
                continue;
            }

            if (_precedence.TryGetValue(provenance.Source, out var rank) && rank < bestRank)
            {
                bestRank = rank;
            }
        }

        return bestRank;
    }

    private static IReadOnlyList<string> ExtractSources(AffectedPackage package)
    {
        if (package.Provenance.Length == 0)
        {
            return Array.Empty<string>();
        }

        return package.Provenance
            .Select(static p => p.Source)
            .Where(static source => !string.IsNullOrWhiteSpace(source))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToImmutableArray();
    }

    private readonly record struct PackageEntry(AffectedPackage Package, int Rank)
    {
        public string Type => Package.Type;

        public string Identifier => Package.Identifier;

        public string? Platform => string.IsNullOrWhiteSpace(Package.Platform) ? null : Package.Platform;
    }
}

public sealed record AffectedPackagePrecedenceResult(
    IReadOnlyList<AffectedPackage> Packages,
    IReadOnlyList<AffectedPackageOverride> Overrides);

public sealed record AffectedPackageOverride(
    string Type,
    string Identifier,
    string? Platform,
    int PrimaryRank,
    int SuppressedRank,
    IReadOnlyList<string> PrimarySources,
    IReadOnlyList<string> SuppressedSources,
    int PrimaryRangeCount,
    int SuppressedRangeCount);
