using System.Collections.Generic;
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 Microsoft.Extensions.DependencyInjection;
using StellaOps.Excititor.Connectors.Abstractions;
using StellaOps.Excititor.Connectors.Cisco.CSAF;
using StellaOps.Excititor.Connectors.Cisco.CSAF.Configuration;
using StellaOps.Excititor.Connectors.Cisco.CSAF.Metadata;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Storage.Mongo;
using System.Collections.Immutable;
using System.IO.Abstractions.TestingHelpers;
using Xunit;
using System.Threading;

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

public sealed class CiscoCsafConnectorTests
{
    [Fact]
    public async Task FetchAsync_NewAdvisory_StoresDocumentAndUpdatesState()
    {
        var responses = new Dictionary<Uri, Queue<HttpResponseMessage>>
        {
            [new Uri("https://api.cisco.test/.well-known/csaf/provider-metadata.json")] = QueueResponses("""
                {
                  "metadata": {
                    "publisher": {
                      "name": "Cisco",
                      "category": "vendor",
                      "contact_details": { "id": "excititor:cisco" }
                    }
                  },
                  "distributions": {
                    "directories": [ "https://api.cisco.test/csaf/" ]
                  }
                }
                """),
            [new Uri("https://api.cisco.test/csaf/index.json")] = QueueResponses("""
                {
                  "advisories": [
                    {
                      "id": "cisco-sa-2025",
                      "url": "https://api.cisco.test/csaf/cisco-sa-2025.json",
                      "published": "2025-10-01T00:00:00Z",
                      "lastModified": "2025-10-02T00:00:00Z",
                      "sha256": "cafebabe"
                    }
                  ]
                }
                """),
            [new Uri("https://api.cisco.test/csaf/cisco-sa-2025.json")] = QueueResponses("{ \"document\": \"payload\" }")
        };

        var handler = new RoutingHttpMessageHandler(responses);
        var httpClient = new HttpClient(handler);
        var factory = new SingleHttpClientFactory(httpClient);
        var metadataLoader = new CiscoProviderMetadataLoader(
            factory,
            new MemoryCache(new MemoryCacheOptions()),
            Options.Create(new CiscoConnectorOptions
            {
                MetadataUri = "https://api.cisco.test/.well-known/csaf/provider-metadata.json",
                PersistOfflineSnapshot = false,
            }),
            NullLogger<CiscoProviderMetadataLoader>.Instance,
            new MockFileSystem());

        var stateRepository = new InMemoryConnectorStateRepository();
        var connector = new CiscoCsafConnector(
            metadataLoader,
            factory,
            stateRepository,
            new[] { new CiscoConnectorOptionsValidator() },
            NullLogger<CiscoCsafConnector>.Instance,
            TimeProvider.System);

        var settings = new VexConnectorSettings(ImmutableDictionary<string, string>.Empty);
        await connector.ValidateAsync(settings, CancellationToken.None);

        var sink = new InMemoryRawSink();
        var context = new VexConnectorContext(null, VexConnectorSettings.Empty, sink, new NoopSignatureVerifier(), new NoopNormalizerRouter(), new ServiceCollection().BuildServiceProvider());

        var documents = new List<VexRawDocument>();
        await foreach (var doc in connector.FetchAsync(context, CancellationToken.None))
        {
            documents.Add(doc);
        }

        documents.Should().HaveCount(1);
        sink.Documents.Should().HaveCount(1);
        stateRepository.CurrentState.Should().NotBeNull();
        stateRepository.CurrentState!.DocumentDigests.Should().HaveCount(1);

        // second run should not refetch documents
        sink.Documents.Clear();
        documents.Clear();

        await foreach (var doc in connector.FetchAsync(context, CancellationToken.None))
        {
            documents.Add(doc);
        }

        documents.Should().BeEmpty();
        sink.Documents.Should().BeEmpty();
    }

    private static Queue<HttpResponseMessage> QueueResponses(string payload)
        => new(new[]
        {
            new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(payload, Encoding.UTF8, "application/json"),
            }
        });

    private sealed class RoutingHttpMessageHandler : HttpMessageHandler
    {
        private readonly Dictionary<Uri, Queue<HttpResponseMessage>> _responses;

        public RoutingHttpMessageHandler(Dictionary<Uri, Queue<HttpResponseMessage>> responses)
        {
            _responses = responses;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.RequestUri is not null && _responses.TryGetValue(request.RequestUri, out var queue) && queue.Count > 0)
            {
                var response = queue.Peek();
                return Task.FromResult(response.Clone());
            }

            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)
            {
                Content = new StringContent($"No response configured for {request.RequestUri}"),
            });
        }
    }

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

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

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

    private sealed class InMemoryConnectorStateRepository : IVexConnectorStateRepository
    {
        public VexConnectorState? CurrentState { get; private set; }

        public ValueTask<VexConnectorState?> GetAsync(string connectorId, CancellationToken cancellationToken)
            => ValueTask.FromResult(CurrentState);

        public ValueTask SaveAsync(VexConnectorState state, CancellationToken cancellationToken)
        {
            CurrentState = state;
            return ValueTask.CompletedTask;
        }
    }

    private sealed class InMemoryRawSink : IVexRawDocumentSink
    {
        public List<VexRawDocument> Documents { get; } = new();

        public ValueTask StoreAsync(VexRawDocument document, CancellationToken cancellationToken)
        {
            Documents.Add(document);
            return ValueTask.CompletedTask;
        }
    }

    private sealed class NoopSignatureVerifier : IVexSignatureVerifier
    {
        public ValueTask<VexSignatureMetadata?> VerifyAsync(VexRawDocument document, CancellationToken cancellationToken)
            => ValueTask.FromResult<VexSignatureMetadata?>(null);
    }

    private sealed class NoopNormalizerRouter : IVexNormalizerRouter
    {
        public ValueTask<VexClaimBatch> NormalizeAsync(VexRawDocument document, CancellationToken cancellationToken)
            => ValueTask.FromResult(new VexClaimBatch(document, ImmutableArray<VexClaim>.Empty, ImmutableDictionary<string, string>.Empty));
    }
}

internal static class HttpResponseMessageExtensions
{
    public static HttpResponseMessage Clone(this HttpResponseMessage response)
    {
        var clone = new HttpResponseMessage(response.StatusCode);
        foreach (var header in response.Headers)
        {
            clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        if (response.Content is not null)
        {
            var payload = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
            clone.Content = new StringContent(payload, Encoding.UTF8, response.Content.Headers.ContentType?.MediaType);
        }

        return clone;
    }
}
