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

namespace StellaOps.Feedser.Source.CertIn.Internal;

public sealed class CertInClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly CertInOptions _options;
    private readonly ILogger<CertInClient> _logger;

    public CertInClient(IHttpClientFactory httpClientFactory, IOptions<CertInOptions> options, ILogger<CertInClient> 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<CertInListingItem>> GetListingsAsync(int page, CancellationToken cancellationToken)
    {
        var client = _httpClientFactory.CreateClient(CertInOptions.HttpClientName);
        var requestUri = BuildPageUri(_options.AlertsEndpoint, page);

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

        await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);

        var root = document.RootElement;
        if (root.ValueKind != JsonValueKind.Array)
        {
            _logger.LogWarning("Unexpected CERT-In alert payload shape for {Uri}", requestUri);
            return Array.Empty<CertInListingItem>();
        }

        var items = new List<CertInListingItem>(capacity: root.GetArrayLength());
        foreach (var element in root.EnumerateArray())
        {
            if (!TryParseListing(element, out var item))
            {
                continue;
            }

            items.Add(item);
        }

        return items;
    }

    private static bool TryParseListing(JsonElement element, out CertInListingItem item)
    {
        item = null!;

        if (!element.TryGetProperty("advisoryId", out var idElement) || idElement.ValueKind != JsonValueKind.String)
        {
            return false;
        }

        var advisoryId = idElement.GetString();
        if (string.IsNullOrWhiteSpace(advisoryId))
        {
            return false;
        }

        var title = element.TryGetProperty("title", out var titleElement) && titleElement.ValueKind == JsonValueKind.String
            ? titleElement.GetString()
            : advisoryId;

        if (!element.TryGetProperty("detailUrl", out var linkElement) || linkElement.ValueKind != JsonValueKind.String)
        {
            return false;
        }

        if (!Uri.TryCreate(linkElement.GetString(), UriKind.Absolute, out var detailUri))
        {
            return false;
        }

        DateTimeOffset published;
        if (element.TryGetProperty("publishedOn", out var publishedElement) && publishedElement.ValueKind == JsonValueKind.String)
        {
            if (!DateTimeOffset.TryParse(publishedElement.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out published))
            {
                return false;
            }
        }
        else
        {
            return false;
        }

        string? summary = null;
        if (element.TryGetProperty("summary", out var summaryElement) && summaryElement.ValueKind == JsonValueKind.String)
        {
            summary = summaryElement.GetString();
        }

        item = new CertInListingItem(advisoryId.Trim(), title?.Trim() ?? advisoryId.Trim(), detailUri, published.ToUniversalTime(), summary?.Trim());
        return true;
    }

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

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