using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Application.Backtesting
{
    public class Backtester : IBacktester
    {
        private readonly IBacktestRepository _backtestRepository;
        private readonly ILogger<Backtester> _logger;
        private readonly IExchangeService _exchangeService;
        private readonly IBotFactory _botFactory;
        private readonly IScenarioService _scenarioService;
        private readonly IAccountService _accountService;

        public Backtester(
            IExchangeService exchangeService,
            IBotFactory botFactory,
            IBacktestRepository backtestRepository,
            ILogger<Backtester> logger, 
            IScenarioService scenarioService,
            IAccountService accountService)
        {
            _exchangeService = exchangeService;
            _botFactory = botFactory;
            _backtestRepository = backtestRepository;
            _logger = logger;
            _scenarioService = scenarioService;
            _accountService = accountService;
        }

        public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
        {
            var simplebot = _botFactory.CreateSimpleBot("scenario", workflow);
            Backtest result = null;
            if (save && result != null)
            {
                // Simple bot backtest not implemented yet, would need user
                // _backtestRepository.InsertBacktestForUser(null, result);
            }

            return result;
        }

        /// <summary>
        /// Runs a unified trading bot backtest with the specified configuration and date range.
        /// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
        /// </summary>
        /// <param name="config">The trading bot configuration</param>
        /// <param name="startDate">The start date for the backtest</param>
        /// <param name="endDate">The end date for the backtest</param>
        /// <param name="user">The user running the backtest</param>
        /// <param name="save">Whether to save the backtest results</param>
        /// <param name="initialCandles">Optional pre-loaded candles</param>
        /// <returns>The backtest results</returns>
        public async Task<Backtest> RunTradingBotBacktest(
            TradingBotConfig config,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool save = false,
            List<Candle>? initialCandles = null)
        {
            var account = await GetAccountFromConfig(config);
            
            // Set FlipPosition based on BotType
            config.FlipPosition = config.BotType == BotType.FlippingBot;
            
            var tradingBot = _botFactory.CreateBacktestTradingBot(config);
            tradingBot.LoadScenario(config.ScenarioName);
            tradingBot.User = user;
            await tradingBot.LoadAccount();
            
            var candles = initialCandles ?? GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
            var result = GetBacktestingResult(config, tradingBot, candles);

            if (user != null)
            {
                result.User = user;
            }

            // Set start and end dates
            result.StartDate = startDate;
            result.EndDate = endDate;

            if (save)
            {
                _backtestRepository.InsertBacktestForUser(user, result);
            }

            return result;
        }

        /// <summary>
        /// Runs a unified trading bot backtest with pre-loaded candles.
        /// Automatically handles ScalpingBot and FlippingBot behavior based on config.BotType.
        /// </summary>
        /// <param name="config">The trading bot configuration</param>
        /// <param name="candles">The candles to use for backtesting</param>
        /// <param name="user">The user running the backtest</param>
        /// <returns>The backtest results</returns>
        public async Task<Backtest> RunTradingBotBacktest(
            TradingBotConfig config,
            List<Candle> candles,
            User user = null)
        {
            var account = await GetAccountFromConfig(config);
            
            // Set FlipPosition based on BotType
            config.FlipPosition = config.BotType == BotType.FlippingBot;
            
            var tradingBot = _botFactory.CreateBacktestTradingBot(config);
            tradingBot.LoadScenario(config.ScenarioName);
            tradingBot.User = user;
            await tradingBot.LoadAccount();

            var result = GetBacktestingResult(config, tradingBot, candles);

            if (user != null)
            {
                result.User = user;
            }

            return result;
        }

        // Legacy methods - maintained for backward compatibility
        public async Task<Backtest> RunScalpingBotBacktest(
            TradingBotConfig config,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool save = false,
            List<Candle>? initialCandles = null)
        {
            config.BotType = BotType.ScalpingBot; // Ensure correct type
            return await RunTradingBotBacktest(config, startDate, endDate, user, save, initialCandles);
        }

        public async Task<Backtest> RunFlippingBotBacktest(
            TradingBotConfig config,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool save = false,
            List<Candle>? initialCandles = null)
        {
            config.BotType = BotType.FlippingBot; // Ensure correct type
            return await RunTradingBotBacktest(config, startDate, endDate, user, save, initialCandles);
        }

        public async Task<Backtest> RunScalpingBotBacktest(
            TradingBotConfig config,
            List<Candle> candles,
            User user = null)
        {
            config.BotType = BotType.ScalpingBot; // Ensure correct type
            return await RunTradingBotBacktest(config, candles, user);
        }

        public async Task<Backtest> RunFlippingBotBacktest(
            TradingBotConfig config,
            List<Candle> candles,
            User user = null)
        {
            config.BotType = BotType.FlippingBot; // Ensure correct type
            return await RunTradingBotBacktest(config, candles, user);
        }

        private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
        {
            // Use the account service to get the actual account
            var account = await _accountService.GetAccount(config.AccountName, false, false);
            if (account != null)
            {
                return account;
            }

            // Fallback: create a basic account structure if not found
            return new Account 
            { 
                Name = config.AccountName, 
                Exchange = TradingExchanges.GmxV2 // Default exchange, should be configurable
            };
        }

        private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
            DateTime startDate, DateTime endDate)
        {
            List<Candle> candles;

            // Use specific date range
            candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
                startDate, timeframe, endDate).Result;

            if (candles == null || candles.Count == 0)
                throw new Exception($"No candles for {ticker} on {account.Exchange}");

            return candles;
        }

        private Backtest GetBacktestingResult(
            TradingBotConfig config,
            ITradingBot bot,
            List<Candle> candles)
        {
            if (candles == null || candles.Count == 0)
            {
                throw new Exception("No candle to backtest");
            }

            bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance);
            foreach (var candle in candles)
            {
                bot.OptimizedCandles.Enqueue(candle);
                bot.Candles.Add(candle);
                bot.Run();
            }

            bot.Candles = new HashSet<Candle>(candles);
            bot.UpdateStrategiesValues();

            var strategies = _scenarioService.GetStrategies();
            var strategiesValues = GetStrategiesValues(strategies, candles);

            var finalPnl = bot.GetProfitAndLoss();
            var winRate = bot.GetWinRate();
            var optimizedMoneyManagement =
                TradingBox.GetBestMoneyManagement(candles, bot.Positions, config.MoneyManagement);
            var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
            var growthPercentage = TradingHelpers.GetGrowthFromInitalBalance(config.BotTradingBalance, finalPnl);
            var hodlPercentage = TradingHelpers.GetHodlPercentage(candles[0], candles.Last());

            var scoringParams = new BacktestScoringParams(
                sharpeRatio: (double)stats.SharpeRatio,
                maxDrawdownPc: (double)stats.MaxDrawdownPc,
                growthPercentage: (double)growthPercentage,
                hodlPercentage: (double)hodlPercentage,
                winRate: winRate,
                totalPnL: (double)finalPnl,
                fees: (double)bot.GetTotalFees(),
                tradeCount: bot.Positions.Count,
                maxDrawdownRecoveryTime: stats.MaxDrawdownRecoveryTime
            );

            var score = BacktestScorer.CalculateTotalScore(scoringParams);

            var result = new Backtest(config, bot.Positions, bot.Signals.ToList(), candles)
            {
                FinalPnl = finalPnl,
                WinRate = winRate,
                GrowthPercentage = growthPercentage,
                HodlPercentage = hodlPercentage,
                Fees = bot.GetTotalFees(),
                WalletBalances = bot.WalletBalances.ToList(),
                Statistics = stats,
                OptimizedMoneyManagement = optimizedMoneyManagement,
                StrategiesValues = AggregateValues(strategiesValues, bot.StrategiesValues),
                Score = score
            };

            return result;
        }

        private Dictionary<StrategyType, StrategiesResultBase> AggregateValues(
            Dictionary<StrategyType, StrategiesResultBase> strategiesValues,
            Dictionary<StrategyType, StrategiesResultBase> botStrategiesValues)
        {
            // Foreach strategy type, only retrieve the values where the strategy is not present already in the bot
            // Then, add the values to the bot values

            var result = new Dictionary<StrategyType, StrategiesResultBase>();
            foreach (var strategy in strategiesValues)
            {
                // if (!botStrategiesValues.ContainsKey(strategy.Key))
                // {
                //     result[strategy.Key] = strategy.Value;
                // }else
                // {
                //     result[strategy.Key] = botStrategiesValues[strategy.Key];
                // }

                result[strategy.Key] = strategy.Value;
            }

            return result;
        }

        private Dictionary<StrategyType, StrategiesResultBase> GetStrategiesValues(IEnumerable<Strategy> strategies,
            List<Candle> candles)
        {
            var strategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();
            var fixedCandles = new FixedSizeQueue<Candle>(10000);
            foreach (var candle in candles)
            {
                fixedCandles.Enqueue(candle);
            }

            foreach (var strategy in strategies)
            {
                try
                {
                    var s = ScenarioHelpers.BuildStrategy(strategy, 10000);
                    s.Candles = fixedCandles;
                    strategiesValues[strategy.Type] = s.GetStrategyValues();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }

            return strategiesValues;
        }


        public bool DeleteBacktest(string id)
        {
            try
            {
                // Since we no longer have a general DeleteBacktestById method in the repository,
                // this should be implemented using DeleteBacktestByIdForUser with null
                _backtestRepository.DeleteBacktestByIdForUser(null, id);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public bool DeleteBacktests()
        {
            try
            {
                // Since we no longer have a general DeleteAllBacktests method in the repository,
                // this should be implemented using DeleteAllBacktestsForUser with null
                _backtestRepository.DeleteAllBacktestsForUser(null);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public async Task<IEnumerable<Backtest>> GetBacktestsByUser(User user)
        {
            var backtests = _backtestRepository.GetBacktestsByUser(user).ToList();

            // For each backtest, ensure candles are loaded
            foreach (var backtest in backtests)
            {
                // If the backtest has no candles or only a few sample candles, retrieve them
                if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
                {
                    try
                    {
                        var candles = await _exchangeService.GetCandlesInflux(
                            user.Accounts.First().Exchange,
                            backtest.Config.Ticker,
                            backtest.StartDate,
                            backtest.Config.Timeframe,
                            backtest.EndDate);

                        if (candles != null && candles.Count > 0)
                        {
                            backtest.Candles = candles;
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", backtest.Id);
                        // Continue with the next backtest if there's an error
                    }
                }
            }

            return backtests;
        }

        public Backtest GetBacktestByIdForUser(User user, string id)
        {
            // Get the backtest from the repository
            var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);

            if (backtest == null)
                return null;

            // If the backtest has no candles or only a few sample candles, retrieve them
            if (backtest.Candles == null || backtest.Candles.Count == 0 || backtest.Candles.Count < 10)
            {
                try
                {
                    // Get the account
                    var account = new Account
                        { Name = backtest.Config.AccountName, Exchange = TradingExchanges.Evm };

                    // Use the stored start and end dates to retrieve candles
                    var candles = _exchangeService.GetCandlesInflux(
                        account.Exchange,
                        backtest.Config.Ticker,
                        backtest.StartDate,
                        backtest.Config.Timeframe,
                        backtest.EndDate).Result;

                    if (candles != null && candles.Count > 0)
                    {
                        backtest.Candles = candles;
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to retrieve candles for backtest {Id}", id);
                    // Return the backtest without candles if there's an error
                }
            }

            return backtest;
        }

        public bool DeleteBacktestByUser(User user, string id)
        {
            try
            {
                _backtestRepository.DeleteBacktestByIdForUser(user, id);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }

        public bool DeleteBacktestsByUser(User user)
        {
            try
            {
                _backtestRepository.DeleteAllBacktestsForUser(user);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
                return false;
            }
        }
    }
}