using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Source.Common;
using StellaOps.Feedser.Source.Common.Packages;
using StellaOps.Feedser.Storage.Mongo.Documents;
using StellaOps.Feedser.Storage.Mongo.Dtos;
using StellaOps.Feedser.Storage.Mongo.PsirtFlags;

namespace StellaOps.Feedser.Source.Vndr.Vmware.Internal;

internal static class VmwareMapper
{
    public static (Advisory Advisory, PsirtFlagRecord Flag) Map(VmwareDetailDto dto, DocumentRecord document, DtoRecord dtoRecord)
    {
        ArgumentNullException.ThrowIfNull(dto);
        ArgumentNullException.ThrowIfNull(document);
        ArgumentNullException.ThrowIfNull(dtoRecord);

        var recordedAt = dtoRecord.ValidatedAt.ToUniversalTime();
        var fetchProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "document", document.Uri, document.FetchedAt.ToUniversalTime());
        var mappingProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "mapping", dto.AdvisoryId, recordedAt);

        var aliases = BuildAliases(dto);
        var references = BuildReferences(dto, recordedAt);
        var affectedPackages = BuildAffectedPackages(dto, recordedAt);

        var advisory = new Advisory(
            dto.AdvisoryId,
            dto.Title,
            dto.Summary,
            language: "en",
            dto.Published?.ToUniversalTime(),
            dto.Modified?.ToUniversalTime(),
            severity: null,
            exploitKnown: false,
            aliases,
            references,
            affectedPackages,
            cvssMetrics: Array.Empty<CvssMetric>(),
            provenance: new[] { fetchProvenance, mappingProvenance });

        var flag = new PsirtFlagRecord(
            dto.AdvisoryId,
            "VMware",
            VmwareConnectorPlugin.SourceName,
            dto.AdvisoryId,
            recordedAt);

        return (advisory, flag);
    }

    private static IEnumerable<string> BuildAliases(VmwareDetailDto dto)
    {
        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { dto.AdvisoryId };
        if (dto.CveIds is not null)
        {
            foreach (var cve in dto.CveIds)
            {
                if (!string.IsNullOrWhiteSpace(cve))
                {
                    set.Add(cve.Trim());
                }
            }
        }

        return set;
    }

    private static IReadOnlyList<AdvisoryReference> BuildReferences(VmwareDetailDto dto, DateTimeOffset recordedAt)
    {
        if (dto.References is null || dto.References.Count == 0)
        {
            return Array.Empty<AdvisoryReference>();
        }

        var references = new List<AdvisoryReference>(dto.References.Count);
        foreach (var reference in dto.References)
        {
            if (string.IsNullOrWhiteSpace(reference.Url))
            {
                continue;
            }

            var kind = NormalizeReferenceKind(reference.Type);
            var provenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "reference", reference.Url, recordedAt);
            try
            {
                references.Add(new AdvisoryReference(reference.Url, kind, reference.Type, null, provenance));
            }
            catch (ArgumentException)
            {
                // ignore invalid urls
            }
        }

        references.Sort(static (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left.Url, right.Url));
        return references.Count == 0 ? Array.Empty<AdvisoryReference>() : references;
    }

    private static string? NormalizeReferenceKind(string? type)
    {
        if (string.IsNullOrWhiteSpace(type))
        {
            return null;
        }

        return type.Trim().ToLowerInvariant() switch
        {
            "advisory" => "advisory",
            "kb" or "kb_article" => "kb",
            "patch" => "patch",
            "workaround" => "workaround",
            _ => null,
        };
    }

    private static IReadOnlyList<AffectedPackage> BuildAffectedPackages(VmwareDetailDto dto, DateTimeOffset recordedAt)
    {
        if (dto.Affected is null || dto.Affected.Count == 0)
        {
            return Array.Empty<AffectedPackage>();
        }

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

            var provenance = new[]
            {
                new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "affected", product.Product, recordedAt),
            };

            var ranges = new List<AffectedVersionRange>();
            if (!string.IsNullOrWhiteSpace(product.Version) || !string.IsNullOrWhiteSpace(product.FixedVersion))
            {
                var rangeProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "range", product.Product, recordedAt);
                ranges.Add(new AffectedVersionRange(
                    rangeKind: "vendor",
                    introducedVersion: product.Version,
                    fixedVersion: product.FixedVersion,
                    lastAffectedVersion: null,
                    rangeExpression: product.Version,
                    provenance: rangeProvenance,
                    primitives: BuildRangePrimitives(product)));
            }

            packages.Add(new AffectedPackage(
                AffectedPackageTypes.Vendor,
                product.Product,
                platform: null,
                versionRanges: ranges,
                statuses: Array.Empty<AffectedPackageStatus>(),
                provenance: provenance));
        }

        return packages;
    }

    private static RangePrimitives? BuildRangePrimitives(VmwareAffectedProductDto product)
    {
        var extensions = new Dictionary<string, string>(StringComparer.Ordinal);
        AddExtension(extensions, "vmware.product", product.Product);
        AddExtension(extensions, "vmware.version.raw", product.Version);
        AddExtension(extensions, "vmware.fixedVersion.raw", product.FixedVersion);

        var semVer = BuildSemVerPrimitive(product.Version, product.FixedVersion);
        if (semVer is null && extensions.Count == 0)
        {
            return null;
        }

        return new RangePrimitives(semVer, null, null, extensions.Count == 0 ? null : extensions);
    }

    private static SemVerPrimitive? BuildSemVerPrimitive(string? introduced, string? fixedVersion)
    {
        var introducedNormalized = NormalizeSemVer(introduced);
        var fixedNormalized = NormalizeSemVer(fixedVersion);

        if (introducedNormalized is null && fixedNormalized is null)
        {
            return null;
        }

        return new SemVerPrimitive(
            introducedNormalized,
            IntroducedInclusive: true,
            fixedNormalized,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: null);
    }

    private static string? NormalizeSemVer(string? value)
    {
        if (PackageCoordinateHelper.TryParseSemVer(value, out _, out var normalized) && !string.IsNullOrWhiteSpace(normalized))
        {
            return normalized;
        }

        if (Version.TryParse(value, out var parsed))
        {
            if (parsed.Build >= 0 && parsed.Revision >= 0)
            {
                return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}.{parsed.Revision}";
            }

            if (parsed.Build >= 0)
            {
                return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}";
            }

            return $"{parsed.Major}.{parsed.Minor}";
        }

        return null;
    }

    private static void AddExtension(Dictionary<string, string> extensions, string key, string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return;
        }

        extensions[key] = value.Trim();
    }
}
