using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Authentication;
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Configuration;
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Metadata;
using System.IO.Abstractions.TestingHelpers;
using System.Threading;
using Xunit;

namespace StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Tests.Metadata;

public sealed class RancherHubMetadataLoaderTests
{
    private const string SampleDiscovery = """
        {
          "hubId": "excititor:suse.rancher",
          "title": "SUSE Rancher VEX Hub",
          "subscription": {
            "eventsUri": "https://vexhub.suse.com/api/v1/events",
            "checkpointUri": "https://vexhub.suse.com/api/v1/checkpoints",
            "requiresAuthentication": true,
            "channels": ["rke2", "k3s"],
            "scopes": ["hub.read", "hub.events"]
          },
          "authentication": {
            "tokenUri": "https://identity.suse.com/oauth2/token",
            "audience": "https://vexhub.suse.com"
          },
          "offline": {
            "snapshotUri": "https://downloads.suse.com/vexhub/snapshot.json",
            "sha256": "deadbeef",
            "updated": "2025-10-10T12:00:00Z"
          }
        }
        """;

    [Fact]
    public async Task LoadAsync_FetchesAndCachesMetadata()
    {
        var handler = TestHttpMessageHandler.RespondWith(_ =>
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(SampleDiscovery, Encoding.UTF8, "application/json"),
            };
            response.Headers.ETag = new EntityTagHeaderValue("\"abc\"");
            return response;
        });

        var client = new HttpClient(handler)
        {
            BaseAddress = new Uri("https://vexhub.suse.com"),
        };

        var factory = new SingleClientHttpClientFactory(client);
        var cache = new MemoryCache(new MemoryCacheOptions());
        var fileSystem = new MockFileSystem();
        var offlinePath = fileSystem.Path.Combine(@"C:\offline", "rancher-hub.json");
        var options = new RancherHubConnectorOptions
        {
            DiscoveryUri = new Uri("https://vexhub.suse.com/.well-known/vex/rancher-hub.json"),
            OfflineSnapshotPath = offlinePath,
        };

        var tokenProvider = new RancherHubTokenProvider(factory, cache, NullLogger<RancherHubTokenProvider>.Instance);
        var loader = new RancherHubMetadataLoader(factory, cache, tokenProvider, fileSystem, NullLogger<RancherHubMetadataLoader>.Instance);

        var result = await loader.LoadAsync(options, CancellationToken.None);

        result.FromCache.Should().BeFalse();
        result.FromOfflineSnapshot.Should().BeFalse();
        result.Metadata.Provider.DisplayName.Should().Be("SUSE Rancher VEX Hub");
        result.Metadata.Subscription.EventsUri.Should().Be(new Uri("https://vexhub.suse.com/api/v1/events"));
        result.Metadata.Authentication.TokenEndpoint.Should().Be(new Uri("https://identity.suse.com/oauth2/token"));

        // Second call should be served from cache (no additional HTTP invocation).
        handler.ResetInvocationCount();
        await loader.LoadAsync(options, CancellationToken.None);
        handler.InvocationCount.Should().Be(0);
    }

    [Fact]
    public async Task LoadAsync_UsesOfflineSnapshotWhenNetworkFails()
    {
        var handler = TestHttpMessageHandler.RespondWith(_ => throw new HttpRequestException("network down"));
        var client = new HttpClient(handler)
        {
            BaseAddress = new Uri("https://vexhub.suse.com"),
        };

        var factory = new SingleClientHttpClientFactory(client);
        var cache = new MemoryCache(new MemoryCacheOptions());
        var fileSystem = new MockFileSystem();
        var offlinePath = fileSystem.Path.Combine(@"C:\offline", "rancher-hub.json");
        fileSystem.AddFile(offlinePath, new MockFileData(SampleDiscovery));

        var options = new RancherHubConnectorOptions
        {
            DiscoveryUri = new Uri("https://vexhub.suse.com/.well-known/vex/rancher-hub.json"),
            OfflineSnapshotPath = offlinePath,
        };

        var tokenProvider = new RancherHubTokenProvider(factory, cache, NullLogger<RancherHubTokenProvider>.Instance);
        var loader = new RancherHubMetadataLoader(factory, cache, tokenProvider, fileSystem, NullLogger<RancherHubMetadataLoader>.Instance);

        var result = await loader.LoadAsync(options, CancellationToken.None);
        result.FromOfflineSnapshot.Should().BeTrue();
        result.Metadata.Subscription.RequiresAuthentication.Should().BeTrue();
        result.Metadata.OfflineSnapshot.Should().NotBeNull();
    }

    [Fact]
    public async Task LoadAsync_ThrowsWhenOfflinePreferredButMissing()
    {
        var handler = TestHttpMessageHandler.RespondWith(_ => throw new HttpRequestException("network down"));
        var client = new HttpClient(handler)
        {
            BaseAddress = new Uri("https://vexhub.suse.com"),
        };

        var factory = new SingleClientHttpClientFactory(client);
        var cache = new MemoryCache(new MemoryCacheOptions());
        var fileSystem = new MockFileSystem();
        var options = new RancherHubConnectorOptions
        {
            DiscoveryUri = new Uri("https://vexhub.suse.com/.well-known/vex/rancher-hub.json"),
            OfflineSnapshotPath = "/offline/missing.json",
            PreferOfflineSnapshot = true,
        };

        var tokenProvider = new RancherHubTokenProvider(factory, cache, NullLogger<RancherHubTokenProvider>.Instance);
        var loader = new RancherHubMetadataLoader(factory, cache, tokenProvider, fileSystem, NullLogger<RancherHubMetadataLoader>.Instance);

        await Assert.ThrowsAsync<InvalidOperationException>(() => loader.LoadAsync(options, CancellationToken.None));
    }

    private sealed class SingleClientHttpClientFactory : IHttpClientFactory
    {
        private readonly HttpClient _client;

        public SingleClientHttpClientFactory(HttpClient client)
        {
            _client = client;
        }

        public HttpClient CreateClient(string name) => _client;
    }

    private sealed class TestHttpMessageHandler : HttpMessageHandler
    {
        private readonly Func<HttpRequestMessage, HttpResponseMessage> _responseFactory;

        private TestHttpMessageHandler(Func<HttpRequestMessage, HttpResponseMessage> responseFactory)
        {
            _responseFactory = responseFactory;
        }

        public int InvocationCount { get; private set; }

        public static TestHttpMessageHandler RespondWith(Func<HttpRequestMessage, HttpResponseMessage> responseFactory)
            => new(responseFactory);

        public void ResetInvocationCount() => InvocationCount = 0;

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            InvocationCount++;
            return Task.FromResult(_responseFactory(request));
        }
    }
}
