using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Feedser.Merge.Services;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Storage.Mongo.MergeEvents;

namespace StellaOps.Feedser.Merge.Tests;

public sealed class MergeEventWriterTests
{
    [Fact]
    public async Task AppendAsync_WritesRecordWithComputedHashes()
    {
        var store = new InMemoryMergeEventStore();
        var calculator = new CanonicalHashCalculator();
        var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2024-05-01T00:00:00Z"));
        var writer = new MergeEventWriter(store, calculator, timeProvider, NullLogger<MergeEventWriter>.Instance);

        var before = CreateAdvisory("CVE-2024-0001", "Initial");
        var after = CreateAdvisory("CVE-2024-0001", "Sample", summary: "Updated");

        var documentIds = new[] { Guid.NewGuid(), Guid.NewGuid() };
        var record = await writer.AppendAsync("CVE-2024-0001", before, after, documentIds, Array.Empty<MergeFieldDecision>(), CancellationToken.None);

        Assert.NotEqual(Guid.Empty, record.Id);
        Assert.Equal("CVE-2024-0001", record.AdvisoryKey);
        Assert.True(record.AfterHash.Length > 0);
        Assert.Equal(timeProvider.GetUtcNow(), record.MergedAt);
        Assert.Equal(documentIds, record.InputDocumentIds);
        Assert.NotNull(store.LastRecord);
        Assert.Same(store.LastRecord, record);
    }

    [Fact]
    public async Task AppendAsync_NullBeforeUsesEmptyHash()
    {
        var store = new InMemoryMergeEventStore();
        var calculator = new CanonicalHashCalculator();
        var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2024-05-01T00:00:00Z"));
        var writer = new MergeEventWriter(store, calculator, timeProvider, NullLogger<MergeEventWriter>.Instance);

        var after = CreateAdvisory("CVE-2024-0002", "Changed");

        var record = await writer.AppendAsync("CVE-2024-0002", null, after, Array.Empty<Guid>(), Array.Empty<MergeFieldDecision>(), CancellationToken.None);

        Assert.Empty(record.BeforeHash);
        Assert.True(record.AfterHash.Length > 0);
    }


    private static Advisory CreateAdvisory(string advisoryKey, string title, string? summary = null)
    {
        return new Advisory(
            advisoryKey,
            title,
            summary,
            language: "en",
            published: DateTimeOffset.Parse("2024-01-01T00:00:00Z"),
            modified: DateTimeOffset.Parse("2024-01-02T00:00:00Z"),
            severity: "medium",
            exploitKnown: false,
            aliases: new[] { advisoryKey },
            references: new[]
            {
                new AdvisoryReference("https://example.com/" + advisoryKey.ToLowerInvariant(), "external", "vendor", summary: null, provenance: AdvisoryProvenance.Empty)
            },
            affectedPackages: Array.Empty<AffectedPackage>(),
            cvssMetrics: Array.Empty<CvssMetric>(),
            provenance: Array.Empty<AdvisoryProvenance>());
    }

    private sealed class InMemoryMergeEventStore : IMergeEventStore
    {
        public MergeEventRecord? LastRecord { get; private set; }

        public Task AppendAsync(MergeEventRecord record, CancellationToken cancellationToken)
        {
            LastRecord = record;
            return Task.CompletedTask;
        }

        public Task<IReadOnlyList<MergeEventRecord>> GetRecentAsync(string advisoryKey, int limit, CancellationToken cancellationToken)
            => Task.FromResult<IReadOnlyList<MergeEventRecord>>(Array.Empty<MergeEventRecord>());
    }
}
