using System.Collections.Concurrent;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Bots;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Application.ManageBot
{
    public class BotService : IBotService
    {
        private readonly IBotRepository _botRepository;
        private readonly IExchangeService _exchangeService;
        private readonly IMessengerService _messengerService;
        private readonly IAccountService _accountService;
        private readonly ILogger<TradingBot> _tradingBotLogger;
        private readonly ITradingService _tradingService;
        private readonly IMoneyManagementService _moneyManagementService;
        private readonly IUserService _userService;

        private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
            new ConcurrentDictionary<string, BotTaskWrapper>();

        public BotService(IBotRepository botRepository, IExchangeService exchangeService,
            IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
            ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
        {
            _botRepository = botRepository;
            _exchangeService = exchangeService;
            _messengerService = messengerService;
            _accountService = accountService;
            _tradingBotLogger = tradingBotLogger;
            _tradingService = tradingService;
            _moneyManagementService = moneyManagementService;
            _userService = userService;
        }

        public BotBackup GetBotBackup(string identifier)
        {
            return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
        }

        public void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data)
        {
            var backup = GetBotBackup(identifier);

            if (backup != null)
            {
                backup.LastStatus = status;
                backup.Data = data;
                _botRepository.UpdateBackupBot(backup);
            }
            else
            {
                var botBackup = new BotBackup
                {
                    LastStatus = status,
                    User = user,
                    Identifier = identifier,
                    BotType = botType,
                    Data = data
                };

                _botRepository.InsertBotAsync(botBackup);
            }
        }

        public class BotTaskWrapper
        {
            public Task Task { get; private set; }
            public Type BotType { get; private set; }
            public object BotInstance { get; private set; } // Add this line

            public BotTaskWrapper(Task task, Type botType, object botInstance) // Update constructor
            {
                Task = task;
                BotType = botType;
                BotInstance = botInstance; // Set the bot instance
            }
        }

        public void AddSimpleBotToCache(IBot bot)
        {
            var botTask =
                new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot); // Pass bot as the instance
            _botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
        }

        public void AddTradingBotToCache(ITradingBot bot)
        {
            var botTask = new BotTaskWrapper(Task.Run(() => bot.Start()), bot.GetType(), bot);
            _botTasks.AddOrUpdate(bot.Identifier, botTask, (key, existingVal) => botTask);
        }

        public List<ITradingBot> GetActiveBots()
        {
            var bots = _botTasks.Values
                .Where(wrapper => typeof(ITradingBot).IsAssignableFrom(wrapper.BotType))
                .Select(wrapper => wrapper.BotInstance as ITradingBot)
                .Where(bot => bot != null)
                .ToList();

            return bots;
        }

        public IEnumerable<BotBackup> GetSavedBots()
        {
            return _botRepository.GetBots();
        }

        public void StartBotFromBackup(BotBackup backupBot)
        {
            object bot = null;
            Task botTask = null;

            switch (backupBot.BotType)
            {
                case BotType.ScalpingBot:
                case BotType.FlippingBot:
                    var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
                    var scalpingMoneyManagement =
                        _moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
                    
                    // Create config from backup data
                    var scalpingConfig = new TradingBotConfig
                    {
                        AccountName = scalpingBotData.AccountName,
                        MoneyManagement = scalpingMoneyManagement,
                        Ticker = scalpingBotData.Ticker,
                        ScenarioName = scalpingBotData.ScenarioName,
                        Timeframe = scalpingBotData.Timeframe,
                        IsForWatchingOnly = scalpingBotData.IsForWatchingOnly,
                        BotTradingBalance = scalpingBotData.BotTradingBalance,
                        BotType = scalpingBotData.BotType,
                        Name = scalpingBotData.Name,
                        CooldownPeriod = scalpingBotData.CooldownPeriod,
                        MaxLossStreak = scalpingBotData.MaxLossStreak,
                        MaxPositionTimeHours = scalpingBotData.MaxPositionTimeHours == 0m ? null : scalpingBotData.MaxPositionTimeHours,
                        FlipOnlyWhenInProfit = scalpingBotData.FlipOnlyWhenInProfit,
                        IsForBacktest = false,
                        FlipPosition = false,
                        CloseEarlyWhenProfitable = scalpingBotData.CloseEarlyWhenProfitable
                    };
                    
                    bot = CreateTradingBot(scalpingConfig);
                    botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
                    break;
            }

            if (bot != null && botTask != null)
            {
                var botWrapper = new BotTaskWrapper(botTask, bot.GetType(), bot);
                _botTasks.AddOrUpdate(backupBot.Identifier, botWrapper, (key, existingVal) => botWrapper);
            }
        }

        private void InitBot(ITradingBot bot, BotBackup backupBot)
        {
            var user = _userService.GetUser(backupBot.User.Name);
            bot.User = user;
            // Config is already set correctly from backup data, so we only need to restore signals, positions, etc.
            bot.LoadBackup(backupBot);
            bot.Start();
        }

        public IBot CreateSimpleBot(string botName, Workflow workflow)
        {
            return new SimpleBot(botName, _tradingBotLogger, workflow, this);
        }

        public async Task<string> StopBot(string identifier)
        {
            if (_botTasks.TryGetValue(identifier, out var botWrapper))
            {
                if (botWrapper.BotInstance is IBot bot)
                {
                    await Task.Run(() =>
                        bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
                    return bot.GetStatus();
                }
            }

            return BotStatus.Down.ToString();
        }

        public async Task<bool> DeleteBot(string identifier)
        {
            if (_botTasks.TryRemove(identifier, out var botWrapper))
            {
                try
                {
                    if (botWrapper.BotInstance is IBot bot)
                    {
                        await Task.Run(() =>
                            bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
                    }

                    await _botRepository.DeleteBotBackup(identifier);
                    return true;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    return false;
                }
            }

            return false;
        }

        public Task<string> RestartBot(string identifier)
        {
            if (_botTasks.TryGetValue(identifier, out var botWrapper))
            {
                if (botWrapper.BotInstance is IBot bot)
                {
                    bot.Restart();
                    return Task.FromResult(bot.GetStatus());
                }
            }

            return Task.FromResult(BotStatus.Down.ToString());
        }

        public void ToggleIsForWatchingOnly(string identifier)
        {
            if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
                botTaskWrapper.BotInstance is ITradingBot tradingBot)
            {
                tradingBot.ToggleIsForWatchOnly().Wait();
            }
        }

        /// <summary>
        /// Updates the configuration of an existing bot without stopping and restarting it.
        /// </summary>
        /// <param name="identifier">The bot identifier</param>
        /// <param name="newConfig">The new configuration to apply</param>
        /// <returns>True if the configuration was successfully updated, false otherwise</returns>
        public async Task<bool> UpdateBotConfiguration(string identifier, TradingBotConfig newConfig)
        {
            if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
                botTaskWrapper.BotInstance is TradingBot tradingBot)
            {
                // Check if the bot name is changing
                if (newConfig.Name != identifier && !string.IsNullOrEmpty(newConfig.Name))
                {
                    // Check if new name already exists
                    if (_botTasks.ContainsKey(newConfig.Name))
                    {
                        return false; // New name already in use
                    }

                    // Update the bot configuration first
                    var updateResult = await tradingBot.UpdateConfiguration(newConfig, allowNameChange: true);
                    
                    if (updateResult)
                    {
                        // Update the dictionary key
                        if (_botTasks.TryRemove(identifier, out var removedWrapper))
                        {
                            _botTasks.TryAdd(newConfig.Name, removedWrapper);
                            
                            // Update the backup with the new identifier
                            if (!newConfig.IsForBacktest)
                            {
                                // Delete old backup
                                await _botRepository.DeleteBotBackup(identifier);
                                // Save new backup will be handled by the bot's SaveBackup method
                            }
                        }
                    }
                    
                    return updateResult;
                }
                else
                {
                    // No name change, just update configuration
                    return await tradingBot.UpdateConfiguration(newConfig);
                }
            }

            return false;
        }

        

        public ITradingBot CreateTradingBot(TradingBotConfig config)
        {
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }

        public ITradingBot CreateBacktestTradingBot(TradingBotConfig config)
        {
            config.IsForBacktest = true;
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }

        public ITradingBot CreateScalpingBot(TradingBotConfig config)
        {
            config.FlipPosition = false;
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }

        public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
        {
            config.IsForBacktest = true;
            config.FlipPosition = false;
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }

        public ITradingBot CreateFlippingBot(TradingBotConfig config)
        {
            config.FlipPosition = true;
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }

        public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
        {
            config.IsForBacktest = true;
            config.FlipPosition = true;
            return new TradingBot(
                _exchangeService,
                _tradingBotLogger,
                _tradingService,
                _accountService,
                _messengerService,
                this,
                config);
        }
    }
}