using System;
using System.Linq;
using StellaOps.Feedser.Merge.Identity;
using StellaOps.Feedser.Models;
using Xunit;

namespace StellaOps.Feedser.Merge.Tests;

public sealed class AdvisoryIdentityResolverTests
{
    private readonly AdvisoryIdentityResolver _resolver = new();

    [Fact]
    public void Resolve_GroupsBySharedCveAlias()
    {
        var nvd = CreateAdvisory("CVE-2025-1234", aliases: new[] { "CVE-2025-1234" }, source: "nvd");
        var vendor = CreateAdvisory("VSA-2025-01", aliases: new[] { "CVE-2025-1234", "VSA-2025-01" }, source: "vendor");

        var clusters = _resolver.Resolve(new[] { nvd, vendor });

        var cluster = Assert.Single(clusters);
        Assert.Equal("CVE-2025-1234", cluster.AdvisoryKey);
        Assert.Equal(2, cluster.Advisories.Length);
        Assert.True(cluster.Aliases.Any(alias => alias.Value == "CVE-2025-1234"));
    }

    [Fact]
    public void Resolve_PrefersPsirtAliasWhenNoCve()
    {
        var vendor = CreateAdvisory("VMSA-2025-0001", aliases: new[] { "VMSA-2025-0001" }, source: "vmware");
        var osv = CreateAdvisory("OSV-2025-1", aliases: new[] { "OSV-2025-1", "GHSA-xxxx-yyyy-zzzz", "VMSA-2025-0001" }, source: "osv");

        var clusters = _resolver.Resolve(new[] { vendor, osv });

        var cluster = Assert.Single(clusters);
        Assert.Equal("VMSA-2025-0001", cluster.AdvisoryKey);
        Assert.Equal(2, cluster.Advisories.Length);
        Assert.True(cluster.Aliases.Any(alias => alias.Value == "VMSA-2025-0001"));
    }

    [Fact]
    public void Resolve_FallsBackToGhsaWhenOnlyGhsaPresent()
    {
        var ghsa = CreateAdvisory("GHSA-aaaa-bbbb-cccc", aliases: new[] { "GHSA-aaaa-bbbb-cccc" }, source: "ghsa");
        var osv = CreateAdvisory("OSV-2025-99", aliases: new[] { "OSV-2025-99", "GHSA-aaaa-bbbb-cccc" }, source: "osv");

        var clusters = _resolver.Resolve(new[] { ghsa, osv });

        var cluster = Assert.Single(clusters);
        Assert.Equal("GHSA-aaaa-bbbb-cccc", cluster.AdvisoryKey);
        Assert.Equal(2, cluster.Advisories.Length);
        Assert.True(cluster.Aliases.Any(alias => alias.Value == "GHSA-aaaa-bbbb-cccc"));
    }

    [Fact]
    public void Resolve_GroupsByKeyWhenNoAliases()
    {
        var first = CreateAdvisory("custom-1", aliases: Array.Empty<string>(), source: "source-a");
        var second = CreateAdvisory("custom-1", aliases: Array.Empty<string>(), source: "source-b");

        var clusters = _resolver.Resolve(new[] { first, second });

        var cluster = Assert.Single(clusters);
        Assert.Equal("custom-1", cluster.AdvisoryKey);
        Assert.Equal(2, cluster.Advisories.Length);
        Assert.Contains(cluster.Aliases, alias => alias.Value == "custom-1");
    }

    private static Advisory CreateAdvisory(string key, string[] aliases, string source)
    {
        var provenance = new[]
        {
            new AdvisoryProvenance(source, "mapping", key, DateTimeOffset.UtcNow),
        };

        return new Advisory(
            key,
            $"{key} title",
            $"{key} summary",
            "en",
            DateTimeOffset.UtcNow,
            DateTimeOffset.UtcNow,
            null,
            exploitKnown: false,
            aliases,
            Array.Empty<AdvisoryCredit>(),
            Array.Empty<AdvisoryReference>(),
            Array.Empty<AffectedPackage>(),
            Array.Empty<CvssMetric>(),
            provenance);
    }
}
