using System.Globalization;

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

public sealed class MsrcOptions
{
    public const string HttpClientName = "feedser.source.vndr.msrc";
    public const string TokenClientName = "feedser.source.vndr.msrc.token";

    public Uri BaseUri { get; set; } = new("https://api.msrc.microsoft.com/sug/v2.0/", UriKind.Absolute);

    public string Locale { get; set; } = "en-US";

    public string ApiVersion { get; set; } = "2024-08-01";

    /// <summary>
    /// Azure AD tenant identifier used for client credential flow.
    /// </summary>
    public string TenantId { get; set; } = string.Empty;

    /// <summary>
    /// Azure AD application (client) identifier.
    /// </summary>
    public string ClientId { get; set; } = string.Empty;

    /// <summary>
    /// Azure AD client secret used for token acquisition.
    /// </summary>
    public string ClientSecret { get; set; } = string.Empty;

    /// <summary>
    /// Scope requested during client-credential token acquisition.
    /// </summary>
    public string Scope { get; set; } = "api://api.msrc.microsoft.com/.default";

    /// <summary>
    /// Maximum advisories to fetch per cycle.
    /// </summary>
    public int MaxAdvisoriesPerFetch { get; set; } = 200;

    /// <summary>
    /// Page size used when iterating the MSRC API.
    /// </summary>
    public int PageSize { get; set; } = 100;

    /// <summary>
    /// Overlap window added when resuming from the last modified cursor.
    /// </summary>
    public TimeSpan CursorOverlap { get; set; } = TimeSpan.FromMinutes(10);

    /// <summary>
    /// When enabled the connector downloads the CVRF artefact referenced by each advisory.
    /// </summary>
    public bool DownloadCvrf { get; set; } = false;

    public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);

    public TimeSpan FailureBackoff { get; set; } = TimeSpan.FromMinutes(5);

    /// <summary>
    /// Optional lower bound for the initial sync if the cursor is empty.
    /// </summary>
    public DateTimeOffset? InitialLastModified { get; set; } = DateTimeOffset.UtcNow.AddDays(-30);

    public void Validate()
    {
        if (BaseUri is null || !BaseUri.IsAbsoluteUri)
        {
            throw new InvalidOperationException("MSRC base URI must be absolute.");
        }

        if (string.IsNullOrWhiteSpace(Locale))
        {
            throw new InvalidOperationException("Locale must be provided.");
        }

        if (!string.IsNullOrWhiteSpace(Locale) && !CultureInfo.GetCultures(CultureTypes.AllCultures).Any(c => string.Equals(c.Name, Locale, StringComparison.OrdinalIgnoreCase)))
        {
            throw new InvalidOperationException($"Locale '{Locale}' is not recognised.");
        }

        if (string.IsNullOrWhiteSpace(ApiVersion))
        {
            throw new InvalidOperationException("API version must be provided.");
        }

        if (!Guid.TryParse(TenantId, out _))
        {
            throw new InvalidOperationException("TenantId must be a valid GUID.");
        }

        if (string.IsNullOrWhiteSpace(ClientId))
        {
            throw new InvalidOperationException("ClientId must be provided.");
        }

        if (string.IsNullOrWhiteSpace(ClientSecret))
        {
            throw new InvalidOperationException("ClientSecret must be provided.");
        }

        if (string.IsNullOrWhiteSpace(Scope))
        {
            throw new InvalidOperationException("Scope must be provided.");
        }

        if (MaxAdvisoriesPerFetch <= 0)
        {
            throw new InvalidOperationException($"{nameof(MaxAdvisoriesPerFetch)} must be greater than zero.");
        }

        if (PageSize <= 0 || PageSize > 500)
        {
            throw new InvalidOperationException($"{nameof(PageSize)} must be between 1 and 500.");
        }

        if (CursorOverlap < TimeSpan.Zero || CursorOverlap > TimeSpan.FromHours(6))
        {
            throw new InvalidOperationException($"{nameof(CursorOverlap)} must be within 0-6 hours.");
        }

        if (RequestDelay < TimeSpan.Zero)
        {
            throw new InvalidOperationException($"{nameof(RequestDelay)} cannot be negative.");
        }

        if (FailureBackoff <= TimeSpan.Zero)
        {
            throw new InvalidOperationException($"{nameof(FailureBackoff)} must be positive.");
        }
    }
}
