using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace StellaOps.Feedser.Source.Cve.Configuration;

public sealed class CveOptions
{
    public static string HttpClientName => "source.cve";

    public Uri BaseEndpoint { get; set; } = new("https://cveawg.mitre.org/api/", UriKind.Absolute);

    /// <summary>
    /// CVE Services requires an organisation identifier for authenticated requests.
    /// </summary>
    public string ApiOrg { get; set; } = string.Empty;

    /// <summary>
    /// CVE Services user identifier. Typically the username registered with the CVE Program.
    /// </summary>
    public string ApiUser { get; set; } = string.Empty;

    /// <summary>
    /// API key issued by the CVE Program for the configured organisation/user pair.
    /// </summary>
    public string ApiKey { get; set; } = string.Empty;

    /// <summary>
    /// Optional path containing seed CVE JSON documents used when live credentials are unavailable.
    /// </summary>
    public string? SeedDirectory { get; set; }

    /// <summary>
    /// Results fetched per page when querying CVE Services. Valid range 1-500.
    /// </summary>
    public int PageSize { get; set; } = 200;

    /// <summary>
    /// Maximum number of pages to fetch in a single run. Guards against runaway backfills.
    /// </summary>
    public int MaxPagesPerFetch { get; set; } = 5;

    /// <summary>
    /// Sliding look-back window when no previous cursor is available.
    /// </summary>
    public TimeSpan InitialBackfill { get; set; } = TimeSpan.FromDays(30);

    /// <summary>
    /// Delay between paginated requests to respect API throttling guidance.
    /// </summary>
    public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);

    /// <summary>
    /// Backoff applied when the connector encounters an unrecoverable failure.
    /// </summary>
    public TimeSpan FailureBackoff { get; set; } = TimeSpan.FromMinutes(10);

    [MemberNotNull(nameof(BaseEndpoint))]
    public void Validate()
    {
        if (BaseEndpoint is null || !BaseEndpoint.IsAbsoluteUri)
        {
            throw new InvalidOperationException("BaseEndpoint must be an absolute URI.");
        }

        var hasCredentials = !string.IsNullOrWhiteSpace(ApiOrg)
            && !string.IsNullOrWhiteSpace(ApiUser)
            && !string.IsNullOrWhiteSpace(ApiKey);
        var hasSeedDirectory = !string.IsNullOrWhiteSpace(SeedDirectory);

        if (!hasCredentials && !hasSeedDirectory)
        {
            throw new InvalidOperationException("Api credentials must be provided unless a SeedDirectory is configured.");
        }

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

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

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

        if (hasSeedDirectory && !Directory.Exists(SeedDirectory!))
        {
            throw new InvalidOperationException($"SeedDirectory '{SeedDirectory}' does not exist.");
        }

        if (PageSize is < 1 or > 500)
        {
            throw new InvalidOperationException("PageSize must be between 1 and 500.");
        }

        if (MaxPagesPerFetch <= 0)
        {
            throw new InvalidOperationException("MaxPagesPerFetch must be a positive integer.");
        }

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

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

        if (FailureBackoff <= TimeSpan.Zero)
        {
            throw new InvalidOperationException("FailureBackoff must be greater than zero.");
        }
    }

    public bool HasCredentials()
        => !string.IsNullOrWhiteSpace(ApiOrg)
            && !string.IsNullOrWhiteSpace(ApiUser)
            && !string.IsNullOrWhiteSpace(ApiKey);
}
