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

namespace StellaOps.Feedser.Source.Ics.Kaspersky.Internal;

public sealed class KasperskyFeedClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly KasperskyOptions _options;
    private readonly ILogger<KasperskyFeedClient> _logger;

    private static readonly XNamespace ContentNamespace = "http://purl.org/rss/1.0/modules/content/";

    public KasperskyFeedClient(IHttpClientFactory httpClientFactory, IOptions<KasperskyOptions> options, ILogger<KasperskyFeedClient> 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<KasperskyFeedItem>> GetItemsAsync(int page, CancellationToken cancellationToken)
    {
        var client = _httpClientFactory.CreateClient(KasperskyOptions.HttpClientName);
        var feedUri = BuildUri(_options.FeedUri, page);

        using var response = await client.GetAsync(feedUri, cancellationToken).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();

        await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        using var reader = new StreamReader(stream, Encoding.UTF8);
        var xml = await reader.ReadToEndAsync().ConfigureAwait(false);

        var document = XDocument.Parse(xml, LoadOptions.None);
        var items = new List<KasperskyFeedItem>();
        var channel = document.Root?.Element("channel");
        if (channel is null)
        {
            _logger.LogWarning("Feed {FeedUri} is missing channel element", feedUri);
            return items;
        }

        foreach (var item in channel.Elements("item"))
        {
            var title = item.Element("title")?.Value?.Trim();
            var linkValue = item.Element("link")?.Value?.Trim();
            var pubDateValue = item.Element("pubDate")?.Value?.Trim();
            var summary = item.Element("description")?.Value?.Trim();

            if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(linkValue) || string.IsNullOrWhiteSpace(pubDateValue))
            {
                continue;
            }

            if (!Uri.TryCreate(linkValue, UriKind.Absolute, out var link))
            {
                _logger.LogWarning("Skipping feed item with invalid link: {Link}", linkValue);
                continue;
            }

            if (!DateTimeOffset.TryParse(pubDateValue, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var published))
            {
                _logger.LogWarning("Skipping feed item {Title} due to invalid pubDate {PubDate}", title, pubDateValue);
                continue;
            }

            var encoded = item.Element(ContentNamespace + "encoded")?.Value;
            if (!string.IsNullOrWhiteSpace(encoded))
            {
                summary ??= HtmlToPlainText(encoded);
            }

            items.Add(new KasperskyFeedItem(title, Canonicalize(link), published.ToUniversalTime(), summary));
        }

        return items;
    }

    private static Uri BuildUri(Uri baseUri, int page)
    {
        if (page <= 1)
        {
            return baseUri;
        }

        var builder = new UriBuilder(baseUri);
        var trimmed = builder.Query.TrimStart('?');
        var pageSegment = $"paged={page.ToString(CultureInfo.InvariantCulture)}";
        builder.Query = string.IsNullOrEmpty(trimmed)
            ? pageSegment
            : $"{trimmed}&{pageSegment}";
        return builder.Uri;
    }

    private static Uri Canonicalize(Uri link)
    {
        if (string.IsNullOrEmpty(link.Query))
        {
            return link;
        }

        var builder = new UriBuilder(link)
        {
            Query = string.Empty,
        };
        return builder.Uri;
    }

    private static string? HtmlToPlainText(string html)
    {
        if (string.IsNullOrWhiteSpace(html))
        {
            return null;
        }

        var withoutScripts = System.Text.RegularExpressions.Regex.Replace(html, "<script[\\s\\S]*?</script>", string.Empty, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        var withoutStyles = System.Text.RegularExpressions.Regex.Replace(withoutScripts, "<style[\\s\\S]*?</style>", string.Empty, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        var withoutTags = System.Text.RegularExpressions.Regex.Replace(withoutStyles, "<[^>]+>", " ");
        var decoded = System.Net.WebUtility.HtmlDecode(withoutTags);
        return string.IsNullOrWhiteSpace(decoded) ? null : System.Text.RegularExpressions.Regex.Replace(decoded, "\\s+", " ").Trim();
    }
}
