using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Feedser.Source.Common.Fetch;
using StellaOps.Feedser.Source.Vndr.Cisco.Configuration;

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

public sealed class CiscoOpenVulnClient
{
    private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true,
        NumberHandling = JsonNumberHandling.AllowReadingFromString,
    };

    private readonly SourceFetchService _fetchService;
    private readonly IOptionsMonitor<CiscoOptions> _options;
    private readonly ILogger<CiscoOpenVulnClient> _logger;
    private readonly string _sourceName;

    public CiscoOpenVulnClient(
        SourceFetchService fetchService,
        IOptionsMonitor<CiscoOptions> options,
        ILogger<CiscoOpenVulnClient> logger,
        string sourceName)
    {
        _fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _sourceName = sourceName ?? throw new ArgumentNullException(nameof(sourceName));
    }

    internal async Task<CiscoAdvisoryPage?> FetchAsync(DateOnly date, int pageIndex, CancellationToken cancellationToken)
    {
        var options = _options.CurrentValue;
        var requestUri = options.BuildLastModifiedUri(date, pageIndex, options.PageSize);
        var request = new SourceFetchRequest(CiscoOptions.HttpClientName, _sourceName, requestUri)
        {
            AcceptHeaders = new[] { "application/json" },
            TimeoutOverride = options.RequestTimeout,
        };

        var result = await _fetchService.FetchContentAsync(request, cancellationToken).ConfigureAwait(false);
        if (!result.IsSuccess || result.Content is null)
        {
            _logger.LogDebug("Cisco openVuln request returned empty payload for {Uri} (status {Status})", requestUri, result.StatusCode);
            return null;
        }

        return CiscoAdvisoryPage.Parse(result.Content);
    }
}

internal sealed record CiscoAdvisoryPage(
    IReadOnlyList<CiscoAdvisoryItem> Advisories,
    CiscoPagination Pagination)
{
    public bool HasMore => Pagination.PageIndex < Pagination.TotalPages;

    public static CiscoAdvisoryPage Parse(byte[] content)
    {
        using var document = JsonDocument.Parse(content);
        var root = document.RootElement;
        var advisories = new List<CiscoAdvisoryItem>();

        if (root.TryGetProperty("advisories", out var advisoriesElement) && advisoriesElement.ValueKind == JsonValueKind.Array)
        {
            foreach (var advisory in advisoriesElement.EnumerateArray())
            {
                if (!TryCreateItem(advisory, out var item))
                {
                    continue;
                }

                advisories.Add(item);
            }
        }

        var pagination = CiscoPagination.FromJson(root.TryGetProperty("pagination", out var paginationElement) ? paginationElement : default);
        return new CiscoAdvisoryPage(advisories, pagination);
    }

    private static bool TryCreateItem(JsonElement advisory, [NotNullWhen(true)] out CiscoAdvisoryItem? item)
    {
        var rawJson = advisory.GetRawText();
        var advisoryId = GetString(advisory, "advisoryId");
        if (string.IsNullOrWhiteSpace(advisoryId))
        {
            item = null;
            return false;
        }

        var lastUpdated = ParseDate(GetString(advisory, "lastUpdated"));
        var firstPublished = ParseDate(GetString(advisory, "firstPublished"));
        var severity = GetString(advisory, "sir");
        var publicationUrl = GetString(advisory, "publicationUrl");
        var csafUrl = GetString(advisory, "csafUrl");
        var cvrfUrl = GetString(advisory, "cvrfUrl");
        var cvss = GetString(advisory, "cvssBaseScore");

        var cves = ReadStringArray(advisory, "cves");
        var bugIds = ReadStringArray(advisory, "bugIDs");
        var productNames = ReadStringArray(advisory, "productNames");

        item = new CiscoAdvisoryItem(
            advisoryId,
            lastUpdated,
            firstPublished,
            severity,
            publicationUrl,
            csafUrl,
            cvrfUrl,
            cvss,
            cves,
            bugIds,
            productNames,
            rawJson);
        return true;
    }

    private static string? GetString(JsonElement element, string propertyName)
        => element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String
            ? value.GetString()
            : null;

    private static DateTimeOffset? ParseDate(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        if (DateTimeOffset.TryParse(value, out var parsed))
        {
            return parsed.ToUniversalTime();
        }

        return null;
    }

    private static IReadOnlyList<string> ReadStringArray(JsonElement element, string property)
    {
        if (!element.TryGetProperty(property, out var value) || value.ValueKind != JsonValueKind.Array)
        {
            return Array.Empty<string>();
        }

        var results = new List<string>();
        foreach (var child in value.EnumerateArray())
        {
            if (child.ValueKind == JsonValueKind.String)
            {
                var text = child.GetString();
                if (!string.IsNullOrWhiteSpace(text))
                {
                    results.Add(text.Trim());
                }
            }
        }

        return results;
    }
}

internal sealed record CiscoAdvisoryItem(
    string AdvisoryId,
    DateTimeOffset? LastUpdated,
    DateTimeOffset? FirstPublished,
    string? Severity,
    string? PublicationUrl,
    string? CsafUrl,
    string? CvrfUrl,
    string? CvssBaseScore,
    IReadOnlyList<string> Cves,
    IReadOnlyList<string> BugIds,
    IReadOnlyList<string> ProductNames,
    string RawJson)
{
    public byte[] GetRawBytes() => Encoding.UTF8.GetBytes(RawJson);
}

internal sealed record CiscoPagination(int PageIndex, int PageSize, int TotalPages, int TotalRecords)
{
    public static CiscoPagination FromJson(JsonElement element)
    {
        var pageIndex = element.TryGetProperty("pageIndex", out var index) && index.TryGetInt32(out var parsedIndex) ? parsedIndex : 1;
        var pageSize = element.TryGetProperty("pageSize", out var size) && size.TryGetInt32(out var parsedSize) ? parsedSize : 0;
        var totalPages = element.TryGetProperty("totalPages", out var pages) && pages.TryGetInt32(out var parsedPages) ? parsedPages : pageIndex;
        var totalRecords = element.TryGetProperty("totalRecords", out var records) && records.TryGetInt32(out var parsedRecords) ? parsedRecords : 0;
        return new CiscoPagination(pageIndex, pageSize, totalPages, totalRecords);
    }
}
