using System.Net;
using System.Net.Http;
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Excititor.Connectors.Cisco.CSAF.Configuration;
using StellaOps.Excititor.Connectors.Cisco.CSAF.Metadata;
using StellaOps.Excititor.Core;
using System.IO.Abstractions.TestingHelpers;

namespace StellaOps.Excititor.Connectors.Cisco.CSAF.Tests.Metadata;

public sealed class CiscoProviderMetadataLoaderTests
{
    [Fact]
    public async Task LoadAsync_FetchesFromNetworkWithBearerToken()
    {
        var payload = """
        {
          "metadata": {
            "publisher": {
              "name": "Cisco CSAF",
              "category": "vendor",
              "contact_details": {
                "id": "excititor:cisco"
              }
            }
          },
          "distributions": {
            "directories": [
              "https://api.security.cisco.com/csaf/v2/advisories/"
            ]
          },
          "discovery": {
            "well_known": "https://api.security.cisco.com/.well-known/csaf/provider-metadata.json",
            "rolie": "https://api.security.cisco.com/csaf/rolie/feed"
          },
          "trust": {
            "weight": 0.9,
            "cosign": {
              "issuer": "https://oidc.security.cisco.com",
              "identity_pattern": "spiffe://cisco/*"
            },
            "pgp_fingerprints": [ "1234ABCD" ]
          }
        }
        """;

        HttpRequestMessage? capturedRequest = null;
        var handler = new FakeHttpMessageHandler(request =>
        {
            capturedRequest = request;
            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(payload, Encoding.UTF8, "application/json"),
                Headers = { ETag = new System.Net.Http.Headers.EntityTagHeaderValue("\"etag1\"") }
            };
        });

        var httpClient = new HttpClient(handler);
        var factory = new SingleHttpClientFactory(httpClient);
        var cache = new MemoryCache(new MemoryCacheOptions());
        var options = Options.Create(new CiscoConnectorOptions
        {
            ApiToken = "token-123",
            MetadataUri = "https://api.security.cisco.com/.well-known/csaf/provider-metadata.json",
        });
        var fileSystem = new MockFileSystem();
        var loader = new CiscoProviderMetadataLoader(factory, cache, options, NullLogger<CiscoProviderMetadataLoader>.Instance, fileSystem);

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

        result.Provider.Id.Should().Be("excititor:cisco");
        result.Provider.BaseUris.Should().ContainSingle(uri => uri.ToString() == "https://api.security.cisco.com/csaf/v2/advisories/");
        result.Provider.Discovery.RolIeService.Should().Be(new Uri("https://api.security.cisco.com/csaf/rolie/feed"));
        result.ServedFromCache.Should().BeFalse();
        capturedRequest.Should().NotBeNull();
        capturedRequest!.Headers.Authorization.Should().NotBeNull();
        capturedRequest.Headers.Authorization!.Parameter.Should().Be("token-123");
    }

    [Fact]
    public async Task LoadAsync_FallsBackToOfflineSnapshot()
    {
        var payload = """
        {
          "metadata": {
            "publisher": {
              "name": "Cisco CSAF",
              "category": "vendor",
              "contact_details": {
                "id": "excititor:cisco"
              }
            }
          }
        }
        """;

        var handler = new FakeHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError));
        var httpClient = new HttpClient(handler);
        var factory = new SingleHttpClientFactory(httpClient);
        var cache = new MemoryCache(new MemoryCacheOptions());
        var options = Options.Create(new CiscoConnectorOptions
        {
            MetadataUri = "https://api.security.cisco.com/.well-known/csaf/provider-metadata.json",
            PreferOfflineSnapshot = true,
            OfflineSnapshotPath = "/snapshots/cisco.json",
        });
        var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
        {
            ["/snapshots/cisco.json"] = new MockFileData(payload),
        });
        var loader = new CiscoProviderMetadataLoader(factory, cache, options, NullLogger<CiscoProviderMetadataLoader>.Instance, fileSystem);

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

        result.FromOfflineSnapshot.Should().BeTrue();
        result.Provider.Id.Should().Be("excititor:cisco");
    }

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

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

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

    private sealed class FakeHttpMessageHandler : HttpMessageHandler
    {
        private readonly Func<HttpRequestMessage, HttpResponseMessage> _responder;

        public FakeHttpMessageHandler(Func<HttpRequestMessage, HttpResponseMessage> responder)
        {
            _responder = responder;
        }

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