using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using StellaOps.Auth.Abstractions;

namespace StellaOps.Feedser.WebService.Options;

public static class FeedserOptionsValidator
{
    public static void Validate(FeedserOptions options)
    {
        ArgumentNullException.ThrowIfNull(options);

        if (!string.Equals(options.Storage.Driver, "mongo", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only Mongo storage driver is supported (storage.driver == 'mongo').");
        }

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

        if (options.Storage.CommandTimeoutSeconds <= 0)
        {
            throw new InvalidOperationException("Command timeout must be greater than zero seconds.");
        }

        options.Telemetry ??= new FeedserOptions.TelemetryOptions();

        options.Authority ??= new FeedserOptions.AuthorityOptions();
        options.Authority.Resilience ??= new FeedserOptions.AuthorityOptions.ResilienceOptions();
        NormalizeList(options.Authority.Audiences, toLower: false);
        NormalizeList(options.Authority.RequiredScopes, toLower: true);
        NormalizeList(options.Authority.BypassNetworks, toLower: false);
        NormalizeList(options.Authority.ClientScopes, toLower: true);
        ValidateResilience(options.Authority.Resilience);

        if (options.Authority.RequiredScopes.Count == 0)
        {
            options.Authority.RequiredScopes.Add(StellaOpsScopes.FeedserJobsTrigger);
        }

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

        if (options.Authority.ClientScopes.Count == 0)
        {
            options.Authority.ClientScopes.Add(StellaOpsScopes.FeedserJobsTrigger);
        }

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

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

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

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

            if (options.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(options.Authority.MetadataAddress) && !Uri.TryCreate(options.Authority.MetadataAddress, UriKind.Absolute, out _))
            {
                throw new InvalidOperationException("Authority metadataAddress must be an absolute URI when specified.");
            }

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

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

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

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

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

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

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

    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 index = values.Count - 1; index >= 0; index--)
        {
            var entry = values[index];
            if (string.IsNullOrWhiteSpace(entry))
            {
                values.RemoveAt(index);
                continue;
            }

            var normalized = entry.Trim();
            if (toLower)
            {
                normalized = normalized.ToLowerInvariant();
            }

            if (!seen.Add(normalized))
            {
                values.RemoveAt(index);
                continue;
            }

            values[index] = normalized;
        }
    }

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

        foreach (var delay in resilience.RetryDelays)
        {
            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.");
        }
    }
}
