using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using MongoDB.Bson;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Source.Osv;
using StellaOps.Feedser.Source.Osv.Internal;
using StellaOps.Feedser.Storage.Mongo.Documents;
using StellaOps.Feedser.Storage.Mongo.Dtos;
using StellaOps.Feedser.Source.Common;
using Xunit;
using Xunit.Abstractions;

namespace StellaOps.Feedser.Source.Osv.Tests;

public sealed class OsvSnapshotTests
{
    private static readonly DateTimeOffset BaselinePublished = new(2025, 1, 5, 12, 0, 0, TimeSpan.Zero);
    private static readonly DateTimeOffset BaselineModified = new(2025, 1, 8, 6, 30, 0, TimeSpan.Zero);
    private static readonly DateTimeOffset BaselineFetched = new(2025, 1, 8, 7, 0, 0, TimeSpan.Zero);

    private readonly ITestOutputHelper _output;

    public OsvSnapshotTests(ITestOutputHelper output)
    {
        _output = output;
    }

    [Theory]
    [InlineData("PyPI", "pkg:pypi/requests", "requests", "osv-pypi.snapshot.json")]
    [InlineData("npm", "pkg:npm/%40scope%2Fleft-pad", "@scope/left-pad", "osv-npm.snapshot.json")]
    public void Map_ProducesExpectedSnapshot(string ecosystem, string purl, string packageName, string snapshotFile)
    {
        var dto = CreateDto(ecosystem, purl, packageName);
        var document = CreateDocumentRecord(ecosystem);
        var dtoRecord = CreateDtoRecord(document, dto);

        var advisory = OsvMapper.Map(dto, document, dtoRecord, ecosystem);
        var actual = SnapshotSerializer.ToSnapshot(advisory).Trim();

        var snapshotPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", snapshotFile);
        var expected = File.Exists(snapshotPath) ? File.ReadAllText(snapshotPath).Trim() : string.Empty;

        if (!string.Equals(actual, expected, StringComparison.Ordinal))
        {
            _output.WriteLine(actual);
        }

        Assert.False(string.IsNullOrEmpty(expected), $"Snapshot '{snapshotFile}' not found or empty.");

        using var expectedJson = JsonDocument.Parse(expected);
        using var actualJson = JsonDocument.Parse(actual);
        Assert.True(JsonElement.DeepEquals(actualJson.RootElement, expectedJson.RootElement), "OSV snapshot mismatch.");
    }

    private static OsvVulnerabilityDto CreateDto(string ecosystem, string purl, string packageName)
    {
        return new OsvVulnerabilityDto
        {
            Id = $"OSV-2025-{ecosystem}-0001",
            Summary = $"{ecosystem} package vulnerability",
            Details = $"Detailed description for {ecosystem} package {packageName}.",
            Published = BaselinePublished,
            Modified = BaselineModified,
            Aliases = new[] { $"CVE-2025-11{ecosystem.Length}", $"GHSA-{ecosystem.Length}abc-{ecosystem.Length}def-{ecosystem.Length}ghi" },
            Related = new[] { $"OSV-RELATED-{ecosystem}-42" },
            References = new[]
            {
                new OsvReferenceDto { Url = $"https://example.com/{ecosystem}/advisory", Type = "ADVISORY" },
                new OsvReferenceDto { Url = $"https://example.com/{ecosystem}/fix", Type = "FIX" },
            },
            Severity = new[]
            {
                new OsvSeverityDto { Type = "CVSS_V3", Score = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" },
            },
            Affected = new[]
            {
                new OsvAffectedPackageDto
                {
                    Package = new OsvPackageDto
                    {
                        Ecosystem = ecosystem,
                        Name = packageName,
                        Purl = purl,
                    },
                    Ranges = new[]
                    {
                        new OsvRangeDto
                        {
                            Type = "SEMVER",
                            Events = new[]
                            {
                                new OsvEventDto { Introduced = "0" },
                                new OsvEventDto { Fixed = "2.0.0" },
                            }
                        }
                    },
                    Versions = new[] { "1.0.0", "1.5.0" },
                    EcosystemSpecific = ParseElement("{\"severity\":\"high\"}"),
                }
            },
            DatabaseSpecific = ParseElement("{\"source\":\"osv.dev\"}"),
        };
    }

    private static DocumentRecord CreateDocumentRecord(string ecosystem)
        => new(
            Guid.Parse("11111111-1111-1111-1111-111111111111"),
            OsvConnectorPlugin.SourceName,
            $"https://osv.dev/vulnerability/OSV-2025-{ecosystem}-0001",
            BaselineFetched,
            "sha256-osv-snapshot",
            DocumentStatuses.PendingParse,
            "application/json",
            null,
            new Dictionary<string, string>(StringComparer.Ordinal)
            {
                ["osv.ecosystem"] = ecosystem,
            },
            "\"osv-etag\"",
            BaselineModified,
            null,
            null);

    private static DtoRecord CreateDtoRecord(DocumentRecord document, OsvVulnerabilityDto dto)
    {
        var payload = BsonDocument.Parse(JsonSerializer.Serialize(dto, new JsonSerializerOptions(JsonSerializerDefaults.Web)
        {
            DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
        }));

        return new DtoRecord(Guid.Parse("22222222-2222-2222-2222-222222222222"), document.Id, OsvConnectorPlugin.SourceName, "osv.v1", payload, BaselineModified);
    }

    private static JsonElement ParseElement(string json)
    {
        using var document = JsonDocument.Parse(json);
        return document.RootElement.Clone();
    }
}
