using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Feedser.Source.Cccs.Configuration;
using StellaOps.Feedser.Source.Common.Fetch;

namespace StellaOps.Feedser.Source.Cccs.Internal;

public sealed class CccsFeedClient
{
    private static readonly string[] AcceptHeaders =
    {
        "application/json",
        "application/vnd.api+json;q=0.9",
        "text/json;q=0.8",
        "application/*+json;q=0.7",
    };

    private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    };

    private readonly SourceFetchService _fetchService;
    private readonly ILogger<CccsFeedClient> _logger;

    public CccsFeedClient(SourceFetchService fetchService, ILogger<CccsFeedClient> logger)
    {
        _fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    internal async Task<CccsFeedResult> FetchAsync(CccsFeedEndpoint endpoint, TimeSpan requestTimeout, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(endpoint);
        if (endpoint.Uri is null)
        {
            throw new InvalidOperationException("Feed endpoint URI must be configured.");
        }

        var request = new SourceFetchRequest(CccsOptions.HttpClientName, CccsConnectorPlugin.SourceName, endpoint.Uri)
        {
            AcceptHeaders = AcceptHeaders,
            TimeoutOverride = requestTimeout,
            Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
            {
                ["cccs.language"] = endpoint.Language,
                ["cccs.feedUri"] = endpoint.Uri.ToString(),
            },
        };

        try
        {
            var result = await _fetchService.FetchContentAsync(request, cancellationToken).ConfigureAwait(false);

            if (!result.IsSuccess || result.Content is null)
            {
                _logger.LogWarning("CCCS feed fetch returned no content for {Uri} (status={Status})", endpoint.Uri, result.StatusCode);
                return CccsFeedResult.Empty;
            }

            var feedResponse = Deserialize<CccsFeedResponse>(result.Content);
            if (feedResponse is null || feedResponse.Error)
            {
                _logger.LogWarning("CCCS feed response flagged an error for {Uri}", endpoint.Uri);
                return CccsFeedResult.Empty;
            }

            var taxonomy = await FetchTaxonomyAsync(endpoint, requestTimeout, cancellationToken).ConfigureAwait(false);
            var items = (IReadOnlyList<CccsFeedItem>)feedResponse.Response ?? Array.Empty<CccsFeedItem>();
            return new CccsFeedResult(items, taxonomy, result.LastModified);
        }
        catch (Exception ex) when (ex is JsonException or InvalidOperationException)
        {
            _logger.LogError(ex, "CCCS feed deserialization failed for {Uri}", endpoint.Uri);
            throw;
        }
        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
        {
            _logger.LogWarning(ex, "CCCS feed fetch failed for {Uri}", endpoint.Uri);
            throw;
        }
    }

    private async Task<IReadOnlyDictionary<int, string>> FetchTaxonomyAsync(CccsFeedEndpoint endpoint, TimeSpan timeout, CancellationToken cancellationToken)
    {
        var taxonomyUri = endpoint.BuildTaxonomyUri();
        var request = new SourceFetchRequest(CccsOptions.HttpClientName, CccsConnectorPlugin.SourceName, taxonomyUri)
        {
            AcceptHeaders = AcceptHeaders,
            TimeoutOverride = timeout,
            Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
            {
                ["cccs.language"] = endpoint.Language,
                ["cccs.taxonomyUri"] = taxonomyUri.ToString(),
            },
        };

        try
        {
            var result = await _fetchService.FetchContentAsync(request, cancellationToken).ConfigureAwait(false);
            if (!result.IsSuccess || result.Content is null)
            {
                _logger.LogDebug("CCCS taxonomy fetch returned no content for {Uri}", taxonomyUri);
                return new Dictionary<int, string>(0);
            }

            var taxonomyResponse = Deserialize<CccsTaxonomyResponse>(result.Content);
            if (taxonomyResponse is null || taxonomyResponse.Error)
            {
                _logger.LogDebug("CCCS taxonomy response indicated error for {Uri}", taxonomyUri);
                return new Dictionary<int, string>(0);
            }

            var map = new Dictionary<int, string>(taxonomyResponse.Response.Count);
            foreach (var item in taxonomyResponse.Response)
            {
                if (!string.IsNullOrWhiteSpace(item.Title))
                {
                    map[item.Id] = item.Title!;
                }
            }

            return map;
        }
        catch (Exception ex) when (ex is JsonException or InvalidOperationException)
        {
            _logger.LogWarning(ex, "Failed to deserialize CCCS taxonomy for {Uri}", taxonomyUri);
            return new Dictionary<int, string>(0);
        }
        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
        {
            _logger.LogWarning(ex, "CCCS taxonomy fetch failed for {Uri}", taxonomyUri);
            return new Dictionary<int, string>(0);
        }
    }

    private static T? Deserialize<T>(byte[] content)
        => JsonSerializer.Deserialize<T>(content, SerializerOptions);
}
