using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
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;

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

        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;
        }

        public async Task<Backtest> RunScalpingBotBacktest(
            Account account,
            MoneyManagement moneyManagement,
            Ticker ticker,
            Scenario scenario,
            Timeframe timeframe,
            decimal balance,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool isForWatchingOnly = false,
            bool save = false,
            List<Candle> initialCandles = null)
        {
            var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
                timeframe, isForWatchingOnly, balance);
            scalpingBot.LoadScenario(scenario.Name);
            await scalpingBot.LoadAccount();
            var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
            var result = GetBacktestingResult(ticker, scenario, timeframe, scalpingBot, candles, balance, account,
                moneyManagement);

            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;
        }

        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;
        }

        public async Task<Backtest> RunFlippingBotBacktest(
            Account account,
            MoneyManagement moneyManagement,
            Ticker ticker,
            Scenario scenario,
            Timeframe timeframe,
            decimal balance,
            DateTime startDate,
            DateTime endDate,
            User user = null,
            bool isForWatchingOnly = false,
            bool save = false,
            List<Candle> initialCandles = null)
        {
            var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
                timeframe, false, balance);
            flippingBot.LoadScenario(scenario.Name);
            await flippingBot.LoadAccount();

            var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
            var result = GetBacktestingResult(ticker, scenario, timeframe, flippingBot, candles, balance, account,
                moneyManagement);

            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;
        }

        public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement,
            Scenario scenario,
            Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
        {
            var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
            var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
                timeframe, false, balance);
            bot.LoadScenario(scenario.Name);
            await bot.LoadAccount();

            var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
                moneyManagement);

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

            return result;
        }

        public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement,
            Scenario scenario,
            Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
        {
            var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
            var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
                timeframe, false, balance);
            bot.LoadScenario(scenario.Name);
            await bot.LoadAccount();

            var result = GetBacktestingResult(ticker, scenario, timeframe, bot, candles, balance, account,
                moneyManagement);

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

            return result;
        }

        private Backtest GetBacktestingResult(
            Ticker ticker,
            Scenario scenario,
            Timeframe timeframe,
            ITradingBot bot,
            List<Candle> candles,
            decimal balance,
            Account account,
            MoneyManagement moneyManagement)
        {
            if (candles == null || candles.Count == 0)
            {
                throw new Exception("No candle to backtest");
            }

            bot.WalletBalances.Add(candles.FirstOrDefault().Date, balance);
            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, moneyManagement);
            var stats = TradingHelpers.GetStatistics(bot.WalletBalances);
            var growthPercentage = TradingHelpers.GetGrowthFromInitalBalance(balance, 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(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles,
                bot.BotType, account.Name)
            {
                FinalPnl = finalPnl,
                WinRate = winRate,
                GrowthPercentage = growthPercentage,
                HodlPercentage = hodlPercentage,
                Fees = bot.GetTotalFees(),
                WalletBalances = bot.WalletBalances.ToList(),
                Statistics = stats,
                OptimizedMoneyManagement = optimizedMoneyManagement,
                MoneyManagement = moneyManagement,
                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.Ticker,
                            backtest.StartDate,
                            backtest.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.AccountName, Exchange = TradingExchanges.Binance };

                    // Use the stored start and end dates to retrieve candles
                    var candles = _exchangeService.GetCandlesInflux(
                        account.Exchange,
                        backtest.Ticker,
                        backtest.StartDate,
                        backtest.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;
            }
        }
    }
}