using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Common;
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
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.Trades;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Application.Bots;

public class TradingBot : Bot, ITradingBot
{
    public readonly ILogger<TradingBot> Logger;
    public readonly IExchangeService ExchangeService;
    public readonly IMessengerService MessengerService;
    public readonly IAccountService AccountService;
    private readonly ITradingService TradingService;
    private readonly IBotService BotService;

    public Account Account { get; set; }
    public HashSet<IStrategy> Strategies { get; set; }
    public FixedSizeQueue<Candle> OptimizedCandles { get; set; }
    public HashSet<Candle> Candles { get; set; }
    public HashSet<Signal> Signals { get; set; }
    public List<Position> Positions { get; set; }
    public Ticker Ticker { get; set; }
    public string ScenarioName { get; set; }
    public string AccountName { get; set; }
    public MoneyManagement MoneyManagement { get; set; }
    public Timeframe Timeframe { get; set; }
    public bool IsForBacktest { get; set; }
    public DateTime PreloadSince { get; set; }
    public bool IsForWatchingOnly { get; set; }
    public bool FlipPosition { get; set; }
    public int PreloadedCandlesCount { get; set; }
    public BotType BotType { get; set; }
    public decimal Fee { get; set; }
    public Scenario Scenario { get; set; }
    public Dictionary<DateTime, decimal> WalletBalances { get; set; }
    public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
    public DateTime StartupTime { get; set; }

    /// <summary>
    /// The dedicated trading balance for this bot in USD
    /// </summary>
    public decimal BotTradingBalance { get; set; }

    public TradingBot(
        string accountName,
        MoneyManagement moneyManagement,
        string name,
        Ticker ticker,
        string scenarioName,
        IExchangeService exchangeService,
        ILogger<TradingBot> logger,
        ITradingService tradingService,
        Timeframe timeframe,
        IAccountService accountService,
        IMessengerService messengerService,
        IBotService botService,
        decimal initialTradingBalance,
        bool isForBacktest = false,
        bool isForWatchingOnly = false,
        bool flipPosition = false)
        : base(name)
    {
        ExchangeService = exchangeService;
        AccountService = accountService;
        MessengerService = messengerService;
        TradingService = tradingService;
        BotService = botService;

        if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
        {
            throw new ArgumentException(
                $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}",
                nameof(initialTradingBalance));
        }

        IsForWatchingOnly = isForWatchingOnly;
        FlipPosition = flipPosition;
        AccountName = accountName;
        MoneyManagement = moneyManagement;
        Ticker = ticker;
        ScenarioName = scenarioName;
        Timeframe = timeframe;
        IsForBacktest = isForBacktest;
        Logger = logger;
        BotTradingBalance = initialTradingBalance;

        Strategies = new HashSet<IStrategy>();
        Signals = new HashSet<Signal>();
        OptimizedCandles = new FixedSizeQueue<Candle>(600);
        Candles = new HashSet<Candle>();
        Positions = new List<Position>();
        WalletBalances = new Dictionary<DateTime, decimal>();
        StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();

        if (!isForBacktest)
        {
            Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe);
            PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe);
        }
    }

    public override async void Start()
    {
        base.Start();
        // Load account synchronously
        await LoadAccount();

        if (!IsForBacktest)
        {
            LoadScenario(ScenarioName);
            await PreloadCandles();
            await CancelAllOrders();

            try
            {
                // await MessengerService.SendMessage(
                //     $"Hey everyone! I'm about to start {Name}. 🚀\n" +
                //     $"I'll post an update here each time a signal is triggered by the following strategies: {string.Join(", ", Strategies.Select(s => s.Name))}."
                // );
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, ex.Message);
            }

            await InitWorker(Run);
        }

        // Fee = TradingService.GetFee(Account, IsForBacktest);
    }

    public async Task LoadAccount()
    {
        var account = await AccountService.GetAccount(AccountName, false, false);
        if (account == null)
        {
            Logger.LogWarning($"No account found for this {AccountName}");
            Stop();
        }
        else
        {
            Account = account;
            // Set the User property from the account
            User = account.User;
        }
    }

    public void LoadScenario(string scenarioName)
    {
        var scenario = TradingService.GetScenarioByName(scenarioName);
        if (scenario == null)
        {
            Logger.LogWarning("No scenario found for this scenario name");
            Stop();
        }
        else
        {
            Scenario = scenario;
            LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario));
        }
    }

    public void LoadStrategies(IEnumerable<IStrategy> strategies)
    {
        foreach (var strategy in strategies)
        {
            Strategies.Add(strategy);
        }
    }

    public async Task Run()
    {
        if (!IsForBacktest)
        {
            // Check broker balance before running
            var balance = await ExchangeService.GetBalance(Account, false);
            if (balance < Constants.GMX.Config.MinimumPositionAmount)
            {
                await LogWarning(
                    $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot {Identifier} and saving backup.");
                SaveBackup();
                Stop();
                return;
            }

            Logger.LogInformation($"____________________{Name}____________________");
            Logger.LogInformation(
                $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
        }

        var previousLastCandle = OptimizedCandles.LastOrDefault();

        if (!IsForBacktest)
            await UpdateCandles();

        var currentLastCandle = OptimizedCandles.LastOrDefault();

        if (currentLastCandle != previousLastCandle || IsForBacktest)
            await UpdateSignals(OptimizedCandles);
        else
            Logger.LogInformation($"No need to update signals for {Ticker}");

        if (!IsForWatchingOnly)
            await ManagePositions();

        if (!IsForBacktest)
        {
            SaveBackup();
            UpdateStrategiesValues();
        }

        await UpdateWalletBalances();
        if (OptimizedCandles.Count % 100 == 0) // Log every 10th execution
        {
            Logger.LogInformation($"Candle date : {OptimizedCandles.Last().Date:u}");
            Logger.LogInformation($"Signals : {Signals.Count}");
            Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
            Logger.LogInformation($"Positions : {Positions.Count}");
            Logger.LogInformation("__________________________________________________");
        }
    }

    public void UpdateStrategiesValues()
    {
        foreach (var strategy in Strategies)
        {
            StrategiesValues[strategy.Type] = ((Strategy)strategy).GetStrategyValues();
        }
    }

    private async Task PreloadCandles()
    {
        if (OptimizedCandles.Any())
            return;

        var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);

        foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
        {
            if (!OptimizedCandles.Any(c => c.Date == candle.Date))
            {
                OptimizedCandles.Enqueue(candle);
                Candles.Add(candle);
                await UpdateSignals(OptimizedCandles);
            }
        }

        PreloadedCandlesCount = OptimizedCandles.Count();
    }

    private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
    {
        var signal = TradingBox.GetSignal(candles.ToHashSet(), Strategies, Signals, Scenario.LoopbackPeriod);
        if (signal == null) return;

        signal.User = Account.User;
        await AddSignal(signal);
    }


    private async Task AddSignal(Signal signal)
    {
        // if (!IsForBacktest)
        //     TradingService.InsertSignal(signal);

        if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
            signal.Status = SignalStatus.Expired;

        Signals.Add(signal);

        var signalText = $"{ScenarioName} trigger a signal. Signal told you " +
                         $"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";

        Logger.LogInformation(signalText);

        if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0)
        {
            await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe);
        }
    }

    protected async Task UpdateCandles()
    {
        if (OptimizedCandles.Count == 0 || ExecutionCount == 0)
            return;

        var lastCandle = OptimizedCandles.Last();
        var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);

        foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
        {
            OptimizedCandles.Enqueue(candle);
            Candles.Add(candle);
        }
    }

    private async Task ManagePositions()
    {
        // Update position
        foreach (var signal in Signals.Where(s => s.Status == SignalStatus.PositionOpen))
        {
            var positionForSignal = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier);
            if (positionForSignal == null)
            {
                await LogInformation($"Cannot find position for signal {signal.Identifier}");
            }
            else
            {
                await UpdatePosition(signal, positionForSignal);
            }
        }

        // Open position for signal waiting for a position open
        foreach (var signal in Signals.Where(s => s.Status == SignalStatus.WaitingForPosition))
        {
            Task.Run(() => OpenPosition(signal)).GetAwaiter().GetResult();
        }
    }

    private async Task UpdateWalletBalances()
    {
        var lastCandle = OptimizedCandles.LastOrDefault();
        if (lastCandle == null) return;

        var date = lastCandle.Date;

        if (WalletBalances.Count == 0)
        {
            // WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
            WalletBalances[date] = BotTradingBalance;
            return;
        }

        if (!WalletBalances.ContainsKey(date))
        {
            var previousBalance = WalletBalances.First().Value;
            WalletBalances[date] = previousBalance + GetProfitAndLoss();
        }
    }


    private async Task UpdatePosition(Signal signal, Position positionForSignal)
    {
        try
        {
            Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");

            var position = IsForBacktest
                ? positionForSignal
                : TradingService.GetPositionByIdentifier(positionForSignal.Identifier);

            var positionsExchange = IsForBacktest
                ? new List<Position> { position }
                : await TradingService.GetBrokerPositions(Account);

            if (!IsForBacktest)
            {
                var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Ticker);
                if (brokerPosition != null)
                {
                    UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);

                    if (position.Status.Equals(PositionStatus.New))
                    {
                        await SetPositionStatus(position.SignalIdentifier, PositionStatus.Filled);
                    }

                    position = brokerPosition;
                }
                else
                {
                    // No position, position close on the broker 
                    if (!position.Status.Equals(PositionStatus.New))
                    {
                        // Setup the previous status of the position
                        position.Status = PositionStatus.Filled;
                    }
                }
            }

            if (position.Status == PositionStatus.New)
            {
                var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
                if (orders.Any())
                {
                    await LogInformation(
                        $"Cannot update Position. Position is still waiting for opening. There is {orders.Count()} open orders.");
                }
                else
                {
                    await LogWarning(
                        $"Cannot update Position. No position on exchange and no orders. Position {signal.Identifier} might be closed already.");
                    await HandleClosedPosition(positionForSignal);
                }
            }
            else if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
            {
                await HandleClosedPosition(positionForSignal);
            }
            else if (position.Status == (PositionStatus.Filled | PositionStatus.PartiallyFilled))
            {
                // For backtesting or force close if not executed on exchange : 
                // check if position is still open
                // Check status, if still open update the status of the position

                // Position might be partially filled, meaning that TPSL havent been sended yet
                // But the position might already been closed by the exchange so we have to check should be closed
                var lastCandle = IsForBacktest
                    ? OptimizedCandles.Last()
                    : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);

                if (positionForSignal.OriginDirection == TradeDirection.Long)
                {
                    if (positionForSignal.StopLoss.Price >= lastCandle.Low)
                    {
                        await LogInformation(
                            $"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
                            positionForSignal.StopLoss.Price, true);
                        positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
                    }
                    else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
                             && positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
                    {
                        await LogInformation(
                            $"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
                            positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
                        positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
                    }
                    else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
                    {
                        await LogInformation(
                            $"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
                            positionForSignal.TakeProfit2.Price, true);
                        positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
                    }
                    else
                    {
                        Logger.LogInformation(
                            $"Position {signal.Identifier} don't need to be update. Position still opened");
                    }
                }

                if (positionForSignal.OriginDirection == TradeDirection.Short)
                {
                    if (positionForSignal.StopLoss.Price <= lastCandle.High)
                    {
                        await LogInformation(
                            $"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss,
                            positionForSignal.StopLoss.Price, true);
                        positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
                    }
                    else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
                             && positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
                    {
                        await LogInformation(
                            $"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1,
                            positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
                        positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
                    }
                    else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
                    {
                        await LogInformation(
                            $"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
                        await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2,
                            positionForSignal.TakeProfit2.Price, true);
                        positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
                    }
                    else
                    {
                        Logger.LogInformation(
                            $"Position {signal.Identifier} don't need to be update. Position still opened");
                    }
                }
            }
            else if (position.Status == (PositionStatus.Rejected | PositionStatus.Canceled))
            {
                await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
                // if position is not open
                // Re-open the trade for the signal only if signal still up 
                if (signal.Status == SignalStatus.PositionOpen)
                {
                    Logger.LogInformation($"Try to re-open position");
                    await OpenPosition(signal);
                }
            }
        }
        catch (Exception ex)
        {
            await LogWarning($"Cannot update position {positionForSignal.Identifier}: {ex.Message}");
            SentrySdk.CaptureException(ex);
            return;
        }
    }


    private async Task OpenPosition(Signal signal)
    {
        // Check if a position is already open
        Logger.LogInformation($"Opening position for {signal.Identifier}");

        var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
                                                           && p.SignalIdentifier != signal.Identifier);

        var lastPrice = IsForBacktest
            ? OptimizedCandles.Last().Close
            : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);

        // If position open
        if (openedPosition != null)
        {
            var previousSignal = Signals.First(s => s.Identifier == openedPosition.SignalIdentifier);

            // Check if signal is the opposite side => flip the position
            if (openedPosition.OriginDirection == signal.Direction)
            {
                // An operation is already open for the same direction
                await LogInformation(
                    $"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
                SetSignalStatus(signal.Identifier, SignalStatus.Expired);
            }
            else
            {
                // An operation is already open for the opposite direction
                // ==> Flip the position
                if (FlipPosition)
                {
                    await LogInformation("Try to flip the position because of an opposite direction signal");
                    await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
                    await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
                    await OpenPosition(signal);
                    await LogInformation(
                        $"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
                }
                else
                {
                    await LogInformation(
                        $"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
                    SetSignalStatus(signal.Identifier, SignalStatus.Expired);
                }
            }
        }
        else
        {
            if (!CanOpenPosition(signal))
            {
                await LogInformation(
                    "Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
                SetSignalStatus(signal.Identifier, SignalStatus.Expired);
                return;
            }

            await LogInformation(
                $"Open position - Date:  {signal.Date:T} - SignalIdentifier : {signal.Identifier}");

            try
            {
                var command = new OpenPositionRequest(
                    AccountName,
                    MoneyManagement,
                    signal.Direction,
                    Ticker,
                    PositionInitiator.Bot,
                    signal.Date,
                    User,
                    BotTradingBalance,
                    IsForBacktest,
                    lastPrice,
                    signalIdentifier: signal.Identifier);

                var position = (new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
                    .Handle(command)).Result;

                if (position != null)
                {
                    Positions.Add(position);

                    if (position.Open.Status != TradeStatus.Cancelled)
                    {
                        SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);

                        if (!IsForBacktest)
                        {
                            await MessengerService.SendPosition(position);
                        }

                        Logger.LogInformation($"Position requested");
                    }
                    else
                    {
                        await SetPositionStatus(signal.Identifier, PositionStatus.Rejected);
                        SetSignalStatus(signal.Identifier, SignalStatus.Expired);
                    }
                }
            }
            catch (Exception ex)
            {
                // Keep signal open for debug purpose 
                //SetSignalStatus(signal.Identifier, SignalStatus.Expired);
                SetSignalStatus(signal.Identifier, SignalStatus.Expired);
                await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
            }
        }
    }

    private bool CanOpenPosition(Signal signal)
    {
        if (!IsForBacktest && ExecutionCount < 1)
            return false;

        if (Positions.Count == 0)
            return true;

        var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
                                                        && p.SignalIdentifier != signal.Identifier
                                                        && p.OriginDirection == signal.Direction);

        if (lastPosition == null)
            return true;

        var tenCandleAgo = OptimizedCandles.TakeLast(10).First();
        var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);

        return positionSignal.Date < tenCandleAgo.Date;
    }

    public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
        bool tradeClosingPosition = false)
    {
        if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled &&
            tradeToClose.TradeType == TradeType.StopMarket)
        {
            // If trade is the 2nd Take profit
            tradeToClose.Quantity = position.TakeProfit2.Quantity;
        }

        await LogInformation(
            $"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
            $"- Closing Position : {tradeClosingPosition}");

        // Get status of position before closing it. The position might be already close by the exchange
        if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0)
        {
            Logger.LogInformation($"Trade already close on exchange");
            await HandleClosedPosition(position);
        }
        else
        {
            var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest);
            try
            {
                var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
                    .Handle(command)).Result;

                if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
                {
                    if (tradeClosingPosition)
                    {
                        await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
                    }

                    await HandleClosedPosition(closedPosition);
                }
                else
                {
                    throw new Exception($"Wrong position status : {closedPosition.Status}");
                }
            }
            catch (Exception ex)
            {
                await LogWarning($"Position {signal.Identifier} not closed : {ex.Message}");

                if (position.Status == (PositionStatus.Canceled | PositionStatus.Rejected))
                {
                    // Trade close on exchange => Should close trade manually
                    await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
                }
            }
        }
    }

    private async Task HandleClosedPosition(Position position)
    {
        if (Positions.Any(p => p.Identifier == position.Identifier))
        {
            await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
            Logger.LogInformation(
                $"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss?.Realized}");

            // Update the bot's trading balance after position is closed
            if (position.ProfitAndLoss != null)
            {
                // Add PnL (could be positive or negative)
                BotTradingBalance += position.ProfitAndLoss.Realized;

                // Subtract fees
                BotTradingBalance -= GetPositionFees(position);

                Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}");
            }
        }
        else
        {
            await LogWarning("Weird things happen - Trying to update position status, but no position found");
        }

        if (!IsForBacktest)
        {
            await MessengerService.SendClosingPosition(position);
        }

        await CancelAllOrders();
    }

    private async Task CancelAllOrders()
    {
        if (!IsForBacktest && !IsForWatchingOnly)
        {
            try
            {
                var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker);
                if (openOrders.Any())
                {
                    var openPositions = (await ExchangeService.GetBrokerPositions(Account))
                        .Where(p => p.Ticker == Ticker);
                    var cancelClose = openPositions.Any();

                    if (cancelClose)
                    {
                        Logger.LogInformation($"Position still open, cancel close orders&");
                    }
                    else
                    {
                        Logger.LogInformation($"Canceling all orders for {Ticker}");
                        await ExchangeService.CancelOrder(Account, Ticker);
                        var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
                        Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
                    }
                }
                else
                {
                    Logger.LogInformation($"No need to cancel orders for {Ticker}");
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error during cancelOrders");
                SentrySdk.CaptureException(ex);
            }
        }
    }

    private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
    {
        if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus))
        {
            await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
            Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
        }

        SetSignalStatus(signalIdentifier,
            positionStatus == PositionStatus.Filled ? SignalStatus.PositionOpen : SignalStatus.Expired);
    }

    private void UpdatePositionPnl(string identifier, decimal realized)
    {
        Positions.First(p => p.Identifier == identifier).ProfitAndLoss = new ProfitAndLoss()
        {
            Realized = realized
        };
    }

    private void SetSignalStatus(string signalIdentifier, SignalStatus signalStatus)
    {
        if (Signals.Any(s => s.Identifier == signalIdentifier))
        {
            Signals.First(s => s.Identifier == signalIdentifier).Status = signalStatus;
            Logger.LogInformation($"Signal {signalIdentifier} is now {signalStatus}");
        }
    }

    public int GetWinRate()
    {
        var succeededPositions = Positions.Where(p => p.IsFinished()).Count(p => p.ProfitAndLoss?.Realized > 0);
        var total = Positions.Where(p => p.IsFinished()).Count();

        if (total == 0)
            return 0;

        return (succeededPositions * 100) / total;
    }

    public decimal GetProfitAndLoss()
    {
        var pnl = Positions.Where(p => p.ProfitAndLoss != null).Sum(p => p.ProfitAndLoss.Realized);
        return pnl - GetTotalFees();
    }

    /// <summary>
    /// Calculates the total fees paid by the trading bot for each position.
    /// </summary>
    /// <returns>Returns the total fees paid as a decimal value.</returns>
    public decimal GetTotalFees()
    {
        decimal fees = 0;
        foreach (var position in Positions.Where(p => p.Open.Fee > 0))
        {
            fees += position.Open.Fee;
            fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0;
            fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0;

            if (position.IsFinished() &&
                position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled)
                fees += position.Open.Fee;

            if (position.TakeProfit2 != null)
                fees += position.TakeProfit2.Fee;
        }

        return fees;
    }

    /// <summary>
    /// Calculates the total fees for a specific position
    /// </summary>
    /// <param name="position">The position to calculate fees for</param>
    /// <returns>The total fees for the position</returns>
    private decimal GetPositionFees(Position position)
    {
        decimal fees = 0;

        fees += position.Open.Fee;
        fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0;
        fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0;

        if (position.IsFinished() &&
            position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled)
            fees += position.Open.Fee;

        if (position.TakeProfit2 != null)
            fees += position.TakeProfit2.Status == TradeStatus.Filled ? position.TakeProfit2.Fee : 0;

        return fees;
    }

    public async Task ToggleIsForWatchOnly()
    {
        IsForWatchingOnly = (!IsForWatchingOnly);
        await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}");
    }

    private async Task LogInformation(string message)
    {
        Logger.LogInformation(message);
        await SendTradeMessage(message);
    }

    private async Task LogWarning(string message)
    {
        message = $"[{Identifier}] {message}";
        SentrySdk.CaptureException(new Exception(message));
        await SendTradeMessage(message, true);
    }

    private async Task SendTradeMessage(string message, bool isBadBehavior = false)
    {
        if (!IsForBacktest)
        {
            await MessengerService.SendTradeMessage(message, isBadBehavior);
        }
    }

    public override void SaveBackup()
    {
        var data = new TradingBotBackup
        {
            Name = Name,
            BotType = BotType,
            Signals = Signals,
            Positions = Positions,
            Timeframe = Timeframe,
            Ticker = Ticker,
            ScenarioName = ScenarioName,
            AccountName = AccountName,
            IsForWatchingOnly = IsForWatchingOnly,
            WalletBalances = WalletBalances,
            MoneyManagement = MoneyManagement,
            BotTradingBalance = BotTradingBalance,
            StartupTime = StartupTime,
        };
        BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data));
    }

    public override void LoadBackup(BotBackup backup)
    {
        var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data);
        Signals = data.Signals;
        Positions = data.Positions;
        WalletBalances = data.WalletBalances;
        // MoneyManagement = data.MoneyManagement; => loaded from database
        Timeframe = data.Timeframe;
        Ticker = data.Ticker;
        ScenarioName = data.ScenarioName;
        AccountName = data.AccountName;
        IsForWatchingOnly = data.IsForWatchingOnly;
        BotTradingBalance = data.BotTradingBalance;
        Identifier = backup.Identifier;
        User = backup.User;

        // Restore the startup time if it was previously saved
        if (data.StartupTime != DateTime.MinValue)
        {
            StartupTime = data.StartupTime;
        }
    }

    /// <summary>
    /// Manually opens a position using the bot's settings and a generated signal.
    /// Relies on the bot's MoneyManagement for Stop Loss and Take Profit placement.
    /// </summary>
    /// <param name="direction">The direction of the trade (Long/Short).</param>
    /// <returns>The created Position object.</returns>
    /// <exception cref="Exception">Throws if no candles are available or position opening fails.</exception>
    public async Task<Position> OpenPositionManually(TradeDirection direction)
    {
        var lastCandle = OptimizedCandles.LastOrDefault();
        if (lastCandle == null)
        {
            throw new Exception("No candles available to open position");
        }

        // Create a fake signal for manual position opening
        var signal = new Signal(Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, TradingExchanges.GmxV2,
            StrategyType.Stc, SignalType.Signal);
        signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
        signal.User = Account.User; // Assign user

        // Add the signal to our collection
        await AddSignal(signal);

        // Open the position using the generated signal (SL/TP handled by MoneyManagement)
        await OpenPosition(signal);

        // Get the opened position
        var position = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier);
        if (position == null)
        {
            // Clean up the signal if position creation failed
            SetSignalStatus(signal.Identifier, SignalStatus.Expired);
            throw new Exception("Failed to open position");
        }

        Logger.LogInformation($"Manually opened position {position.Identifier} for signal {signal.Identifier}");
        return position;
    }
}

public class TradingBotBackup
{
    public string Name { get; set; }
    public BotType BotType { get; set; }
    public HashSet<Signal> Signals { get; set; }
    public List<Position> Positions { get; set; }
    public Timeframe Timeframe { get; set; }
    public Ticker Ticker { get; set; }
    public string ScenarioName { get; set; }
    public string AccountName { get; set; }
    public bool IsForWatchingOnly { get; set; }
    public Dictionary<DateTime, decimal> WalletBalances { get; set; }
    public MoneyManagement MoneyManagement { get; set; }
    public DateTime StartupTime { get; set; }
    public decimal BotTradingBalance { get; set; }
}