using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Feedser.Source.CertBund.Configuration;

namespace StellaOps.Feedser.Source.CertBund.Internal;

public sealed class CertBundFeedClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly CertBundOptions _options;
    private readonly ILogger<CertBundFeedClient> _logger;
    private readonly SemaphoreSlim _bootstrapSemaphore = new(1, 1);
    private volatile bool _bootstrapped;

    public CertBundFeedClient(
        IHttpClientFactory httpClientFactory,
        IOptions<CertBundOptions> options,
        ILogger<CertBundFeedClient> logger)
    {
        _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
        _options = (options ?? throw new ArgumentNullException(nameof(options))).Value ?? throw new ArgumentNullException(nameof(options));
        _options.Validate();
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<IReadOnlyList<CertBundFeedItem>> LoadAsync(CancellationToken cancellationToken)
    {
        var client = _httpClientFactory.CreateClient(CertBundOptions.HttpClientName);
        await EnsureSessionAsync(client, cancellationToken).ConfigureAwait(false);

        using var request = new HttpRequestMessage(HttpMethod.Get, _options.FeedUri);
        request.Headers.TryAddWithoutValidation("Accept", "application/rss+xml, application/xml;q=0.9, text/xml;q=0.8");
        using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();

        await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        var document = XDocument.Load(stream);

        var items = new List<CertBundFeedItem>();
        foreach (var element in document.Descendants("item"))
        {
            cancellationToken.ThrowIfCancellationRequested();

            var linkValue = element.Element("link")?.Value?.Trim();
            if (string.IsNullOrWhiteSpace(linkValue) || !Uri.TryCreate(linkValue, UriKind.Absolute, out var portalUri))
            {
                continue;
            }

            var advisoryId = TryExtractNameParameter(portalUri);
            if (string.IsNullOrWhiteSpace(advisoryId))
            {
                continue;
            }

            var detailUri = _options.BuildDetailUri(advisoryId);
            var pubDateText = element.Element("pubDate")?.Value;
            var published = ParseDate(pubDateText);
            var title = element.Element("title")?.Value?.Trim();
            var category = element.Element("category")?.Value?.Trim();

            items.Add(new CertBundFeedItem(advisoryId, detailUri, portalUri, published, title, category));
        }

        return items;
    }

    private async Task EnsureSessionAsync(HttpClient client, CancellationToken cancellationToken)
    {
        if (_bootstrapped)
        {
            return;
        }

        await _bootstrapSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            if (_bootstrapped)
            {
                return;
            }

            using var request = new HttpRequestMessage(HttpMethod.Get, _options.PortalBootstrapUri);
            request.Headers.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
            using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            response.EnsureSuccessStatusCode();

            _bootstrapped = true;
        }
        finally
        {
            _bootstrapSemaphore.Release();
        }
    }

    private static string? TryExtractNameParameter(Uri portalUri)
    {
        if (portalUri is null)
        {
            return null;
        }

        var query = portalUri.Query;
        if (string.IsNullOrEmpty(query))
        {
            return null;
        }

        var trimmed = query.TrimStart('?');
        foreach (var pair in trimmed.Split('&', StringSplitOptions.RemoveEmptyEntries))
        {
            var separatorIndex = pair.IndexOf('=');
            if (separatorIndex <= 0)
            {
                continue;
            }

            var key = pair[..separatorIndex].Trim();
            if (!key.Equals("name", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var value = pair[(separatorIndex + 1)..];
            return Uri.UnescapeDataString(value);
        }

        return null;
    }

    private static DateTimeOffset ParseDate(string? value)
        => DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var parsed)
            ? parsed
            : DateTimeOffset.UtcNow;
}
