using System.Net;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using FluentValidation;
using Managing.Application;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Accounts;
using Managing.Application.Agents;
using Managing.Application.Backtests;
using Managing.Application.Grains;
using Managing.Application.ManageBot;
using Managing.Application.ManageBot.Commands;
using Managing.Application.MoneyManagements;
using Managing.Application.Orleans;
using Managing.Application.Scenarios;
using Managing.Application.Shared;
using Managing.Application.Shared.Behaviours;
using Managing.Application.Synth;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Application.Trading.Handlers;
using Managing.Application.Users;
using Managing.Application.Workers;
using Managing.Domain.Trades;
using Managing.Infrastructure.Databases;
using Managing.Infrastructure.Databases.InfluxDb;
using Managing.Infrastructure.Databases.InfluxDb.Abstractions;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges;
using Managing.Infrastructure.Exchanges.Abstractions;
using Managing.Infrastructure.Exchanges.Exchanges;
using Managing.Infrastructure.Messengers.Discord;
using Managing.Infrastructure.Storage;
using MediatR;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Orleans.Configuration;

namespace Managing.Bootstrap;

public static class ApiBootstrap
{
    private static readonly Assembly ApplicationAssembly = typeof(StartBotCommand).GetTypeInfo().Assembly;

    public static IServiceCollection RegisterApiDependencies(this IServiceCollection services,
        IConfiguration configuration)
    {
        services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));

        return services
                .AddApplication()
                .AddInfrastructure(configuration)
                .AddWorkers(configuration)
                .AddFluentValidation()
                .AddMediatR()
            ;
    }

    public static IServiceCollection AddHostedServices(this IServiceCollection services)
    {
        services.AddHostedService<GrainInitializer>();
        return services;
    }

    // Note: IClusterClient is automatically available in co-hosting scenarios
    // through IGrainFactory. Services should inject IGrainFactory instead of IClusterClient
    // to avoid circular dependency issues during DI container construction.

    public static IHostBuilder ConfigureOrleans(this IHostBuilder hostBuilder, IConfiguration configuration,
        bool isProduction)
    {
        // Check if Orleans grains should be active using root-level configuration
        var runOrleansGrains = configuration.GetValue<bool>("RunOrleansGrains", true);

        // Check environment variable as override
        var runOrleansGrainsEnv = Environment.GetEnvironmentVariable("RUN_ORLEANS_GRAINS");

        if (!string.IsNullOrEmpty(runOrleansGrainsEnv) &&
            bool.TryParse(runOrleansGrainsEnv, out var runOrleansGrainsFromEnv))
        {
            runOrleansGrains = runOrleansGrainsFromEnv;
        }

        // Allow disabling Orleans clustering entirely in case of issues
        var disableOrleansClusteringEnv = Environment.GetEnvironmentVariable("DISABLE_ORLEANS_CLUSTERING");
        var disableOrleansClustering = !string.IsNullOrEmpty(disableOrleansClusteringEnv) &&
                                       bool.TryParse(disableOrleansClusteringEnv, out var disabled) && disabled;

        // Get TASK_SLOT for multi-instance configuration
        var taskSlotEnv = Environment.GetEnvironmentVariable("TASK_SLOT");
        var taskSlot = 1; // Default to 1 if not provided
        if (!string.IsNullOrEmpty(taskSlotEnv) && int.TryParse(taskSlotEnv, out var parsedTaskSlot))
        {
            taskSlot = parsedTaskSlot;
        }

        // Calculate unique ports based on task slot
        var siloPort = 11111 + (taskSlot - 1) * 10; // 11111, 11121, 11131, etc.
        var gatewayPort = 30000 + (taskSlot - 1) * 10; // 30000, 30010, 30020, etc.
        var dashboardPort = 9999 + (taskSlot - 1); // 9999, 10000, 10001, etc.

        // Get hostname for clustering - prioritize external IP for multi-server setups
        var hostname = Environment.GetEnvironmentVariable("CAPROVER_SERVER_IP") ?? // CapRover server IP
                       Environment.GetEnvironmentVariable("EXTERNAL_IP") ?? // Custom external IP
                       Environment.GetEnvironmentVariable("HOSTNAME") ?? // Container hostname
                       Environment.GetEnvironmentVariable("COMPUTERNAME") ?? // Windows hostname
                       "localhost";

        // For Docker containers, always use localhost for same-server clustering
        IPAddress advertisedIP = IPAddress.Loopback; // Advertise as localhost for same-server clustering

        // Only use external IP if specifically provided for multi-server scenarios
        var externalIP = Environment.GetEnvironmentVariable("CAPROVER_SERVER_IP") ??
                         Environment.GetEnvironmentVariable("EXTERNAL_IP");

        if (!string.IsNullOrEmpty(externalIP) && IPAddress.TryParse(externalIP, out var parsedExternalIP))
        {
            advertisedIP = parsedExternalIP;
            Console.WriteLine($"Using external IP for multi-server clustering: {advertisedIP}");
        }
        else
        {
            Console.WriteLine($"Using localhost for same-server clustering: {advertisedIP}");
        }

        var postgreSqlConnectionString = configuration.GetSection("PostgreSql")["Orleans"];
        var siloRole = Environment.GetEnvironmentVariable("SILO_ROLE") ?? "Trading";

        Console.WriteLine($"Task Slot: {taskSlot}");
        Console.WriteLine($"Hostname: {hostname}");
        Console.WriteLine($"Advertised IP: {advertisedIP}");
        Console.WriteLine($"Role: {siloRole}");
        Console.WriteLine($"Silo port: {siloPort}");
        Console.WriteLine($"Gateway port: {gatewayPort}");
        Console.WriteLine($"Dashboard port: {dashboardPort}");

        return hostBuilder.UseOrleans(siloBuilder =>
            {
                // Configure clustering with improved networking or use localhost clustering if disabled
                if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
                {
                    siloBuilder
                        .UseAdoNetClustering(options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        })
                        .Configure<EndpointOptions>(options =>
                        {
                            // Advertise the specific IP for clustering
                            options.AdvertisedIPAddress = advertisedIP;
                            options.SiloPort = siloPort;
                            options.GatewayPort = gatewayPort;
                            options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, siloPort);
                            options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, gatewayPort);
                        })
                        .Configure<ClusterOptions>(options =>
                        {
                            options.ServiceId = "ManagingApp";
                            options.ClusterId = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Development";
                        })
                        .Configure<SiloOptions>(options =>
                        {
                            // Configure silo address for multi-server clustering
                            options.SiloName = $"ManagingApi-{taskSlot}-{siloRole}";
                            Console.WriteLine($"Configuring silo with role: {siloRole}");
                        });
                }
                else
                {
                    // Fallback to localhost clustering for testing or when database is unavailable
                    siloBuilder.UseLocalhostClustering(siloPort, gatewayPort);
                }

                // Conditionally configure reminder service based on flag
                if (runOrleansGrains && !disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
                {
                    siloBuilder.UseAdoNetReminderService(options =>
                    {
                        options.ConnectionString = postgreSqlConnectionString;
                        options.Invariant = "Npgsql";
                    });
                }

                // Configure networking - use specific IP for Docker containerized environments
                if (disableOrleansClustering)
                {
                    // Use localhost clustering when clustering is disabled
                    siloBuilder.ConfigureEndpoints(IPAddress.Loopback, siloPort, gatewayPort);
                }
                else if (string.IsNullOrEmpty(postgreSqlConnectionString))
                {
                    // In Docker/containerized environments, use endpoint configuration
                    siloBuilder.Configure<EndpointOptions>(options =>
                    {
                        // Advertise the specific IP for clustering
                        options.AdvertisedIPAddress = advertisedIP;
                        options.SiloPort = siloPort;
                        options.GatewayPort = gatewayPort;
                        options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, siloPort);
                        options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, gatewayPort);
                    });
                }

                siloBuilder
                    .Configure<MessagingOptions>(options =>
                    {
                        // Configure messaging for better reliability with increased timeouts
                        options.ResponseTimeout = TimeSpan.FromSeconds(60);
                        options.DropExpiredMessages = true;
                    })
                    .Configure<ClusterMembershipOptions>(options =>
                    {
                        // Configure cluster membership for better resilience
                        options.EnableIndirectProbes = true;
                        options.ProbeTimeout = TimeSpan.FromSeconds(10);
                        options.IAmAliveTablePublishTimeout = TimeSpan.FromSeconds(30);
                        options.MaxJoinAttemptTime = TimeSpan.FromSeconds(120);

                        // Improved settings for development environments with stale members
                        options.DefunctSiloCleanupPeriod = TimeSpan.FromMinutes(1);
                        options.DefunctSiloExpiration = TimeSpan.FromMinutes(2);
                    })
                    .Configure<GatewayOptions>(options =>
                    {
                        // Configure gateway with improved timeouts
                        options.GatewayListRefreshPeriod = TimeSpan.FromSeconds(60);
                    });

                // Conditionally configure grain execution based on flag
                if (runOrleansGrains)
                {
                    siloBuilder.Configure<GrainCollectionOptions>(options =>
                    {
                        // Enable grain collection for active grains
                        options.CollectionAge = TimeSpan.FromMinutes(10);
                        options.CollectionQuantum = TimeSpan.FromMinutes(1);
                    });
                }
                else
                {
                    // Disable grain execution completely
                    siloBuilder.Configure<GrainCollectionOptions>(options =>
                    {
                        // Disable grain collection to prevent grains from running
                        options.CollectionAge = TimeSpan.FromDays(365); // Very long collection age
                        options.CollectionQuantum = TimeSpan.FromDays(1); // Very long quantum
                    });
                }

                siloBuilder
                    .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information));

                // Only enable dashboard in development to avoid shutdown issues
                if (!isProduction)
                {
                    siloBuilder.UseDashboard(options =>
                    {
                        // Configure dashboard with proper shutdown handling and unique ports per instance
                        options.Port = dashboardPort;
                        options.HostSelf = true;
                        options.CounterUpdateIntervalMs = 10000; // 10 seconds
                        options.HideTrace = true; // Hide trace to reduce dashboard overhead
                        options.Host = "0.0.0.0"; // Allow external connections
                        options.Username = "admin";
                        options.Password = "admin";
                    });
                }

                // Configure grain storage - use ADO.NET for production or memory for fallback
                if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
                {
                    siloBuilder
                        .AddAdoNetGrainStorage("bot-store", options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        })
                        .AddAdoNetGrainStorage("registry-store", options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        })
                        .AddAdoNetGrainStorage("agent-store", options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        })
                        .AddAdoNetGrainStorage("platform-summary-store", options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        })
                        .AddAdoNetGrainStorage("candle-store", options =>
                        {
                            options.ConnectionString = postgreSqlConnectionString;
                            options.Invariant = "Npgsql";
                        });
                }
                else
                {
                    // Fallback to memory storage when database is unavailable
                    siloBuilder
                        .AddMemoryGrainStorage("bot-store")
                        .AddMemoryGrainStorage("registry-store")
                        .AddMemoryGrainStorage("agent-store")
                        .AddMemoryGrainStorage("platform-summary-store")
                        .AddMemoryGrainStorage("candle-store");
                }

                // Configure Orleans Streams for price data distribution
                siloBuilder.AddMemoryStreams("ManagingStreamProvider")
                    .AddMemoryGrainStorage("PubSubStore");

                siloBuilder
                    .ConfigureServices(services =>
                    {
                        // Register custom placement directors for role-based placement
                        services.AddPlacementDirector<ComputePlacementStrategy, ComputePlacementDirector>();
                        services.AddPlacementDirector<TradingPlacementStrategy, TradingPlacementDirector>();

                        // Register existing services for Orleans DI
                        // These will be available to grains through dependency injection
                        services.AddTransient<IExchangeService, ExchangeService>();
                        services.AddTransient<IAccountService, AccountService>();
                        services.AddTransient<ITradingService, TradingService>();
                        services.AddTransient<IMessengerService, MessengerService>();
                        // Use Singleton for InfluxDB repositories to prevent connection disposal issues in Orleans grains
                        services.AddSingleton<IInfluxDbRepository, InfluxDbRepository>();
                        services.AddSingleton<ICandleRepository, CandleRepository>();
                        services.AddSingleton<IAgentBalanceRepository, AgentBalanceRepository>();
                    });
            })
            ;
    }

    private static IServiceCollection AddApplication(this IServiceCollection services)
    {
        services.AddScoped<ITradingService, TradingService>();
        services.AddScoped<IScenarioService, ScenarioService>();
        services.AddScoped<IMoneyManagementService, MoneyManagementService>();
        services.AddScoped<IAccountService, AccountService>();
        services.AddScoped<IStatisticService, StatisticService>();
        services.AddScoped<IAgentService, AgentService>();
        services.AddScoped<ISettingsService, SettingsService>();
        services.AddScoped<IUserService, UserService>();
        services.AddScoped<IGeneticService, GeneticService>();
        services.AddScoped<IBotService, BotService>();
        services.AddScoped<IWorkerService, WorkerService>();
        services.AddScoped<ISynthPredictionService, SynthPredictionService>();
        services.AddScoped<ISynthApiClient, SynthApiClient>();
        services.AddScoped<IPricesService, PricesService>();
        services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
        services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();

        // Processors
        services.AddTransient<IBacktester, Backtester>();
        services.AddTransient<IExchangeProcessor, EvmProcessor>();

        services.AddTransient<ITradaoService, TradaoService>();
        services.AddTransient<IExchangeService, ExchangeService>();


        services.AddTransient<IPrivyService, PrivyService>();
        services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
        services.AddTransient<IWebhookService, WebhookService>();
        services.AddTransient<IKaigenService, KaigenService>();

        services.AddSingleton<IMessengerService, MessengerService>();
        services.AddSingleton<IDiscordService, DiscordService>();

        // Admin services
        services.AddSingleton<IAdminConfigurationService, AdminConfigurationService>();

        return services;
    }

    private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
    {
        // Database
        services.AddSingleton<IPostgreSqlSettings>(sp =>
            sp.GetRequiredService<IOptions<PostgreSqlSettings>>().Value);

        services.AddSingleton<IInfluxDbSettings>(sp =>
            sp.GetRequiredService<IOptions<InfluxDbSettings>>().Value);

        services.AddSingleton<IPrivySettings>(sp =>
            sp.GetRequiredService<IOptions<PrivySettings>>().Value);

        services.Configure<KaigenSettings>(configuration.GetSection("Kaigen"));

        // Evm 
        services.AddGbcFeed();
        services.AddUniswapV2();
        services.AddChainlink();
        services.AddChainlinkGmx();
        services.AddSingleton<IEvmManager, EvmManager>();

        // PostgreSql Repositories

        services.AddTransient<IAccountRepository, PostgreSqlAccountRepository>();
        services.AddTransient<IBacktestRepository, PostgreSqlBacktestRepository>();
        services.AddTransient<IGeneticRepository, PostgreSqlGeneticRepository>();
        services.AddTransient<ITradingRepository, PostgreSqlTradingRepository>();
        services.AddTransient<ISettingsRepository, PostgreSqlSettingsRepository>();
        services.AddTransient<IUserRepository, PostgreSqlUserRepository>();
        services.AddTransient<IAgentSummaryRepository, AgentSummaryRepository>();
        services.AddTransient<IStatisticRepository, PostgreSqlStatisticRepository>();
        services.AddTransient<IBotRepository, PostgreSqlBotRepository>();
        services.AddTransient<IWorkerRepository, PostgreSqlWorkerRepository>();
        services.AddTransient<ISynthRepository, PostgreSqlSynthRepository>();

        // InfluxDb Repositories - Use Singleton for proper connection management in Orleans grains
        services.AddSingleton<IInfluxDbRepository, InfluxDbRepository>();
        services.AddSingleton<ICandleRepository, CandleRepository>();
        services.AddSingleton<IAgentBalanceRepository, AgentBalanceRepository>();

        // Cache
        services.AddDistributedMemoryCache();
        services.AddTransient<ICacheService, CacheService>();
        services.AddSingleton<ITaskCache, TaskCache>();

        // Services
        services.AddTransient<ICacheService, CacheService>();
        services.AddSingleton<ITaskCache, TaskCache>();

        return services;
    }

    private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
    {
        if (configuration.GetValue<bool>("WorkerNotifyBundleBacktest", false))
        {
            services.AddHostedService<NotifyBundleBacktestWorker>();
        }

        // Price Workers
        if (configuration.GetValue<bool>("WorkerPricesFifteenMinutes", false))
        {
            services.AddHostedService<PricesFifteenMinutesWorker>();
        }

        if (configuration.GetValue<bool>("WorkerPricesOneHour", false))
        {
            services.AddHostedService<PricesOneHourWorker>();
        }

        if (configuration.GetValue<bool>("WorkerPricesFourHours", false))
        {
            services.AddHostedService<PricesFourHoursWorker>();
        }

        if (configuration.GetValue<bool>("WorkerPricesOneDay", false))
        {
            services.AddHostedService<PricesOneDayWorker>();
        }

        if (configuration.GetValue<bool>("WorkerPricesFiveMinutes", false))
        {
            services.AddHostedService<PricesFiveMinutesWorker>();
        }

        // Other Workers
        if (configuration.GetValue<bool>("WorkerSpotlight", false))
        {
            services.AddHostedService<SpotlightWorker>();
        }

        if (configuration.GetValue<bool>("WorkerTraderWatcher", false))
        {
            services.AddHostedService<TraderWatcher>();
        }

        if (configuration.GetValue<bool>("WorkerLeaderboard", false))
        {
            services.AddHostedService<LeaderboardWorker>();
        }

        if (configuration.GetValue<bool>("WorkerFundingRatesWatcher", false))
        {
            services.AddHostedService<FundingRatesWatcher>();
        }

        if (configuration.GetValue<bool>("WorkerGeneticAlgorithm", false))
        {
            services.AddHostedService<GeneticAlgorithmWorker>();
        }

        // DEPRECATED: BundleBacktestWorker has been replaced by BundleBacktestGrain
        // Bundle backtest processing is now handled by Orleans grain triggered directly from Backtester.cs
        // if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
        // {
        //     services.AddHostedService<BundleBacktestWorker>();
        // }

        return services;
    }

    private static IServiceCollection AddMediatR(this IServiceCollection services)
    {
        return services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()))
            .AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>))
            .AddTransient(typeof(IUnhandledExceptionBehaviour<,>), typeof(UnhandledExceptionBehaviour<,>));
    }

    private static IServiceCollection AddFluentValidation(this IServiceCollection services)
    {
        return services.AddValidatorsFromAssembly(ApplicationAssembly);
    }

    public static IWebHostBuilder SetupDiscordBot(this IWebHostBuilder builder)
    {
        return builder.ConfigureServices(services =>
        {
            services
                .AddSingleton<DiscordSettings>()
                .AddSingleton<DiscordSocketClient>()
                .AddSingleton<CommandService>()
                .AddHostedService<DiscordService>();
        });
    }
}