using System;
using System.CommandLine;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Auth.Client;
using StellaOps.Cli.Commands;
using StellaOps.Cli.Configuration;
using StellaOps.Cli.Services;
using StellaOps.Cli.Telemetry;

namespace StellaOps.Cli;

internal static class Program
{
    internal static async Task<int> Main(string[] args)
    {
        var (options, configuration) = CliBootstrapper.Build(args);

        var services = new ServiceCollection();
        services.AddSingleton(configuration);
        services.AddSingleton(options);

        var verbosityState = new VerbosityState();
        services.AddSingleton(verbosityState);

        services.AddLogging(builder =>
        {
            builder.ClearProviders();
            builder.AddSimpleConsole(logOptions =>
            {
                logOptions.TimestampFormat = "HH:mm:ss ";
                logOptions.SingleLine = true;
            });
            builder.AddFilter((category, level) => level >= verbosityState.MinimumLevel);
        });

        if (!string.IsNullOrWhiteSpace(options.Authority.Url))
        {
            services.AddStellaOpsAuthClient(clientOptions =>
            {
                clientOptions.Authority = options.Authority.Url;
                clientOptions.ClientId = options.Authority.ClientId ?? string.Empty;
                clientOptions.ClientSecret = options.Authority.ClientSecret;
                clientOptions.DefaultScopes.Clear();
                clientOptions.DefaultScopes.Add(string.IsNullOrWhiteSpace(options.Authority.Scope)
                    ? StellaOps.Auth.Abstractions.StellaOpsScopes.FeedserJobsTrigger
                    : options.Authority.Scope);

                var resilience = options.Authority.Resilience ?? new StellaOpsCliAuthorityResilienceOptions();
                clientOptions.EnableRetries = resilience.EnableRetries ?? true;

                if (resilience.RetryDelays is { Count: > 0 })
                {
                    clientOptions.RetryDelays.Clear();
                    foreach (var delay in resilience.RetryDelays)
                    {
                        if (delay > TimeSpan.Zero)
                        {
                            clientOptions.RetryDelays.Add(delay);
                        }
                    }
                }

                if (resilience.AllowOfflineCacheFallback.HasValue)
                {
                    clientOptions.AllowOfflineCacheFallback = resilience.AllowOfflineCacheFallback.Value;
                }

                if (resilience.OfflineCacheTolerance.HasValue && resilience.OfflineCacheTolerance.Value >= TimeSpan.Zero)
                {
                    clientOptions.OfflineCacheTolerance = resilience.OfflineCacheTolerance.Value;
                }
            });

            var cacheDirectory = options.Authority.TokenCacheDirectory;
            if (!string.IsNullOrWhiteSpace(cacheDirectory))
            {
                Directory.CreateDirectory(cacheDirectory);
                services.AddStellaOpsFileTokenCache(cacheDirectory);
            }

            services.AddHttpClient<IAuthorityRevocationClient, AuthorityRevocationClient>(client =>
            {
                client.Timeout = TimeSpan.FromMinutes(2);
                if (Uri.TryCreate(options.Authority.Url, UriKind.Absolute, out var authorityUri))
                {
                    client.BaseAddress = authorityUri;
                }
            });
        }

        services.AddHttpClient<IBackendOperationsClient, BackendOperationsClient>(client =>
        {
            client.Timeout = TimeSpan.FromMinutes(5);
            if (!string.IsNullOrWhiteSpace(options.BackendUrl) &&
                Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri))
            {
                client.BaseAddress = backendUri;
            }
        });

        services.AddSingleton<IScannerExecutor, ScannerExecutor>();
        services.AddSingleton<IScannerInstaller, ScannerInstaller>();

        await using var serviceProvider = services.BuildServiceProvider();
        var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
        var startupLogger = loggerFactory.CreateLogger("StellaOps.Cli.Startup");
        AuthorityDiagnosticsReporter.Emit(configuration, startupLogger);
        using var cts = new CancellationTokenSource();
        Console.CancelKeyPress += (_, eventArgs) =>
        {
            eventArgs.Cancel = true;
            cts.Cancel();
        };

        var rootCommand = CommandFactory.Create(serviceProvider, options, cts.Token);
        var commandConfiguration = new CommandLineConfiguration(rootCommand);
        var commandExit = await commandConfiguration.InvokeAsync(args, cts.Token).ConfigureAwait(false);

        var finalExit = Environment.ExitCode != 0 ? Environment.ExitCode : commandExit;
        if (cts.IsCancellationRequested && finalExit == 0)
        {
            finalExit = 130; // Typical POSIX cancellation exit code
        }

        return finalExit;
    }
}
