using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Feedser.Source.Vndr.Oracle.Configuration;

namespace StellaOps.Feedser.Source.Vndr.Oracle.Internal;

public sealed class OracleCalendarFetcher
{
    private static readonly Regex AnchorRegex = new("<a[^>]+href=\"(?<url>[^\"]+)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly OracleOptions _options;
    private readonly ILogger<OracleCalendarFetcher> _logger;

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

    public async Task<IReadOnlyCollection<Uri>> GetAdvisoryUrisAsync(CancellationToken cancellationToken)
    {
        if (_options.CalendarUris.Count == 0)
        {
            return Array.Empty<Uri>();
        }

        var discovered = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var client = _httpClientFactory.CreateClient(OracleOptions.HttpClientName);

        foreach (var calendarUri in _options.CalendarUris)
        {
            try
            {
                var content = await client.GetStringAsync(calendarUri, cancellationToken).ConfigureAwait(false);
                foreach (var link in ExtractLinks(calendarUri, content))
                {
                    discovered.Add(link.AbsoluteUri);
                }
            }
            catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or OperationCanceledException)
            {
                _logger.LogWarning(ex, "Oracle calendar fetch failed for {Uri}", calendarUri);
            }
        }

        return discovered
            .Select(static uri => new Uri(uri, UriKind.Absolute))
            .OrderBy(static uri => uri.AbsoluteUri, StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private static IEnumerable<Uri> ExtractLinks(Uri baseUri, string html)
    {
        if (string.IsNullOrWhiteSpace(html))
        {
            yield break;
        }

        foreach (Match match in AnchorRegex.Matches(html))
        {
            if (!match.Success)
            {
                continue;
            }

            var href = match.Groups["url"].Value?.Trim();
            if (string.IsNullOrEmpty(href))
            {
                continue;
            }

            if (!Uri.TryCreate(baseUri, href, out var uri) || !uri.IsAbsoluteUri)
            {
                continue;
            }

            yield return uri;
        }
    }
}
