using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Feedser.Source.Common.Testing;
using StellaOps.Feedser.Source.Common.Http;
using StellaOps.Feedser.Source.Ics.Cisa;
using StellaOps.Feedser.Source.Ics.Cisa.Configuration;
using StellaOps.Feedser.Storage.Mongo;
using StellaOps.Feedser.Storage.Mongo.Advisories;
using StellaOps.Feedser.Testing;
using Xunit;

namespace StellaOps.Feedser.Source.Ics.Cisa.Tests;

[Collection("mongo-fixture")]
public sealed class IcsCisaConnectorTests : IAsyncLifetime
{
    private readonly MongoIntegrationFixture _fixture;
    private readonly CannedHttpMessageHandler _handler = new();

    public IcsCisaConnectorTests(MongoIntegrationFixture fixture)
    {
        _fixture = fixture ?? throw new ArgumentNullException(nameof(fixture));
    }

    [Fact]
    public async Task FetchParseMap_EndToEnd_ProducesCanonicalAdvisories()
    {
        await using var provider = await BuildServiceProviderAsync();
        RegisterResponses();

        var connector = provider.GetRequiredService<IcsCisaConnector>();
        await connector.FetchAsync(provider, CancellationToken.None);
        await connector.ParseAsync(provider, CancellationToken.None);
        await connector.MapAsync(provider, CancellationToken.None);

        _handler.AssertNoPendingResponses();

        var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
        var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);

        Assert.Equal(2, advisories.Count);

        var icsa = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "ICSA-25-123-01");
        Console.WriteLine("ProductsRaw:" + string.Join("|", icsa.AffectedPackages.SelectMany(p => p.Provenance).Select(p => p.Value ?? "<null>")));
        Assert.Contains("CVE-2024-12345", icsa.Aliases);
        Assert.Contains(icsa.References, reference => reference.Url == "https://example.com/security/icsa-25-123-01");
        Assert.Contains(icsa.References, reference => reference.Url == "https://files.cisa.gov/docs/icsa-25-123-01.pdf" && reference.Kind == "attachment");
        var icsaMitigations = icsa.References.Where(reference => reference.Kind == "mitigation").ToList();
        Assert.Equal(2, icsaMitigations.Count);
        Assert.Contains("Apply ControlSuite firmware version 4.2.1 or later.", icsaMitigations[0].Summary, StringComparison.Ordinal);
        Assert.EndsWith("#mitigation-1", icsaMitigations[0].Url, StringComparison.Ordinal);
        Assert.Contains("Restrict network access", icsaMitigations[1].Summary, StringComparison.Ordinal);

        var controlSuitePackage = Assert.Single(icsa.AffectedPackages, package => string.Equals(package.Identifier, "ControlSuite", StringComparison.OrdinalIgnoreCase));
        var controlSuiteRange = Assert.Single(controlSuitePackage.VersionRanges);
        Assert.Equal("product", controlSuiteRange.RangeKind);
        Assert.Equal("4.2", controlSuiteRange.RangeExpression);
        Assert.NotNull(controlSuiteRange.Primitives);
        Assert.NotNull(controlSuiteRange.Primitives!.SemVer);
        Assert.Equal("4.2.0", controlSuiteRange.Primitives.SemVer!.ExactValue);
        Assert.True(controlSuiteRange.Primitives.VendorExtensions!.TryGetValue("ics.product", out var controlSuiteProduct) && controlSuiteProduct == "ControlSuite");
        Assert.True(controlSuiteRange.Primitives.VendorExtensions!.TryGetValue("ics.version", out var controlSuiteVersion) && controlSuiteVersion == "4.2");
        Assert.True(controlSuiteRange.Primitives.VendorExtensions!.TryGetValue("ics.vendors", out var controlSuiteVendors) && controlSuiteVendors == "Example Corp");

        var icsma = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "ICSMA-25-045-01");
        Assert.Contains("CVE-2025-11111", icsma.Aliases);
        var icsmaMitigation = Assert.Single(icsma.References.Where(reference => reference.Kind == "mitigation"));
        Assert.Contains("Contact HealthTech support", icsmaMitigation.Summary, StringComparison.Ordinal);
        Assert.Contains(icsma.References, reference => reference.Url == "https://www.cisa.gov/sites/default/files/2025-10/ICSMA-25-045-01_Supplement.pdf");
        var infusionPackage = Assert.Single(icsma.AffectedPackages, package => string.Equals(package.Identifier, "InfusionManager", StringComparison.OrdinalIgnoreCase));
        var infusionRange = Assert.Single(infusionPackage.VersionRanges);
        Assert.Equal("2.1", infusionRange.RangeExpression);
    }

    private async Task<ServiceProvider> BuildServiceProviderAsync()
    {
        await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName);
        _handler.Clear();

        var services = new ServiceCollection();
        services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
        services.AddSingleton(_handler);

        services.AddMongoStorage(options =>
        {
            options.ConnectionString = _fixture.Runner.ConnectionString;
            options.DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName;
            options.CommandTimeout = TimeSpan.FromSeconds(5);
        });

        services.AddSourceCommon();
        services.AddIcsCisaConnector(options =>
        {
            options.GovDeliveryCode = "TESTCODE";
            options.TopicsEndpoint = new Uri("https://feed.test/topics.rss", UriKind.Absolute);
            options.TopicIds.Clear();
            options.TopicIds.Add("USDHSCISA_TEST");
            options.RequestDelay = TimeSpan.Zero;
            options.DetailBaseUri = new Uri("https://www.cisa.gov/", UriKind.Absolute);
            options.AdditionalHosts.Add("files.cisa.gov");
        });

        services.Configure<HttpClientFactoryOptions>(IcsCisaOptions.HttpClientName, builder =>
        {
            builder.HttpMessageHandlerBuilderActions.Add(handlerBuilder =>
            {
                handlerBuilder.PrimaryHandler = _handler;
            });
        });

        var provider = services.BuildServiceProvider();
        var bootstrapper = provider.GetRequiredService<MongoBootstrapper>();
        await bootstrapper.InitializeAsync(CancellationToken.None);
        return provider;
    }

    private void RegisterResponses()
    {
        var feedUri = new Uri("https://feed.test/topics.rss?code=TESTCODE&format=xml&topic_id=USDHSCISA_TEST", UriKind.Absolute);
        _handler.AddResponse(feedUri, () => CreateTextResponse("IcsCisa/Fixtures/sample-feed.xml", "application/rss+xml"));

        var icsaDetail = new Uri("https://www.cisa.gov/news-events/ics-advisories/icsa-25-123-01", UriKind.Absolute);
        _handler.AddResponse(icsaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsa-25-123-01.html", "text/html"));

        var icsmaDetail = new Uri("https://www.cisa.gov/news-events/ics-medical-advisories/icsma-25-045-01", UriKind.Absolute);
        _handler.AddResponse(icsmaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsma-25-045-01.html", "text/html"));
    }

    private static HttpResponseMessage CreateTextResponse(string relativePath, string contentType)
    {
        var fullPath = Path.Combine(AppContext.BaseDirectory, relativePath);
        var content = File.ReadAllText(fullPath);
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(content, Encoding.UTF8, contentType),
        };
    }

    public Task InitializeAsync() => Task.CompletedTask;

    public Task DisposeAsync()
    {
        _handler.Clear();
        return Task.CompletedTask;
    }
}
