using System.Net;
using System.Net.Http;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Feedser.Source.Common;
using StellaOps.Feedser.Source.Common.Http;
using StellaOps.Feedser.Source.Common.Fetch;
using StellaOps.Feedser.Source.Common.Testing;
using StellaOps.Feedser.Source.Vndr.Apple;
using StellaOps.Feedser.Storage.Mongo;
using StellaOps.Feedser.Storage.Mongo.Advisories;
using StellaOps.Feedser.Storage.Mongo.PsirtFlags;
using StellaOps.Feedser.Testing;
using Xunit;

namespace StellaOps.Feedser.Source.Vndr.Apple.Tests;

[Collection("mongo-fixture")]
public sealed class AppleConnectorTests : IAsyncLifetime
{
    private static readonly Uri IndexUri = new("https://support.example.com/index.json");
    private static readonly Uri DetailBaseUri = new("https://support.example.com/en-us/");

    private readonly MongoIntegrationFixture _fixture;
    private readonly FakeTimeProvider _timeProvider;

    public AppleConnectorTests(MongoIntegrationFixture fixture)
    {
        _fixture = fixture;
        _timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 10, 0, 0, 0, TimeSpan.Zero));
    }

    [Fact]
    public async Task FetchParseMap_EndToEnd_ProducesCanonicalAdvisories()
    {
        var handler = new CannedHttpMessageHandler();
        SeedIndex(handler);
        SeedDetail(handler);

        await using var provider = await BuildServiceProviderAsync(handler);
        var connector = provider.GetRequiredService<AppleConnector>();

        await connector.FetchAsync(provider, CancellationToken.None);
        await connector.ParseAsync(provider, CancellationToken.None);
        await connector.MapAsync(provider, CancellationToken.None);

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

        var iosAdvisory = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "HT214108");
        Assert.Contains("CVE-2025-1234", iosAdvisory.Aliases, StringComparer.OrdinalIgnoreCase);
        Assert.Contains("CVE-2025-5678", iosAdvisory.Aliases, StringComparer.OrdinalIgnoreCase);
        var iosPackage = Assert.Single(iosAdvisory.AffectedPackages);
        Assert.Equal("iPhone 15 Pro", iosPackage.Identifier);
        var iosRange = Assert.Single(iosPackage.VersionRanges);
        Assert.Equal("18.0.1", iosRange.FixedVersion);
        Assert.NotNull(iosRange.Primitives);
        Assert.Equal("22A123", iosRange.Primitives!.VendorExtensions!["apple.build"]);

        var rsrAdvisory = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "HT215500");
        Assert.Contains("CVE-2025-2468", rsrAdvisory.Aliases, StringComparer.OrdinalIgnoreCase);

        var flagStore = provider.GetRequiredService<IPsirtFlagStore>();
        var rsrFlag = await flagStore.FindAsync("HT215500", CancellationToken.None);
        Assert.NotNull(rsrFlag);
    }

    public Task InitializeAsync() => Task.CompletedTask;

    public Task DisposeAsync() => Task.CompletedTask;

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

        var services = new ServiceCollection();
        services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
        services.AddSingleton<TimeProvider>(_timeProvider);
        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.AddAppleConnector(opts =>
        {
            opts.SoftwareLookupUri = IndexUri;
            opts.AdvisoryBaseUri = DetailBaseUri;
            opts.LocaleSegment = "en-us";
            opts.InitialBackfill = TimeSpan.FromDays(120);
            opts.ModifiedTolerance = TimeSpan.FromHours(2);
            opts.MaxAdvisoriesPerFetch = 10;
        });

        services.Configure<HttpClientFactoryOptions>(AppleOptions.HttpClientName, builderOptions =>
        {
            builderOptions.HttpMessageHandlerBuilderActions.Add(builder =>
            {
                builder.PrimaryHandler = handler;
            });
        });

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

    private static void SeedIndex(CannedHttpMessageHandler handler)
    {
        handler.AddJsonResponse(IndexUri, ReadFixture("index.json"));
    }

    private static void SeedDetail(CannedHttpMessageHandler handler)
    {
        AddHtmlResponse(handler, new Uri(DetailBaseUri, "HT214108"), "ht214108.html");
        AddHtmlResponse(handler, new Uri(DetailBaseUri, "HT215500"), "ht215500.html");
    }

    private static void AddHtmlResponse(CannedHttpMessageHandler handler, Uri uri, string fixture)
    {
        handler.AddResponse(uri, () =>
        {
            var content = ReadFixture(fixture);
            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(content, Encoding.UTF8, "text/html"),
            };
        });
    }

    private static string ReadFixture(string name)
    {
        var path = Path.Combine(
            AppContext.BaseDirectory,
            "Source",
            "Vndr",
            "Apple",
            "Fixtures",
            name);
        return File.ReadAllText(path);
    }
}
