using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Core;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Application.Trading.Handlers;

public class ClosePositionCommandHandler(
    IExchangeService exchangeService,
    IAccountService accountService,
    ITradingService tradingService,
    IServiceScopeFactory scopeFactory,
    ILogger<ClosePositionCommandHandler> logger = null)
    : ICommandHandler<ClosePositionCommand, Position>
{
    public async Task<Position> Handle(ClosePositionCommand request)
    {
        try
        {
            // Get Trade
            var account = await accountService.GetAccount(request.Position.AccountName, false, false);
            if (request.Position == null)
            {
                _ = await exchangeService.CancelOrder(account, request.Position.Ticker);
                return request.Position;
            }

            var isForPaperTrading = request.IsForBacktest;

            var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
                ? request.ExecutionPrice.GetValueOrDefault()
                : await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);

            // Check if position still open 
            if (!request.IsForBacktest)
            {
                var p = (await exchangeService.GetBrokerPositions(account))
                    .FirstOrDefault(x => x.Ticker == request.Position.Ticker);

                // Position not available on the broker, so be sure to update the status
                if (p == null)
                {
                    request.Position.Status = PositionStatus.Finished;
                    request.Position.ProfitAndLoss =
                        TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
                            request.Position.Open.Leverage);
                    await tradingService.UpdatePositionAsync(request.Position);
                    return request.Position;
                }
            }


            var closeRequestedOrders =
                true; // TODO: For gmx no need to close orders since they are closed automatically

            // Close market 
            var closedPosition =
                await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);

            if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
            {
                request.Position.Status = PositionStatus.Finished;
                request.Position.ProfitAndLoss =
                    TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
                        request.Position.Open.Leverage);

                if (!request.IsForBacktest)
                    await tradingService.UpdatePositionAsync(request.Position);

                // Notify platform summary about the closed position
                try
                {
                    await ServiceScopeHelpers.WithScopedService<IGrainFactory>(scopeFactory, async grainFactory =>
                    {
                        var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
                        if (platformGrain != null)
                        {
                            var positionClosedEvent = new PositionClosedEvent
                            {
                                PositionId = request.Position.Identifier,
                                Ticker = request.Position.Ticker,
                                RealizedPnL = request.Position.ProfitAndLoss?.Realized ?? 0,
                                Volume = closedPosition.Quantity * lastPrice * request.Position.Open.Leverage,
                                InitialVolume = request.Position.Open.Quantity * request.Position.Open.Price *
                                                request.Position.Open.Leverage
                            };

                            await platformGrain.OnPositionClosedAsync(positionClosedEvent);
                        }
                    });
                }
                catch (Exception ex)
                {
                    SentrySdk.CaptureException(ex);
                    logger?.LogError(ex,
                        "Failed to notify platform summary about position closure for position {PositionId}",
                        request.Position.Identifier);
                }
            }

            return request.Position;
        }
        catch (Exception ex)
        {
            logger?.LogError(ex, "Error closing position: {Message} \n Stacktrace : {StackTrace}", ex.Message,
                ex.StackTrace);

            SentrySdk.CaptureException(ex);
            throw;
        }
    }
}