using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Feedser.Models;

namespace StellaOps.Feedser.Source.CertFr.Internal;

internal static class CertFrMapper
{
    public static Advisory Map(CertFrDto dto, string sourceName, DateTimeOffset recordedAt)
    {
        ArgumentNullException.ThrowIfNull(dto);
        ArgumentException.ThrowIfNullOrEmpty(sourceName);

        var advisoryKey = $"cert-fr/{dto.AdvisoryId}";
        var provenance = new AdvisoryProvenance(sourceName, "document", dto.DetailUrl, recordedAt.ToUniversalTime());

        var aliases = new List<string>
        {
            $"CERT-FR:{dto.AdvisoryId}",
        };

        var references = BuildReferences(dto, provenance).ToArray();

        var affectedPackages = BuildAffectedPackages(dto, provenance).ToArray();

        return new Advisory(
            advisoryKey,
            dto.Title,
            dto.Summary ?? dto.Title,
            language: "fr",
            published: dto.Published.ToUniversalTime(),
            modified: null,
            severity: null,
            exploitKnown: false,
            aliases: aliases,
            references: references,
            affectedPackages: affectedPackages,
            cvssMetrics: Array.Empty<CvssMetric>(),
            provenance: new[] { provenance });
    }

    private static IEnumerable<AdvisoryReference> BuildReferences(CertFrDto dto, AdvisoryProvenance provenance)
    {
        var comparer = StringComparer.OrdinalIgnoreCase;
        var entries = new List<(AdvisoryReference Reference, int Priority)>
        {
            (new AdvisoryReference(dto.DetailUrl, "advisory", "cert-fr", dto.Summary, provenance), 0),
        };

        foreach (var url in dto.References)
        {
            entries.Add((new AdvisoryReference(url, "reference", null, null, provenance), 1));
        }

        return entries
            .GroupBy(tuple => tuple.Reference.Url, comparer)
            .Select(group => group
                .OrderBy(t => t.Priority)
                .ThenBy(t => t.Reference.Kind ?? string.Empty, comparer)
                .ThenBy(t => t.Reference.Url, comparer)
                .First())
            .OrderBy(t => t.Priority)
            .ThenBy(t => t.Reference.Kind ?? string.Empty, comparer)
            .ThenBy(t => t.Reference.Url, comparer)
            .Select(t => t.Reference);
    }

    private static IEnumerable<AffectedPackage> BuildAffectedPackages(CertFrDto dto, AdvisoryProvenance provenance)
    {
        var extensions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        if (!string.IsNullOrWhiteSpace(dto.Summary))
        {
            extensions["certfr.summary"] = dto.Summary.Trim();
        }

        if (!string.IsNullOrWhiteSpace(dto.Content))
        {
            var trimmed = dto.Content.Length > 1024 ? dto.Content[..1024].Trim() : dto.Content.Trim();
            if (trimmed.Length > 0)
            {
                extensions["certfr.content"] = trimmed;
            }
        }

        if (dto.References.Count > 0)
        {
            extensions["certfr.reference.count"] = dto.References.Count.ToString();
        }

        if (extensions.Count == 0)
        {
            return Array.Empty<AffectedPackage>();
        }

        var range = new AffectedVersionRange(
            rangeKind: "vendor",
            introducedVersion: null,
            fixedVersion: null,
            lastAffectedVersion: null,
            rangeExpression: null,
            provenance: provenance,
            primitives: new RangePrimitives(null, null, null, extensions));

        return new[]
        {
            new AffectedPackage(
                AffectedPackageTypes.Vendor,
                identifier: dto.AdvisoryId,
                platform: null,
                versionRanges: new[] { range },
                statuses: Array.Empty<AffectedPackageStatus>(),
                provenance: new[] { provenance })
        };
    }
}
