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

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

public sealed class MsrcApiClient
{
    private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true,
        WriteIndented = false,
    };

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IMsrcTokenProvider _tokenProvider;
    private readonly MsrcOptions _options;
    private readonly ILogger<MsrcApiClient> _logger;

    public MsrcApiClient(
        IHttpClientFactory httpClientFactory,
        IMsrcTokenProvider tokenProvider,
        IOptions<MsrcOptions> options,
        ILogger<MsrcApiClient> logger)
    {
        _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
        _tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider));
        _options = (options ?? throw new ArgumentNullException(nameof(options))).Value ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<IReadOnlyList<MsrcVulnerabilitySummary>> FetchSummariesAsync(DateTimeOffset fromInclusive, DateTimeOffset toExclusive, CancellationToken cancellationToken)
    {
        var client = await CreateAuthenticatedClientAsync(cancellationToken).ConfigureAwait(false);

        var results = new List<MsrcVulnerabilitySummary>();
        var requestUri = BuildSummaryUri(fromInclusive, toExclusive);

        while (requestUri is not null)
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
            using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var preview = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
                throw new HttpRequestException($"MSRC summary fetch failed with {(int)response.StatusCode}. Body: {preview}");
            }

            var payload = await response.Content.ReadFromJsonAsync<MsrcSummaryResponse>(SerializerOptions, cancellationToken).ConfigureAwait(false)
                ?? new MsrcSummaryResponse();

            results.AddRange(payload.Value);

            if (string.IsNullOrWhiteSpace(payload.NextLink))
            {
                break;
            }

            requestUri = new Uri(payload.NextLink, UriKind.Absolute);
        }

        return results;
    }

    public Uri BuildDetailUri(string vulnerabilityId)
    {
        var uri = CreateDetailUriInternal(vulnerabilityId);
        return uri;
    }

    public async Task<byte[]> FetchDetailAsync(string vulnerabilityId, CancellationToken cancellationToken)
    {
        var client = await CreateAuthenticatedClientAsync(cancellationToken).ConfigureAwait(false);
        var uri = CreateDetailUriInternal(vulnerabilityId);

        using var request = new HttpRequestMessage(HttpMethod.Get, uri);
        using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);

        if (!response.IsSuccessStatusCode)
        {
            var preview = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            throw new HttpRequestException($"MSRC detail fetch failed for {vulnerabilityId} with {(int)response.StatusCode}. Body: {preview}");
        }

        return await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
    }

    private async Task<HttpClient> CreateAuthenticatedClientAsync(CancellationToken cancellationToken)
    {
        var token = await _tokenProvider.GetAccessTokenAsync(cancellationToken).ConfigureAwait(false);
        var client = _httpClientFactory.CreateClient(MsrcOptions.HttpClientName);
        client.DefaultRequestHeaders.Remove("Authorization");
        client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
        client.DefaultRequestHeaders.Remove("Accept");
        client.DefaultRequestHeaders.Add("Accept", "application/json");
        client.DefaultRequestHeaders.Remove("api-version");
        client.DefaultRequestHeaders.Add("api-version", _options.ApiVersion);
        client.DefaultRequestHeaders.Remove("Accept-Language");
        client.DefaultRequestHeaders.Add("Accept-Language", _options.Locale);
        return client;
    }

    private Uri BuildSummaryUri(DateTimeOffset fromInclusive, DateTimeOffset toExclusive)
    {
        var builder = new StringBuilder();
        builder.Append(_options.BaseUri.ToString().TrimEnd('/'));
        builder.Append("/vulnerabilities?");
        builder.Append("$top=").Append(_options.PageSize);
        builder.Append("&lastModifiedStartDateTime=").Append(Uri.EscapeDataString(fromInclusive.ToUniversalTime().ToString("O")));
        builder.Append("&lastModifiedEndDateTime=").Append(Uri.EscapeDataString(toExclusive.ToUniversalTime().ToString("O")));
        builder.Append("&$orderby=lastModifiedDate");
        builder.Append("&locale=").Append(Uri.EscapeDataString(_options.Locale));
        builder.Append("&api-version=").Append(Uri.EscapeDataString(_options.ApiVersion));

        return new Uri(builder.ToString(), UriKind.Absolute);
    }

    private Uri CreateDetailUriInternal(string vulnerabilityId)
    {
        if (string.IsNullOrWhiteSpace(vulnerabilityId))
        {
            throw new ArgumentException("Vulnerability identifier must be provided.", nameof(vulnerabilityId));
        }

        var baseUri = _options.BaseUri.ToString().TrimEnd('/');
        var path = $"{baseUri}/vulnerability/{Uri.EscapeDataString(vulnerabilityId)}?api-version={Uri.EscapeDataString(_options.ApiVersion)}&locale={Uri.EscapeDataString(_options.Locale)}";
        return new Uri(path, UriKind.Absolute);
    }
}
