using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Storage.Mongo.Documents;

namespace StellaOps.Feedser.Source.Cccs.Internal;

internal static class CccsMapper
{
    public static Advisory Map(CccsAdvisoryDto dto, DocumentRecord document, DateTimeOffset recordedAt)
    {
        ArgumentNullException.ThrowIfNull(dto);
        ArgumentNullException.ThrowIfNull(document);

        var aliases = BuildAliases(dto);
        var references = BuildReferences(dto, recordedAt);
        var packages = BuildPackages(dto, recordedAt);
        var provenance = new[]
        {
            new AdvisoryProvenance(
                CccsConnectorPlugin.SourceName,
                "advisory",
                dto.AlertType ?? dto.SerialNumber,
                recordedAt,
                new[] { ProvenanceFieldMasks.Advisory })
        };

        return new Advisory(
            advisoryKey: dto.SerialNumber,
            title: dto.Title,
            summary: dto.Summary,
            language: dto.Language,
            published: dto.Published ?? dto.Modified,
            modified: dto.Modified ?? dto.Published,
            severity: null,
            exploitKnown: false,
            aliases: aliases,
            references: references,
            affectedPackages: packages,
            cvssMetrics: Array.Empty<CvssMetric>(),
            provenance: provenance);
    }

    private static IReadOnlyList<string> BuildAliases(CccsAdvisoryDto dto)
    {
        var aliases = new List<string>(capacity: 4)
        {
            dto.SerialNumber,
        };

        if (!string.IsNullOrWhiteSpace(dto.SourceId)
            && !string.Equals(dto.SourceId, dto.SerialNumber, StringComparison.OrdinalIgnoreCase))
        {
            aliases.Add(dto.SourceId);
        }

        foreach (var cve in dto.CveIds)
        {
            if (!string.IsNullOrWhiteSpace(cve))
            {
                aliases.Add(cve);
            }
        }

        return aliases
            .Where(static alias => !string.IsNullOrWhiteSpace(alias))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(static alias => alias, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private static IReadOnlyList<AdvisoryReference> BuildReferences(CccsAdvisoryDto dto, DateTimeOffset recordedAt)
    {
        var references = new List<AdvisoryReference>
        {
            new(dto.CanonicalUrl, "details", "cccs", null, new AdvisoryProvenance(
                CccsConnectorPlugin.SourceName,
                "reference",
                dto.CanonicalUrl,
                recordedAt,
                new[] { ProvenanceFieldMasks.References }))
        };

        foreach (var reference in dto.References)
        {
            if (string.IsNullOrWhiteSpace(reference.Url))
            {
                continue;
            }

            references.Add(new AdvisoryReference(
                reference.Url,
                "reference",
                "cccs",
                reference.Label,
                new AdvisoryProvenance(
                    CccsConnectorPlugin.SourceName,
                    "reference",
                    reference.Url,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.References })));
        }

        return references
            .DistinctBy(static reference => reference.Url, StringComparer.Ordinal)
            .OrderBy(static reference => reference.Url, StringComparer.Ordinal)
            .ToArray();
    }

    private static IReadOnlyList<AffectedPackage> BuildPackages(CccsAdvisoryDto dto, DateTimeOffset recordedAt)
    {
        if (dto.Products.Count == 0)
        {
            return Array.Empty<AffectedPackage>();
        }

        var packages = new List<AffectedPackage>(dto.Products.Count);
        foreach (var product in dto.Products)
        {
            if (string.IsNullOrWhiteSpace(product))
            {
                continue;
            }

            var identifier = product.Trim();
            var provenance = new AdvisoryProvenance(
                CccsConnectorPlugin.SourceName,
                "package",
                identifier,
                recordedAt,
                new[] { ProvenanceFieldMasks.AffectedPackages });

            packages.Add(new AffectedPackage(
                AffectedPackageTypes.Vendor,
                identifier,
                platform: null,
                versionRanges: Array.Empty<AffectedVersionRange>(),
                statuses: Array.Empty<AffectedPackageStatus>(),
                provenance: new[] { provenance },
                normalizedVersions: Array.Empty<NormalizedVersionRule>()));
        }

        return packages.Count == 0
            ? Array.Empty<AffectedPackage>()
            : packages
                .DistinctBy(static package => package.Identifier, StringComparer.OrdinalIgnoreCase)
                .OrderBy(static package => package.Identifier, StringComparer.OrdinalIgnoreCase)
                .ToArray();
    }
}
