using CommandLine;
using Coscine.GraphDeployer.Models.ConfigurationModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Extensions.Logging;
using Winton.Extensions.Configuration.Consul;
using static Coscine.GraphDeployer.Utils.CommandLineOptions;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace Coscine.GraphDeployer;

public class Program
{
    private static IServiceProvider _serviceProvider = null!;

    private static string? _environmentName;

    public static int Main(string[] args)
    {
        _environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");

        Console.WriteLine($"Running in environment: {_environmentName ?? "environment is null"}");

        InitializeServices();

        var logger = _serviceProvider.GetRequiredService<ILogger<Program>>();

        try
        {
            var parserResult = Parser.Default.ParseArguments<GraphDeployerOptions>(args);

            var result = parserResult.MapResult(
                (opts) => _serviceProvider.GetRequiredService<Deployer>().RunAsync(opts).Result,
                HandleParseError
            );

            if (result)
            {
                logger.LogInformation("Finished.");
                return 0; // Exit Code 0 for Success
            }
            else
            {
                logger.LogInformation("Program execution was interrupted.");
                return -1; // Exit Code -1 for Failure
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Exception: {message}", ex.Message);
            return -1; // Exit Code -1 for Failure
        }
        finally
        {
            DisposeServices();
        }
    }

    private static void InitializeServices()
    {
        // Create a new instance of ConfigurationBuilder
        var configBuilder = new ConfigurationBuilder();

        // Define the Consul URL
        var consulUrl = Environment.GetEnvironmentVariable("CONSUL_URL") ?? "http://localhost:8500";

        // Remove the default sources
        configBuilder.Sources.Clear();

        // Add Consul as a configuration source
        var configuration = configBuilder
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{_environmentName}.json", optional: true, reloadOnChange: true)
            .AddConsul(
                "coscine/Coscine.Infrastructure/GraphDeployer/appsettings",
                options =>
                {
                    options.ConsulConfigurationOptions =
                        cco => cco.Address = new Uri(consulUrl);
                    options.Optional = true;
                    options.ReloadOnChange = true;
                    options.PollWaitTime = TimeSpan.FromSeconds(5);
                    options.OnLoadException = exceptionContext => exceptionContext.Ignore = true;
                }
            )
            .AddConsul(
                $"coscine/Coscine.Infrastructure/GraphDeployer/appsettings.{_environmentName}.json",
                options =>
                {
                    options.ConsulConfigurationOptions =
                        cco => cco.Address = new Uri(consulUrl);
                    options.Optional = true;
                    options.ReloadOnChange = true;
                    options.PollWaitTime = TimeSpan.FromSeconds(5);
                    options.OnLoadException = exceptionContext => exceptionContext.Ignore = true;
                }
            )
            .AddEnvironmentVariables()
            .Build();

        var services = new ServiceCollection()
            .AddSingleton<IConfiguration>(configuration);

        // Add the configuration to the service collection
        services.Configure<GraphDeployerConfiguration>(config =>
        {
            configuration.GetSection(GraphDeployerConfiguration.Section).Bind(config);
        });

        // Add logging
        services.AddLogging(builder =>
        {
            builder.ClearProviders();
            builder.SetMinimumLevel(LogLevel.Trace);
            builder.AddNLog();
        });

        var graphDeployerConfiguration = new GraphDeployerConfiguration();
        configuration.Bind(GraphDeployerConfiguration.Section, graphDeployerConfiguration);

        if (graphDeployerConfiguration.Endpoint is null)
        {
            throw new ArgumentNullException(nameof(graphDeployerConfiguration.Endpoint), "Endpoint cannot be null.");
        }

        if (graphDeployerConfiguration.ApiKey is null)
        {
            throw new ArgumentNullException(nameof(graphDeployerConfiguration.ApiKey), "ApiKey cannot be null.");
        }

        // Set the default LogLevel
        LogManager.Configuration.Variables["logLevel"] = graphDeployerConfiguration.Logger?.LogLevel ?? "Trace";
        // Set the log location
        LogManager.Configuration.Variables["logHome"] = graphDeployerConfiguration.Logger?.LogHome ?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");

        // Register the HTTP client
        services.AddHttpClient();

        // Add services for reporting
        services.AddTransient<Deployer>();

        _serviceProvider = services.BuildServiceProvider();
    }

    private static void DisposeServices()
    {
        if (_serviceProvider == null)
        {
            return;
        }
        if (_serviceProvider is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }

    private static bool HandleParseError(IEnumerable<Error> errs)
    {
        var logger = _serviceProvider.GetService<ILogger<Program>>();

        foreach (var err in errs)
        {
            if (err is HelpRequestedError || err is VersionRequestedError)
            {
                // Handle the display of help or version information
                // Usually, the library will automatically display the help/version info,
                // but you can customize it if needed.
            }
            else
            {
                // For other types of errors, you can log them or write them to the console
                logger?.LogError("Error encountered parsing command-line options: {Error}", err.ToString());
                Console.Error.WriteLine($"Error: {err}");
            }
        }

        // Since there were errors, we typically return false to indicate that the program should not proceed
        return false;
    }


    public static TResult SanitizeOptions<TResult>(TResult unsanitizedOptions)
    {
        // Sanitize all input that accepts an array or is a list of inputs.
        if (unsanitizedOptions is not null)
        {
            var type = unsanitizedOptions.GetType();
            if (type == typeof(GraphDeployerOptions))
            {
                var options = unsanitizedOptions as GraphDeployerOptions;
                if (options is not null)
                {
                    // Sanitize options here
                    return (TResult)(object)options;
                }
            }
        }
        return unsanitizedOptions;
    }
}