using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.WebService.Security;

namespace StellaOps.Scanner.WebService.Options;

/// <summary>
/// Validation helpers for <see cref="ScannerWebServiceOptions"/>.
/// </summary>
public static class ScannerWebServiceOptionsValidator
{
    private static readonly HashSet<string> SupportedStorageDrivers = new(StringComparer.OrdinalIgnoreCase)
    {
        "mongo"
    };

    private static readonly HashSet<string> SupportedQueueDrivers = new(StringComparer.OrdinalIgnoreCase)
    {
        "redis",
        "nats",
        "rabbitmq"
    };

    private static readonly HashSet<string> SupportedArtifactDrivers = new(StringComparer.OrdinalIgnoreCase)
    {
        "minio"
    };

    public static void Validate(ScannerWebServiceOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);

        if (options.SchemaVersion <= 0)
        {
            throw new InvalidOperationException("Scanner configuration requires a positive schemaVersion.");
        }

        options.Storage ??= new ScannerWebServiceOptions.StorageOptions();
        ValidateStorage(options.Storage);

        options.Queue ??= new ScannerWebServiceOptions.QueueOptions();
        ValidateQueue(options.Queue);

        options.ArtifactStore ??= new ScannerWebServiceOptions.ArtifactStoreOptions();
        ValidateArtifactStore(options.ArtifactStore);

        options.Features ??= new ScannerWebServiceOptions.FeatureFlagOptions();
        options.Plugins ??= new ScannerWebServiceOptions.PluginOptions();
        options.Telemetry ??= new ScannerWebServiceOptions.TelemetryOptions();
        ValidateTelemetry(options.Telemetry);

        options.Authority ??= new ScannerWebServiceOptions.AuthorityOptions();
        ValidateAuthority(options.Authority);

        options.Signing ??= new ScannerWebServiceOptions.SigningOptions();
        ValidateSigning(options.Signing);

        options.Api ??= new ScannerWebServiceOptions.ApiOptions();
        if (string.IsNullOrWhiteSpace(options.Api.BasePath))
        {
            throw new InvalidOperationException("API basePath must be configured.");
        }
    }

    private static void ValidateStorage(ScannerWebServiceOptions.StorageOptions storage)
    {
        if (!SupportedStorageDrivers.Contains(storage.Driver))
        {
            throw new InvalidOperationException($"Unsupported storage driver '{storage.Driver}'. Supported drivers: mongo.");
        }

        if (string.IsNullOrWhiteSpace(storage.Dsn))
        {
            throw new InvalidOperationException("Storage DSN must be configured.");
        }

        if (storage.CommandTimeoutSeconds <= 0)
        {
            throw new InvalidOperationException("Storage commandTimeoutSeconds must be greater than zero.");
        }

        if (storage.HealthCheckTimeoutSeconds <= 0)
        {
            throw new InvalidOperationException("Storage healthCheckTimeoutSeconds must be greater than zero.");
        }
    }

    private static void ValidateQueue(ScannerWebServiceOptions.QueueOptions queue)
    {
        if (!SupportedQueueDrivers.Contains(queue.Driver))
        {
            throw new InvalidOperationException($"Unsupported queue driver '{queue.Driver}'. Supported drivers: redis, nats, rabbitmq.");
        }

        if (string.IsNullOrWhiteSpace(queue.Dsn))
        {
            throw new InvalidOperationException("Queue DSN must be configured.");
        }

        if (string.IsNullOrWhiteSpace(queue.Namespace))
        {
            throw new InvalidOperationException("Queue namespace must be configured.");
        }

        if (queue.VisibilityTimeoutSeconds <= 0)
        {
            throw new InvalidOperationException("Queue visibilityTimeoutSeconds must be greater than zero.");
        }

        if (queue.LeaseHeartbeatSeconds <= 0)
        {
            throw new InvalidOperationException("Queue leaseHeartbeatSeconds must be greater than zero.");
        }

        if (queue.MaxDeliveryAttempts <= 0)
        {
            throw new InvalidOperationException("Queue maxDeliveryAttempts must be greater than zero.");
        }
    }

    private static void ValidateArtifactStore(ScannerWebServiceOptions.ArtifactStoreOptions artifactStore)
    {
        if (!SupportedArtifactDrivers.Contains(artifactStore.Driver))
        {
            throw new InvalidOperationException($"Unsupported artifact store driver '{artifactStore.Driver}'. Supported drivers: minio.");
        }

        if (string.IsNullOrWhiteSpace(artifactStore.Endpoint))
        {
            throw new InvalidOperationException("Artifact store endpoint must be configured.");
        }

        if (string.IsNullOrWhiteSpace(artifactStore.Bucket))
        {
            throw new InvalidOperationException("Artifact store bucket must be configured.");
        }

        if (artifactStore.EnableObjectLock && artifactStore.ObjectLockRetentionDays <= 0)
        {
            throw new InvalidOperationException("Artifact store objectLockRetentionDays must be greater than zero when object lock is enabled.");
        }
    }

    private static void ValidateTelemetry(ScannerWebServiceOptions.TelemetryOptions telemetry)
    {
        if (string.IsNullOrWhiteSpace(telemetry.MinimumLogLevel))
        {
            throw new InvalidOperationException("Telemetry minimumLogLevel must be configured.");
        }

        if (!Enum.TryParse(telemetry.MinimumLogLevel, ignoreCase: true, out LogLevel _))
        {
            throw new InvalidOperationException($"Telemetry minimumLogLevel '{telemetry.MinimumLogLevel}' is invalid.");
        }

        if (!string.IsNullOrWhiteSpace(telemetry.OtlpEndpoint) && !Uri.TryCreate(telemetry.OtlpEndpoint, UriKind.Absolute, out _))
        {
            throw new InvalidOperationException("Telemetry OTLP endpoint must be an absolute URI when specified.");
        }

        foreach (var attribute in telemetry.ResourceAttributes)
        {
            if (string.IsNullOrWhiteSpace(attribute.Key))
            {
                throw new InvalidOperationException("Telemetry resource attribute keys must be non-empty.");
            }
        }

        foreach (var header in telemetry.OtlpHeaders)
        {
            if (string.IsNullOrWhiteSpace(header.Key))
            {
                throw new InvalidOperationException("Telemetry OTLP header keys must be non-empty.");
            }
        }
    }

    private static void ValidateAuthority(ScannerWebServiceOptions.AuthorityOptions authority)
    {
        authority.Resilience ??= new ScannerWebServiceOptions.AuthorityOptions.ResilienceOptions();
        NormalizeList(authority.Audiences, toLower: false);
        NormalizeList(authority.RequiredScopes, toLower: true);
        NormalizeList(authority.BypassNetworks, toLower: false);
        NormalizeList(authority.ClientScopes, toLower: true);
        NormalizeResilience(authority.Resilience);

        if (authority.RequiredScopes.Count == 0)
        {
            authority.RequiredScopes.Add(ScannerAuthorityScopes.ScansEnqueue);
        }

        if (authority.ClientScopes.Count == 0)
        {
            foreach (var scope in authority.RequiredScopes)
            {
                authority.ClientScopes.Add(scope);
            }
        }

        if (authority.BackchannelTimeoutSeconds <= 0)
        {
            throw new InvalidOperationException("Authority backchannelTimeoutSeconds must be greater than zero.");
        }

        if (authority.TokenClockSkewSeconds < 0 || authority.TokenClockSkewSeconds > 300)
        {
            throw new InvalidOperationException("Authority tokenClockSkewSeconds must be between 0 and 300 seconds.");
        }

        if (!authority.Enabled)
        {
            return;
        }

        if (string.IsNullOrWhiteSpace(authority.Issuer))
        {
            throw new InvalidOperationException("Authority issuer must be configured when authority is enabled.");
        }

        if (!Uri.TryCreate(authority.Issuer, UriKind.Absolute, out var issuerUri))
        {
            throw new InvalidOperationException("Authority issuer must be an absolute URI.");
        }

        if (authority.RequireHttpsMetadata && !issuerUri.IsLoopback && !string.Equals(issuerUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Authority issuer must use HTTPS when requireHttpsMetadata is enabled.");
        }

        if (!string.IsNullOrWhiteSpace(authority.MetadataAddress) && !Uri.TryCreate(authority.MetadataAddress, UriKind.Absolute, out _))
        {
            throw new InvalidOperationException("Authority metadataAddress must be an absolute URI when specified.");
        }

        if (authority.Audiences.Count == 0)
        {
            throw new InvalidOperationException("Authority audiences must include at least one entry when authority is enabled.");
        }

        if (!authority.AllowAnonymousFallback)
        {
            if (string.IsNullOrWhiteSpace(authority.ClientId))
            {
                throw new InvalidOperationException("Authority clientId must be configured when anonymous fallback is disabled.");
            }

            if (string.IsNullOrWhiteSpace(authority.ClientSecret))
            {
                throw new InvalidOperationException("Authority clientSecret must be configured when anonymous fallback is disabled.");
            }
        }
    }

    private static void ValidateSigning(ScannerWebServiceOptions.SigningOptions signing)
    {
        if (signing.EnvelopeTtlSeconds <= 0)
        {
            throw new InvalidOperationException("Signing envelopeTtlSeconds must be greater than zero.");
        }

        if (!signing.Enabled)
        {
            return;
        }

        if (string.IsNullOrWhiteSpace(signing.KeyId))
        {
            throw new InvalidOperationException("Signing keyId must be configured when signing is enabled.");
        }

        if (string.IsNullOrWhiteSpace(signing.Algorithm))
        {
            throw new InvalidOperationException("Signing algorithm must be configured when signing is enabled.");
        }

        if (string.IsNullOrWhiteSpace(signing.KeyPem) && string.IsNullOrWhiteSpace(signing.KeyPemFile))
        {
            throw new InvalidOperationException("Signing requires keyPem or keyPemFile when enabled.");
        }
    }

    private static void NormalizeList(IList<string> values, bool toLower)
    {
        if (values is null || values.Count == 0)
        {
            return;
        }

        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        for (var i = values.Count - 1; i >= 0; i--)
        {
            var entry = values[i];
            if (string.IsNullOrWhiteSpace(entry))
            {
                values.RemoveAt(i);
                continue;
            }

            var normalized = toLower ? entry.Trim().ToLowerInvariant() : entry.Trim();
            if (!seen.Add(normalized))
            {
                values.RemoveAt(i);
                continue;
            }

            values[i] = normalized;
        }
    }

    private static void NormalizeResilience(ScannerWebServiceOptions.AuthorityOptions.ResilienceOptions resilience)
    {
        if (resilience.RetryDelays is null)
        {
            return;
        }

        foreach (var delay in resilience.RetryDelays.ToArray())
        {
            if (delay <= TimeSpan.Zero)
            {
                throw new InvalidOperationException("Authority resilience retryDelays must be greater than zero.");
            }
        }

        if (resilience.OfflineCacheTolerance.HasValue && resilience.OfflineCacheTolerance.Value < TimeSpan.Zero)
        {
            throw new InvalidOperationException("Authority resilience offlineCacheTolerance must be greater than or equal to zero.");
        }
    }
}
