using System.Diagnostics;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

AnsiConsole.Write(new FigletText("Welcome to OF-DL").Color(Color.Red));
await RunAsync(args);

static async Task RunAsync(string[] args)
{
    ServiceCollection services = await ConfigureServices(args);
    ServiceProvider serviceProvider = services.BuildServiceProvider();

    ExitIfOtherProcess(serviceProvider);

    Worker worker = serviceProvider.GetRequiredService<Worker>();
    await worker.RunAsync();
}

static async Task<ServiceCollection> ConfigureServices(string[] args)
{
    // Set up dependency injection with LoggingService and ConfigService
    ServiceCollection services = new();
    services.AddSingleton<ILoggingService, CajetanLoggingService>();
    services.AddSingleton<IConfigService, ConfigService>();
    services.AddSingleton(new ExitHelper(new SpectreDownloadEventHandler()));
    ServiceProvider tempServiceProvider = services.BuildServiceProvider();

    ILoggingService loggingService = tempServiceProvider.GetRequiredService<ILoggingService>();
    IConfigService configService = tempServiceProvider.GetRequiredService<IConfigService>();
    ExitHelper exitHelper = tempServiceProvider.GetRequiredService<ExitHelper>();

    if (!await configService.LoadConfigurationAsync(args))
    {
        AnsiConsole.MarkupLine("\n[red]config.conf is not valid, check your syntax![/]\n");
        AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
        Console.ReadKey();

        exitHelper.ExitWithCode(3);
    }

    if (configService.CurrentConfig.NonInteractiveMode is false)
    {
        AnsiConsole.MarkupLine("\n[red]Cannot run in Interactive Mode![/]\n");
        AnsiConsole.MarkupLine("[red]Press any key to exit.[/]");
        Console.ReadKey();

        exitHelper.ExitWithCode(3);
    }

    AnsiConsole.Markup("[green]config.conf located successfully!\n[/]");

    CajetanConfig cajetanConfig = ParseCommandlineArgs(args, configService.CurrentConfig);

    // Set up full dependency injection with loaded config
    services = [];
    services.AddSingleton(loggingService);
    services.AddSingleton(configService);
    services.AddSingleton(exitHelper);
    services.AddSingleton(cajetanConfig);

    services.AddSingleton<IAuthService, AuthService>();
    services.AddSingleton<IStartupService, StartupService>();
    services.AddSingleton<IFileNameService, FileNameService>();

    services.AddSingleton<ICajetanDownloadService, CajetanDownloadService>();
    services.AddSingleton<IDownloadService>(sp => sp.GetRequiredService<ICajetanDownloadService>());

    services.AddSingleton<ICajetanApiService, CajetanApiService>();
    services.AddSingleton<IApiService>(sp => sp.GetRequiredService<ICajetanApiService>());

    services.AddSingleton<ICajetanDbService, CajetanDbService>();
    services.AddSingleton<IDbService>(sp => sp.GetRequiredService<ICajetanDbService>());


    services.AddSingleton<ICajetanDownloadOrchestrationService, CajetanDownloadOrchestrationService>();
    services.AddSingleton<IDownloadOrchestrationService>(sp => sp.GetRequiredService<ICajetanDownloadOrchestrationService>());

    services.AddSingleton<ICajetanDownloadEventHandler, CajetanDownloadEventHandler>();
    services.AddSingleton<IDownloadEventHandler>(sp => sp.GetRequiredService<ICajetanDownloadEventHandler>());

    services.AddSingleton<Worker>();

    return services;
}

static CajetanConfig ParseCommandlineArgs(string[] args, Config currentConfig)
{
    const string SPECIFIC_LISTS_ARG = "--specific-lists";
    const string SPECIFIC_USERS_ARG = "--specific-users";
    const string OUTPUT_BLOCKED_USERS_ARG = "--output-blocked";
    const string UPDATE_ALL_USER_INFO_ARG = "--update-userinfo";

    CajetanConfig parsedConfig = new();

    if (ParseListAndUserArguments(ref parsedConfig))
        return parsedConfig;

    if (ParseFlagArgument(OUTPUT_BLOCKED_USERS_ARG, EMode.OutputBlockedUsers, ref parsedConfig))
        return parsedConfig;

    if (ParseFlagArgument(UPDATE_ALL_USER_INFO_ARG, EMode.UpdateAllUserInfo, ref parsedConfig))
        return parsedConfig;

    // Will process all active subscriptions
    return parsedConfig;

    bool ParseListAndUserArguments(ref CajetanConfig parsedConfig)
    {
        bool hasSpecificListsArg = ParseCommaSeparatedListArgument(SPECIFIC_LISTS_ARG, ref parsedConfig, (c, v) =>
        {
            c.NonInteractiveSpecificLists = v;
            Log.Logger = Log.Logger.ForContext(nameof(CajetanConfig.NonInteractiveSpecificLists), string.Join(",", v));
        });

        if (hasSpecificListsArg)
            return true;


        bool hasSpecificUsersArg = ParseCommaSeparatedListArgument(SPECIFIC_USERS_ARG, ref parsedConfig, (c, v) =>
        {
            c.NonInteractiveSpecificUsers = v;
            Log.Logger = Log.Logger.ForContext(nameof(CajetanConfig.NonInteractiveSpecificUsers), string.Join(",", v));
        });

        if (hasSpecificUsersArg)
            return true;

        return false;
    }

    bool ParseCommaSeparatedListArgument(string argName, ref CajetanConfig parsedConfig, Action<CajetanConfig, string[]> assignmentFunc)
    {
        char[] separator = [','];
        int indexOfArg = Array.FindIndex(args, a => argName.Equals(a, StringComparison.OrdinalIgnoreCase));

        if (indexOfArg < 0)
            return false;

        int indexOfListValues = indexOfArg + 1;
        string[] strListValues = args.ElementAtOrDefault(indexOfListValues)?.Split(separator, StringSplitOptions.RemoveEmptyEntries) ?? [];

        if (strListValues.Length == 0)
            return false;

        assignmentFunc(parsedConfig, strListValues);
        parsedConfig.Mode = EMode.DownloadCreatorContent;

        Log.Logger = Log.Logger.ForContext("Mode", $"{EMode.DownloadCreatorContent}");
        return true;
    }

    bool ParseFlagArgument(string argName, EMode modeIfArgIsSet, ref CajetanConfig parsedConfig)
    {
        if (!args.Any(a => argName.Equals(a, StringComparison.OrdinalIgnoreCase)))
            return false;

        currentConfig.NonInteractiveMode = true;
        parsedConfig.Mode = modeIfArgIsSet;

        Log.Logger = Log.Logger.ForContext("Mode", $"{modeIfArgIsSet}");
        return true;
    }
}

static void ExitIfOtherProcess(ServiceProvider serviceProvider)
{
    Assembly? entryAssembly = Assembly.GetEntryAssembly();
    AssemblyName? entryAssemblyName = entryAssembly?.GetName();

    if (entryAssemblyName?.Name is null)
        return;

    Process thisProcess = Process.GetCurrentProcess();
    Process[] otherProcesses = [.. Process.GetProcessesByName(entryAssemblyName.Name).Where(p => p.Id != thisProcess.Id)];

    if (otherProcesses.Length <= 0)
        return;

    AnsiConsole.Markup($"[green]Other OF DL process detected, exiting..\n[/]");
    Log.Warning("Other OF DL process detected, exiting..");

    serviceProvider.GetRequiredService<ExitHelper>().ExitWithCode(0);
}

