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

namespace StellaOps.Feedser.Source.Kisa.Internal;

internal static class KisaMapper
{
    public static Advisory Map(KisaParsedAdvisory 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 AdvisoryProvenance(
            KisaConnectorPlugin.SourceName,
            "advisory",
            dto.AdvisoryId,
            recordedAt,
            new[] { ProvenanceFieldMasks.Advisory });

        return new Advisory(
            advisoryKey: dto.AdvisoryId,
            title: dto.Title,
            summary: dto.Summary,
            language: "ko",
            published: dto.Published,
            modified: dto.Modified,
            severity: dto.Severity?.ToLowerInvariant(),
            exploitKnown: false,
            aliases: aliases,
            references: references,
            affectedPackages: packages,
            cvssMetrics: Array.Empty<CvssMetric>(),
            provenance: new[] { provenance });
    }

    private static IReadOnlyList<string> BuildAliases(KisaParsedAdvisory dto)
    {
        var aliases = new List<string>(capacity: dto.CveIds.Count + 1) { dto.AdvisoryId };
        aliases.AddRange(dto.CveIds);
        return aliases
            .Where(static alias => !string.IsNullOrWhiteSpace(alias))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(static alias => alias, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private static IReadOnlyList<AdvisoryReference> BuildReferences(KisaParsedAdvisory dto, DateTimeOffset recordedAt)
    {
        var references = new List<AdvisoryReference>
        {
            new(dto.DetailPageUri.ToString(), "details", "kisa", null, new AdvisoryProvenance(
                KisaConnectorPlugin.SourceName,
                "reference",
                dto.DetailPageUri.ToString(),
                recordedAt,
                new[] { ProvenanceFieldMasks.References }))
        };

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

            references.Add(new AdvisoryReference(
                reference.Url,
                kind: "reference",
                sourceTag: "kisa",
                summary: reference.Label,
                provenance: new AdvisoryProvenance(
                    KisaConnectorPlugin.SourceName,
                    "reference",
                    reference.Url,
                    recordedAt,
                    new[] { ProvenanceFieldMasks.References })));
        }

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

    private static IReadOnlyList<AffectedPackage> BuildPackages(KisaParsedAdvisory 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)
        {
            var vendor = string.IsNullOrWhiteSpace(product.Vendor) ? "Unknown" : product.Vendor!;
            var name = product.Name;
            var identifier = string.IsNullOrWhiteSpace(name) ? vendor : $"{vendor} {name}";

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

            var versionRanges = string.IsNullOrWhiteSpace(product.Versions)
                ? Array.Empty<AffectedVersionRange>()
                : new[]
                    {
                        new AffectedVersionRange(
                            rangeKind: "string",
                            introducedVersion: null,
                            fixedVersion: null,
                            lastAffectedVersion: null,
                            rangeExpression: product.Versions,
                            provenance: new AdvisoryProvenance(
                                KisaConnectorPlugin.SourceName,
                                "package-range",
                                product.Versions,
                                recordedAt,
                                new[] { ProvenanceFieldMasks.VersionRanges }))
                    };

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

        return packages
            .DistinctBy(static package => package.Identifier, StringComparer.OrdinalIgnoreCase)
            .OrderBy(static package => package.Identifier, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }
}
