using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;

namespace StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Configuration;

public sealed class RancherHubConnectorOptions
{
    public static readonly Uri DefaultDiscoveryUri = new("https://vexhub.suse.com/.well-known/vex/rancher-hub.json");

    /// <summary>
    /// HTTP client name registered for the connector.
    /// </summary>
    public const string HttpClientName = "excititor.connector.suse.rancherhub";

    /// <summary>
    /// URI for the Rancher VEX hub discovery document.
    /// </summary>
    public Uri DiscoveryUri { get; set; } = DefaultDiscoveryUri;

    /// <summary>
    /// Optional OAuth2/OIDC token endpoint used for hub authentication.
    /// </summary>
    public Uri? TokenEndpoint { get; set; }

    /// <summary>
    /// Client identifier used when requesting hub access tokens.
    /// </summary>
    public string? ClientId { get; set; }

    /// <summary>
    /// Client secret used when requesting hub access tokens.
    /// </summary>
    public string? ClientSecret { get; set; }

    /// <summary>
    /// OAuth scopes requested for hub access; defaults align with Rancher hub reader role.
    /// </summary>
    public IList<string> Scopes { get; } = new List<string> { "hub.read" };

    /// <summary>
    /// Optional audience claim passed when requesting tokens (client credential grant).
    /// </summary>
    public string? Audience { get; set; }

    /// <summary>
    /// Preferred authentication scheme. Supported: client_secret_basic (default) or client_secret_post.
    /// </summary>
    public string ClientAuthenticationScheme { get; set; } = "client_secret_basic";

    /// <summary>
    /// Duration to cache discovery metadata before re-fetching.
    /// </summary>
    public TimeSpan MetadataCacheDuration { get; set; } = TimeSpan.FromMinutes(30);

    /// <summary>
    /// Optional file path for discovery metadata snapshots.
    /// </summary>
    public string? OfflineSnapshotPath { get; set; }

    /// <summary>
    /// When true, the loader prefers the offline snapshot prior to attempting network discovery.
    /// </summary>
    public bool PreferOfflineSnapshot { get; set; }

    /// <summary>
    /// Enables persisting freshly fetched discovery documents to <see cref="OfflineSnapshotPath"/>.
    /// </summary>
    public bool PersistOfflineSnapshot { get; set; } = true;

    /// <summary>
    /// Weight applied to the provider entry; hubs default below direct vendor feeds.
    /// </summary>
    public double TrustWeight { get; set; } = 0.6;

    /// <summary>
    /// Optional Sigstore/Cosign issuer for verifying hub-delivered attestations.
    /// </summary>
    public string? CosignIssuer { get; set; }

    /// <summary>
    /// Cosign identity pattern matched against transparency log subjects.
    /// </summary>
    public string? CosignIdentityPattern { get; set; }

    /// <summary>
    /// Additional trusted PGP fingerprints declared by the hub.
    /// </summary>
    public IList<string> PgpFingerprints { get; } = new List<string>();

    /// <summary>
    /// Allows falling back to unauthenticated discovery requests when credentials are absent.
    /// </summary>
    public bool AllowAnonymousDiscovery { get; set; }

    public void Validate(IFileSystem? fileSystem = null)
    {
        if (DiscoveryUri is null || !DiscoveryUri.IsAbsoluteUri)
        {
            throw new InvalidOperationException("DiscoveryUri must be an absolute URI.");
        }

        if (DiscoveryUri.Scheme is not ("http" or "https"))
        {
            throw new InvalidOperationException("DiscoveryUri must use HTTP or HTTPS.");
        }

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

        if (!string.IsNullOrWhiteSpace(OfflineSnapshotPath))
        {
            var fs = fileSystem ?? new FileSystem();
            var directory = Path.GetDirectoryName(OfflineSnapshotPath);
            if (!string.IsNullOrWhiteSpace(directory) && !fs.Directory.Exists(directory))
            {
                fs.Directory.CreateDirectory(directory);
            }
        }

        if (PreferOfflineSnapshot && string.IsNullOrWhiteSpace(OfflineSnapshotPath))
        {
            throw new InvalidOperationException("OfflineSnapshotPath must be provided when PreferOfflineSnapshot is enabled.");
        }

        var hasClientId = !string.IsNullOrWhiteSpace(ClientId);
        var hasClientSecret = !string.IsNullOrWhiteSpace(ClientSecret);
        var hasTokenEndpoint = TokenEndpoint is not null;
        if (hasClientId || hasClientSecret || hasTokenEndpoint)
        {
            if (!(hasClientId && hasClientSecret && hasTokenEndpoint))
            {
                throw new InvalidOperationException("ClientId, ClientSecret, and TokenEndpoint must be provided together for authenticated discovery.");
            }

            if (TokenEndpoint is not null && (!TokenEndpoint.IsAbsoluteUri || TokenEndpoint.Scheme is not ("http" or "https")))
            {
                throw new InvalidOperationException("TokenEndpoint must be an absolute HTTP(S) URI.");
            }
        }

        if (double.IsNaN(TrustWeight) || double.IsInfinity(TrustWeight))
        {
            TrustWeight = 0.6;
        }
        else if (TrustWeight <= 0)
        {
            TrustWeight = 0.1;
        }
        else if (TrustWeight > 1.0)
        {
            TrustWeight = 1.0;
        }

        if (!string.IsNullOrWhiteSpace(CosignIssuer) && string.IsNullOrWhiteSpace(CosignIdentityPattern))
        {
            throw new InvalidOperationException("CosignIdentityPattern must be provided when CosignIssuer is specified.");
        }

        if (!string.Equals(ClientAuthenticationScheme, "client_secret_basic", StringComparison.Ordinal) &&
            !string.Equals(ClientAuthenticationScheme, "client_secret_post", StringComparison.Ordinal))
        {
            throw new InvalidOperationException("ClientAuthenticationScheme must be 'client_secret_basic' or 'client_secret_post'.");
        }

        // Remove any empty scopes to avoid token request issues.
        if (Scopes.Count > 0)
        {
            for (var i = Scopes.Count - 1; i >= 0; i--)
            {
                if (string.IsNullOrWhiteSpace(Scopes[i]))
                {
                    Scopes.RemoveAt(i);
                }
            }
        }

        if (Scopes.Count == 0)
        {
            Scopes.Add("hub.read");
        }
    }
}
