using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Driver;
using StellaOps.Feedser.Merge.Services;
using StellaOps.Feedser.Storage.Mongo;
using StellaOps.Feedser.Storage.Mongo.Aliases;
using StellaOps.Feedser.Testing;

namespace StellaOps.Feedser.Merge.Tests;

[Collection("mongo-fixture")]
public sealed class AliasGraphResolverTests : IClassFixture<MongoIntegrationFixture>
{
    private readonly MongoIntegrationFixture _fixture;

    public AliasGraphResolverTests(MongoIntegrationFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public async Task ResolveAsync_ReturnsCollisions_WhenAliasesOverlap()
    {
        await DropAliasCollectionAsync();

        var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
        var resolver = new AliasGraphResolver(aliasStore);

        var timestamp = DateTimeOffset.UtcNow;
        await aliasStore.ReplaceAsync(
            "ADV-1",
            new[] { new AliasEntry("CVE", "CVE-2025-2000"), new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-1") },
            timestamp,
            CancellationToken.None);

        await aliasStore.ReplaceAsync(
            "ADV-2",
            new[] { new AliasEntry("CVE", "CVE-2025-2000"), new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-2") },
            timestamp.AddMinutes(1),
            CancellationToken.None);

        var result = await resolver.ResolveAsync("ADV-1", CancellationToken.None);
        Assert.NotNull(result);
        Assert.Equal("ADV-1", result.AdvisoryKey);
        Assert.NotEmpty(result.Collisions);
        var collision = Assert.Single(result.Collisions);
        Assert.Equal("CVE", collision.Scheme);
        Assert.Contains("ADV-1", collision.AdvisoryKeys);
        Assert.Contains("ADV-2", collision.AdvisoryKeys);
    }

    [Fact]
    public async Task BuildComponentAsync_TracesConnectedAdvisories()
    {
        await DropAliasCollectionAsync();
        var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
        var resolver = new AliasGraphResolver(aliasStore);

        var timestamp = DateTimeOffset.UtcNow;
        await aliasStore.ReplaceAsync(
            "ADV-A",
            new[] { new AliasEntry("CVE", "CVE-2025-4000"), new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-A") },
            timestamp,
            CancellationToken.None);

        await aliasStore.ReplaceAsync(
            "ADV-B",
            new[] { new AliasEntry("CVE", "CVE-2025-4000"), new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-B"), new AliasEntry("OSV", "OSV-2025-1") },
            timestamp.AddMinutes(1),
            CancellationToken.None);

        await aliasStore.ReplaceAsync(
            "ADV-C",
            new[] { new AliasEntry("OSV", "OSV-2025-1"), new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-C") },
            timestamp.AddMinutes(2),
            CancellationToken.None);

        var component = await resolver.BuildComponentAsync("ADV-A", CancellationToken.None);
        Assert.Contains("ADV-A", component.AdvisoryKeys, StringComparer.OrdinalIgnoreCase);
        Assert.Contains("ADV-B", component.AdvisoryKeys, StringComparer.OrdinalIgnoreCase);
        Assert.Contains("ADV-C", component.AdvisoryKeys, StringComparer.OrdinalIgnoreCase);
        Assert.NotEmpty(component.Collisions);
        Assert.True(component.AliasMap.ContainsKey("ADV-A"));
        Assert.Contains(component.AliasMap["ADV-B"], record => record.Scheme == "OSV" && record.Value == "OSV-2025-1");
    }

    private async Task DropAliasCollectionAsync()
    {
        try
        {
            await _fixture.Database.DropCollectionAsync(MongoStorageDefaults.Collections.Alias);
        }
        catch (MongoDB.Driver.MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase))
        {
        }
    }

    [Fact]
    public async Task BuildComponentAsync_LinksOsvAndGhsaAliases()
    {
        await DropAliasCollectionAsync();

        var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
        var resolver = new AliasGraphResolver(aliasStore);
        var timestamp = DateTimeOffset.UtcNow;

        await aliasStore.ReplaceAsync(
            "ADV-OSV",
            new[]
            {
                new AliasEntry("OSV", "OSV-2025-2001"),
                new AliasEntry("GHSA", "GHSA-zzzz-zzzz-zzzz"),
                new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-OSV"),
            },
            timestamp,
            CancellationToken.None);

        await aliasStore.ReplaceAsync(
            "ADV-GHSA",
            new[]
            {
                new AliasEntry("GHSA", "GHSA-zzzz-zzzz-zzzz"),
                new AliasEntry(AliasStoreConstants.PrimaryScheme, "ADV-GHSA"),
            },
            timestamp.AddMinutes(1),
            CancellationToken.None);

        var component = await resolver.BuildComponentAsync("ADV-OSV", CancellationToken.None);

        Assert.Contains("ADV-GHSA", component.AdvisoryKeys, StringComparer.OrdinalIgnoreCase);
        Assert.Contains(component.Collisions, collision => collision.Scheme == "GHSA" && collision.Value == "GHSA-zzzz-zzzz-zzzz");
    }
}
