using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Microsoft.Extensions.Configuration;
using StellaOps.Configuration;
using StellaOps.Auth.Abstractions;

namespace StellaOps.Cli.Configuration;

public static class CliBootstrapper
{
    public static (StellaOpsCliOptions Options, IConfigurationRoot Configuration) Build(string[] args)
    {
        var bootstrap = StellaOpsConfigurationBootstrapper.Build<StellaOpsCliOptions>(options =>
        {
            options.BindingSection = "StellaOps";
            options.ConfigureBuilder = builder =>
            {
                if (args.Length > 0)
                {
                    builder.AddCommandLine(args);
                }
            };
            options.PostBind = (cliOptions, configuration) =>
            {
                cliOptions.ApiKey = ResolveWithFallback(cliOptions.ApiKey, configuration, "API_KEY", "StellaOps:ApiKey", "ApiKey");
                cliOptions.BackendUrl = ResolveWithFallback(cliOptions.BackendUrl, configuration, "STELLAOPS_BACKEND_URL", "StellaOps:BackendUrl", "BackendUrl");
                cliOptions.ScannerSignaturePublicKeyPath = ResolveWithFallback(cliOptions.ScannerSignaturePublicKeyPath, configuration, "SCANNER_PUBLIC_KEY", "STELLAOPS_SCANNER_PUBLIC_KEY", "StellaOps:ScannerSignaturePublicKeyPath", "ScannerSignaturePublicKeyPath");

                cliOptions.ApiKey = cliOptions.ApiKey?.Trim() ?? string.Empty;
                cliOptions.BackendUrl = cliOptions.BackendUrl?.Trim() ?? string.Empty;
                cliOptions.ScannerSignaturePublicKeyPath = cliOptions.ScannerSignaturePublicKeyPath?.Trim() ?? string.Empty;

                var attemptsRaw = ResolveWithFallback(
                    string.Empty,
                    configuration,
                    "SCANNER_DOWNLOAD_ATTEMPTS",
                    "STELLAOPS_SCANNER_DOWNLOAD_ATTEMPTS",
                    "StellaOps:ScannerDownloadAttempts",
                    "ScannerDownloadAttempts");

                if (string.IsNullOrWhiteSpace(attemptsRaw))
                {
                    attemptsRaw = cliOptions.ScannerDownloadAttempts.ToString(CultureInfo.InvariantCulture);
                }

                if (int.TryParse(attemptsRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedAttempts) && parsedAttempts > 0)
                {
                    cliOptions.ScannerDownloadAttempts = parsedAttempts;
                }

                if (cliOptions.ScannerDownloadAttempts <= 0)
                {
                    cliOptions.ScannerDownloadAttempts = 3;
                }

                cliOptions.Authority ??= new StellaOpsCliAuthorityOptions();
                var authority = cliOptions.Authority;

                authority.Url = ResolveWithFallback(
                    authority.Url,
                    configuration,
                    "STELLAOPS_AUTHORITY_URL",
                    "StellaOps:Authority:Url",
                    "Authority:Url",
                    "Authority:Issuer");

                authority.ClientId = ResolveWithFallback(
                    authority.ClientId,
                    configuration,
                    "STELLAOPS_AUTHORITY_CLIENT_ID",
                    "StellaOps:Authority:ClientId",
                    "Authority:ClientId");

                authority.ClientSecret = ResolveWithFallback(
                    authority.ClientSecret ?? string.Empty,
                    configuration,
                    "STELLAOPS_AUTHORITY_CLIENT_SECRET",
                    "StellaOps:Authority:ClientSecret",
                    "Authority:ClientSecret");

                authority.Username = ResolveWithFallback(
                    authority.Username,
                    configuration,
                    "STELLAOPS_AUTHORITY_USERNAME",
                    "StellaOps:Authority:Username",
                    "Authority:Username");

                authority.Password = ResolveWithFallback(
                    authority.Password ?? string.Empty,
                    configuration,
                    "STELLAOPS_AUTHORITY_PASSWORD",
                    "StellaOps:Authority:Password",
                    "Authority:Password");

                authority.Scope = ResolveWithFallback(
                    authority.Scope,
                    configuration,
                    "STELLAOPS_AUTHORITY_SCOPE",
                    "StellaOps:Authority:Scope",
                    "Authority:Scope");

                authority.TokenCacheDirectory = ResolveWithFallback(
                    authority.TokenCacheDirectory,
                    configuration,
                    "STELLAOPS_AUTHORITY_TOKEN_CACHE_DIR",
                    "StellaOps:Authority:TokenCacheDirectory",
                    "Authority:TokenCacheDirectory");

                authority.Url = authority.Url?.Trim() ?? string.Empty;
                authority.ClientId = authority.ClientId?.Trim() ?? string.Empty;
                authority.ClientSecret = string.IsNullOrWhiteSpace(authority.ClientSecret) ? null : authority.ClientSecret.Trim();
                authority.Username = authority.Username?.Trim() ?? string.Empty;
                authority.Password = string.IsNullOrWhiteSpace(authority.Password) ? null : authority.Password.Trim();
                authority.Scope = string.IsNullOrWhiteSpace(authority.Scope) ? StellaOpsScopes.FeedserJobsTrigger : authority.Scope.Trim();

                authority.Resilience ??= new StellaOpsCliAuthorityResilienceOptions();
                authority.Resilience.RetryDelays ??= new List<TimeSpan>();
                var resilience = authority.Resilience;

                if (!resilience.EnableRetries.HasValue)
                {
                    var raw = ResolveWithFallback(
                        string.Empty,
                        configuration,
                        "STELLAOPS_AUTHORITY_ENABLE_RETRIES",
                        "StellaOps:Authority:Resilience:EnableRetries",
                        "StellaOps:Authority:EnableRetries",
                        "Authority:Resilience:EnableRetries",
                        "Authority:EnableRetries");

                    if (TryParseBoolean(raw, out var parsed))
                    {
                        resilience.EnableRetries = parsed;
                    }
                }

                var retryDelaysRaw = ResolveWithFallback(
                    string.Empty,
                    configuration,
                    "STELLAOPS_AUTHORITY_RETRY_DELAYS",
                    "StellaOps:Authority:Resilience:RetryDelays",
                    "StellaOps:Authority:RetryDelays",
                    "Authority:Resilience:RetryDelays",
                    "Authority:RetryDelays");

                if (!string.IsNullOrWhiteSpace(retryDelaysRaw))
                {
                    resilience.RetryDelays.Clear();
                    foreach (var delay in ParseRetryDelays(retryDelaysRaw))
                    {
                        if (delay > TimeSpan.Zero)
                        {
                            resilience.RetryDelays.Add(delay);
                        }
                    }
                }

                if (!resilience.AllowOfflineCacheFallback.HasValue)
                {
                    var raw = ResolveWithFallback(
                        string.Empty,
                        configuration,
                        "STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK",
                        "StellaOps:Authority:Resilience:AllowOfflineCacheFallback",
                        "StellaOps:Authority:AllowOfflineCacheFallback",
                        "Authority:Resilience:AllowOfflineCacheFallback",
                        "Authority:AllowOfflineCacheFallback");

                    if (TryParseBoolean(raw, out var parsed))
                    {
                        resilience.AllowOfflineCacheFallback = parsed;
                    }
                }

                if (!resilience.OfflineCacheTolerance.HasValue)
                {
                    var raw = ResolveWithFallback(
                        string.Empty,
                        configuration,
                        "STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE",
                        "StellaOps:Authority:Resilience:OfflineCacheTolerance",
                        "StellaOps:Authority:OfflineCacheTolerance",
                        "Authority:Resilience:OfflineCacheTolerance",
                        "Authority:OfflineCacheTolerance");

                    if (TimeSpan.TryParse(raw, CultureInfo.InvariantCulture, out var tolerance) && tolerance >= TimeSpan.Zero)
                    {
                        resilience.OfflineCacheTolerance = tolerance;
                    }
                }

                var defaultTokenCache = GetDefaultTokenCacheDirectory();
                if (string.IsNullOrWhiteSpace(authority.TokenCacheDirectory))
                {
                    authority.TokenCacheDirectory = defaultTokenCache;
                }
                else
                {
                    authority.TokenCacheDirectory = Path.GetFullPath(authority.TokenCacheDirectory);
                }
            };
        });

        return (bootstrap.Options, bootstrap.Configuration);
    }

    private static string ResolveWithFallback(string currentValue, IConfiguration configuration, params string[] keys)
    {
        if (!string.IsNullOrWhiteSpace(currentValue))
        {
            return currentValue;
        }

        foreach (var key in keys)
        {
            var value = configuration[key];
            if (!string.IsNullOrWhiteSpace(value))
            {
                return value;
            }
        }

        return string.Empty;
    }

    private static bool TryParseBoolean(string value, out bool parsed)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            parsed = default;
            return false;
        }

        if (bool.TryParse(value, out parsed))
        {
            return true;
        }

        if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var numeric))
        {
            parsed = numeric != 0;
            return true;
        }

        parsed = default;
        return false;
    }

    private static IEnumerable<TimeSpan> ParseRetryDelays(string raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            yield break;
        }

        var separators = new[] { ',', ';', ' ' };
        foreach (var token in raw.Split(separators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            if (TimeSpan.TryParse(token, CultureInfo.InvariantCulture, out var delay) && delay > TimeSpan.Zero)
            {
                yield return delay;
            }
        }
    }

    private static string GetDefaultTokenCacheDirectory()
    {
        var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        if (string.IsNullOrWhiteSpace(home))
        {
            home = AppContext.BaseDirectory;
        }

        return Path.GetFullPath(Path.Combine(home, ".stellaops", "tokens"));
    }
}
