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.Kisa.Configuration;

namespace StellaOps.Feedser.Source.Kisa.Internal;

public sealed class KisaFeedClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly KisaOptions _options;
    private readonly ILogger<KisaFeedClient> _logger;

    public KisaFeedClient(
        IHttpClientFactory httpClientFactory,
        IOptions<KisaOptions> options,
        ILogger<KisaFeedClient> 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<KisaFeedItem>> LoadAsync(CancellationToken cancellationToken)
    {
        var client = _httpClientFactory.CreateClient(KisaOptions.HttpClientName);

        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<KisaFeedItem>();
        foreach (var element in document.Descendants("item"))
        {
            cancellationToken.ThrowIfCancellationRequested();

            var link = element.Element("link")?.Value?.Trim();
            if (string.IsNullOrWhiteSpace(link))
            {
                continue;
            }

            if (!TryExtractIdx(link, out var idx))
            {
                continue;
            }

            var title = element.Element("title")?.Value?.Trim();
            var category = element.Element("category")?.Value?.Trim();
            var published = ParseDate(element.Element("pubDate")?.Value);
            var detailApiUri = _options.BuildDetailApiUri(idx);
            var detailPageUri = _options.BuildDetailPageUri(idx);

            items.Add(new KisaFeedItem(idx, detailApiUri, detailPageUri, published, title, category));
        }

        return items;
    }

    private static bool TryExtractIdx(string link, out string idx)
    {
        idx = string.Empty;
        if (string.IsNullOrWhiteSpace(link))
        {
            return false;
        }

        if (!Uri.TryCreate(link, UriKind.Absolute, out var uri))
        {
            return false;
        }

        var query = uri.Query?.TrimStart('?');
        if (string.IsNullOrEmpty(query))
        {
            return false;
        }

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

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

            idx = Uri.UnescapeDataString(pair[(separatorIndex + 1)..]);
            return !string.IsNullOrWhiteSpace(idx);
        }

        return false;
    }

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