using System;
using System.Globalization;
using MongoDB.Bson;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Source.CertCc.Internal;
using StellaOps.Feedser.Storage.Mongo.Documents;
using StellaOps.Feedser.Storage.Mongo.Dtos;
using Xunit;

namespace StellaOps.Feedser.Source.CertCc.Tests.Internal;

public sealed class CertCcMapperTests
{
    private static readonly DateTimeOffset PublishedAt = DateTimeOffset.Parse("2025-10-03T11:35:31Z", CultureInfo.InvariantCulture);

    [Fact]
    public void Map_ProducesCanonicalAdvisoryWithVendorPrimitives()
    {
        const string vendorStatement =
            "The issue is confirmed, and here is the patch list\n\n" +
            "V3912/V3910/V2962/V1000B\t4.4.3.6/4.4.5.1\n" +
            "V2927/V2865/V2866\t4.5.1\n" +
            "V2765/V2766/V2763/V2135\t4.5.1";

        var vendor = new CertCcVendorDto(
            "DrayTek Corporation",
            ContactDate: PublishedAt.AddDays(-10),
            StatementDate: PublishedAt.AddDays(-5),
            Updated: PublishedAt,
            Statement: vendorStatement,
            Addendum: null,
            References: new[] { "https://www.draytek.com/support/resources?type=version" });

        var vendorStatus = new CertCcVendorStatusDto(
            Vendor: "DrayTek Corporation",
            CveId: "CVE-2025-10547",
            Status: "Affected",
            Statement: null,
            References: Array.Empty<string>(),
            DateAdded: PublishedAt,
            DateUpdated: PublishedAt);

        var vulnerability = new CertCcVulnerabilityDto(
            CveId: "CVE-2025-10547",
            Description: null,
            DateAdded: PublishedAt,
            DateUpdated: PublishedAt);

        var metadata = new CertCcNoteMetadata(
            VuId: "VU#294418",
            IdNumber: "294418",
            Title: "Vigor routers running DrayOS RCE via EasyVPN",
            Overview: "Overview",
            Summary: "Summary",
            Published: PublishedAt,
            Updated: PublishedAt.AddMinutes(5),
            Created: PublishedAt,
            Revision: 2,
            CveIds: new[] { "CVE-2025-10547" },
            PublicUrls: new[]
            {
                "https://www.draytek.com/about/security-advisory/use-of-uninitialized-variable-vulnerabilities/",
                "https://www.draytek.com/support/resources?type=version"
            },
            PrimaryUrl: "https://www.kb.cert.org/vuls/id/294418/");

        var dto = new CertCcNoteDto(
            metadata,
            Vendors: new[] { vendor },
            VendorStatuses: new[] { vendorStatus },
            Vulnerabilities: new[] { vulnerability });

        var document = new DocumentRecord(
            Guid.NewGuid(),
            "cert-cc",
            "https://www.kb.cert.org/vuls/id/294418/",
            PublishedAt,
            Sha256: new string('0', 64),
            Status: "pending-map",
            ContentType: "application/json",
            Headers: null,
            Metadata: null,
            Etag: null,
            LastModified: PublishedAt,
            GridFsId: null);

        var dtoRecord = new DtoRecord(
            Id: Guid.NewGuid(),
            DocumentId: document.Id,
            SourceName: "cert-cc",
            SchemaVersion: "certcc.vince.note.v1",
            Payload: new BsonDocument(),
            ValidatedAt: PublishedAt.AddMinutes(1));

        var advisory = CertCcMapper.Map(dto, document, dtoRecord, "cert-cc");

        Assert.Equal("certcc/vu-294418", advisory.AdvisoryKey);
        Assert.Contains("VU#294418", advisory.Aliases);
        Assert.Contains("CVE-2025-10547", advisory.Aliases);
        Assert.Equal("en", advisory.Language);
        Assert.Equal(PublishedAt, advisory.Published);

        Assert.Contains(advisory.References, reference => reference.Url.Contains("/vuls/id/294418", StringComparison.OrdinalIgnoreCase));

        var affected = Assert.Single(advisory.AffectedPackages);
        Assert.Equal("vendor", affected.Type);
        Assert.Equal("DrayTek Corporation", affected.Identifier);
        Assert.Contains(affected.Statuses, status => status.Status == AffectedPackageStatusCatalog.Affected);

        var range = Assert.Single(affected.VersionRanges);
        Assert.NotNull(range.Primitives);
        Assert.NotNull(range.Primitives!.VendorExtensions);
        Assert.Contains(range.Primitives.VendorExtensions!, kvp => kvp.Key == "certcc.vendor.patches");

        Assert.NotEmpty(affected.NormalizedVersions);
        Assert.Contains(affected.NormalizedVersions, rule => rule.Scheme == "certcc.vendor" && rule.Value == "4.5.1");
    }
}
