using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Accounts;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Api.Workers.Workers;

public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
{
    private static readonly WorkerType _workerType = WorkerType.PositionManager;
    private readonly ITradingService _tradingService;
    private readonly IExchangeService _exchangeService;
    private readonly IAccountService _accountService;
    private readonly ILogger<PositionManagerWorker> _logger;
    private readonly ICacheService _cacheService;

    public PositionManagerWorker(
        ILogger<PositionManagerWorker> logger,
        IWorkerService workerService,
        ITradingService tradingService,
        IExchangeService exchangeService,
        IAccountService accountService, ICacheService cacheService) : base(
        _workerType,
        logger,
        TimeSpan.FromMinutes(1),
        workerService)
    {
        _logger = logger;
        _tradingService = tradingService;
        _exchangeService = exchangeService;
        _accountService = accountService;
        _cacheService = cacheService;
    }

    protected override async Task Run(CancellationToken cancellationToken)
    {
        await ManageNewPositions();
        await ManagePartiallyFilledPositions();
        await ManageFilledPositions();
    }

    private async Task ManagePartiallyFilledPositions()
    {
        var positions = GetPositions(PositionStatus.PartiallyFilled);
        _logger.LogInformation("Processing {PartiallyFilledCount} partially filled positions", positions.Count());

        foreach (var position in positions)
        {
            using (_logger.BeginScope("Position {PositionId} ({Ticker})", position.Identifier, position.Ticker))
            {
                try
                {
                    // Lock position for processing
                    position.Status = PositionStatus.Updating;
                    _tradingService.UpdatePosition(position);
                    
                    _logger.LogDebug("Verifying position on exchange for {Direction} position opened at {OpenDate}", 
                        position.OriginDirection, position.Date.ToString("o"));

                    var account = await _accountService.GetAccount(position.AccountName, false, false);
                    
                    // Get positions directly from broker
                    var brokerPositions = await _exchangeService.GetBrokerPositions(account);
                    var exchangePosition = brokerPositions.FirstOrDefault(p => 
                        p.Ticker == position.Ticker && 
                        p.OriginDirection == position.OriginDirection);
                    
                    if (exchangePosition == null)
                    {
                        _logger.LogWarning("Position not found on exchange - marking as canceled");
                        position.Status = PositionStatus.Canceled;
                        _tradingService.UpdatePosition(position);
                        continue;
                    }
                    
                    // Update with exchange data if available
                    if (exchangePosition.StopLoss != null)
                    {
                        _logger.LogInformation("Stop Loss found on exchange - ID: {OrderId}", exchangePosition.StopLoss.ExchangeOrderId);
                        position.StopLoss = exchangePosition.StopLoss;
                    }
                    else
                    {
                        _logger.LogWarning("Stop Loss not found on exchange - creating new SL order");
                        var updatedSl = await _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection,
                            position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow);
                        
                        if (updatedSl != null)
                        {
                            position.StopLoss = updatedSl;
                        }
                    }

                    if (exchangePosition.TakeProfit1 != null)
                    {
                        _logger.LogInformation("Take Profit found on exchange - ID: {OrderId}", exchangePosition.TakeProfit1.ExchangeOrderId);
                        position.TakeProfit1 = exchangePosition.TakeProfit1;
                    }
                    else
                    {
                        _logger.LogWarning("Take Profit not found on exchange - creating new TP order");
                        var updatedTp1 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
                            position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow);
                        
                        if (updatedTp1 != null)
                        {
                            position.TakeProfit1 = updatedTp1;
                        }
                    }

                    // Handle TP2 if it exists
                    if (position.TakeProfit2 != null)
                    {
                        if (exchangePosition.TakeProfit2 != null)
                        {
                            _logger.LogInformation("Take Profit 2 found on exchange - ID: {OrderId}", exchangePosition.TakeProfit2.ExchangeOrderId);
                            position.TakeProfit2 = exchangePosition.TakeProfit2;
                        }
                        else
                        {
                            _logger.LogWarning("Take Profit 2 not found on exchange - creating new TP2 order");
                            var updatedTp2 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
                                position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow);
                            
                            if (updatedTp2 != null)
                            {
                                position.TakeProfit2 = updatedTp2;
                            }
                        }
                    }

                    // Update position status based on verification results
                    var success = AllTradesActive(position);
                    position.Status = success ? PositionStatus.Filled : PositionStatus.PartiallyFilled;

                    _logger.LogInformation("Final position status: {Status}", position.Status);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Position processing failed");
                    position.Status = PositionStatus.PartiallyFilled;
                }
                finally
                {
                    _tradingService.UpdatePosition(position);
                }
            }
        }
    }

    private bool AllTradesActive(Position position)
    {
        return position.StopLoss.Status.IsActive() &&
               position.TakeProfit1.Status.IsActive() &&
               (position.TakeProfit2?.Status.IsActive() ?? true);
    }

    private async Task ManageFilledPositions()
    {
        var positions = GetPositions(PositionStatus.Filled);
        _logger.LogInformation("Monitoring {FilledPositionCount} filled positions", positions.Count());

        foreach (var position in positions)
        {
            using (_logger.BeginScope("Position {PositionId} ({Ticker})", position.Identifier, position.Ticker))
            {
                try
                {
                    // Acquire processing lock
                    _logger.LogDebug("Acquiring position lock");
                    position.Status = PositionStatus.Updating;
                    _tradingService.UpdatePosition(position);

                    _logger.LogInformation("Checking position state on exchange - Direction: {Direction}, Open Since: {OpenDate}",
                        position.OriginDirection, position.Date.ToString("yyyy-MM-dd HH:mm:ss"));

                    var account = await GetAccount(position.AccountName);
                    
                    // Check if position still exists on broker
                    var brokerPositions = await _exchangeService.GetBrokerPositions(account);
                    var exchangePosition = brokerPositions.FirstOrDefault(p => 
                        p.Ticker == position.Ticker && 
                        p.OriginDirection == position.OriginDirection);
                    
                    if (exchangePosition == null)
                    {
                        // Position no longer on exchange - it has been closed
                        _logger.LogInformation("Position no longer on exchange - marking as finished");
                        position.Status = PositionStatus.Finished;
                        
                        // Determine if SL or TP was hit by checking which one is missing
                        if (exchangePosition?.StopLoss == null && position.StopLoss.Status != TradeStatus.Filled)
                        {
                            _logger.LogInformation("Stop loss appears to have been hit");
                            position.StopLoss.SetStatus(TradeStatus.Filled);
                            position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
                                position, 
                                position.StopLoss.Quantity,
                                position.StopLoss.Price, 
                                position.Open.Leverage);
                        }
                        else if (exchangePosition?.TakeProfit1 == null && position.TakeProfit1.Status != TradeStatus.Filled)
                        {
                            _logger.LogInformation("Take profit 1 appears to have been hit");
                            position.TakeProfit1.SetStatus(TradeStatus.Filled);
                            position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
                                position, 
                                position.TakeProfit1.Quantity,
                                position.TakeProfit1.Price, 
                                position.Open.Leverage);
                        }
                        else if (exchangePosition?.TakeProfit2 == null && position.TakeProfit2?.Status != TradeStatus.Filled)
                        {
                            _logger.LogInformation("Take profit 2 appears to have been hit");
                            position.TakeProfit2.SetStatus(TradeStatus.Filled);
                            position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
                                position, 
                                position.TakeProfit2.Quantity,
                                position.TakeProfit2.Price, 
                                position.Open.Leverage);
                        }
                        
                        // Cancel any remaining orders
                        await _exchangeService.CancelOrder(account, position.Ticker);
                    }
                    else
                    {
                        // Position still exists - update with exchange data
                        _logger.LogInformation("Position still active on exchange with quantity {Quantity}", 
                            exchangePosition.Open?.Quantity ?? 0);
                        
                        // Update our position with broker data
                        if (exchangePosition.Open != null)
                            position.Open = exchangePosition.Open;
                        
                        if (exchangePosition.StopLoss != null)
                            position.StopLoss = exchangePosition.StopLoss;
                        
                        if (exchangePosition.TakeProfit1 != null)
                            position.TakeProfit1 = exchangePosition.TakeProfit1;
                        
                        if (exchangePosition.TakeProfit2 != null)
                            position.TakeProfit2 = exchangePosition.TakeProfit2;
                        
                        if (exchangePosition.ProfitAndLoss != null)
                            position.ProfitAndLoss = exchangePosition.ProfitAndLoss;
                        else
                        {
                            // Calculate PNL if not provided
                            var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
                            position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
                                position, 
                                position.Open.Quantity, 
                                lastPrice, 
                                position.Open.Leverage);
                        }
                        
                        _logger.LogInformation("Updated position from exchange - PNL: {PNL}", 
                            position.ProfitAndLoss?.Net ?? 0);
                        
                        // Keep status as Filled
                        position.Status = PositionStatus.Filled;
                    }

                    _tradingService.UpdatePosition(position);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to manage position - {ErrorMessage}", ex.Message);
                    
                    // Reset status for retry
                    position.Status = PositionStatus.Filled;
                    _tradingService.UpdatePosition(position);
                }
                finally
                {
                    // Ensure lock is always released
                    if (position.Status == PositionStatus.Updating)
                    {
                        position.Status = PositionStatus.Filled;
                        _tradingService.UpdatePosition(position);
                    }
                }
            }
        }
    }

    private IEnumerable<Position> GetPositions(PositionStatus positionStatus)
    {
        return _tradingService.GetPositionsByStatus(positionStatus)
            .Where(p => p.Initiator != PositionInitiator.PaperTrading);
    }

    private async Task ManageNewPositions()
    {
        var positions = GetPositions(PositionStatus.New);

        _logger.LogInformation("Processing {NewPositionCount} new positions", positions.Count());

        foreach (var position in positions)
        {
            using (_logger.BeginScope("Position {Identifier}", position.Identifier))
            {
                try
                {
                    // Immediate status update for concurrency protection
                    _logger.LogDebug("[{Identifier}] Acquiring position lock via status update", position.Identifier);
                    position.Status = PositionStatus.Updating;
                    _tradingService.UpdatePosition(position);

                    var account = await GetAccount(position.AccountName);
                    
                    // Check if position exists on broker
                    var brokerPositions = await _exchangeService.GetBrokerPositions(account);
                    var exchangePosition = brokerPositions.FirstOrDefault(p => 
                        p.Ticker == position.Ticker && 
                        p.OriginDirection == position.OriginDirection);
                    
                    if (exchangePosition != null)
                    {
                        // Position is confirmed on exchange
                        position.Status = PositionStatus.PartiallyFilled;
                        position.Open = exchangePosition.Open; // Use the exchange data
                        _tradingService.UpdatePosition(position);

                        _logger.LogInformation("[{Identifier}] Position found on exchange - moving to partially filled status", 
                            position.Identifier);
                        continue;
                    }
                    
                    // Position not found on exchange - check for staleness
                    if (position.Date < DateTime.UtcNow.AddDays(-1))
                    {
                        position.Status = PositionStatus.Canceled;
                        _tradingService.UpdatePosition(position);
                        _logger.LogWarning("[{Identifier}] Position canceled - stale since {PositionAge} days",
                            position.Identifier,
                            (DateTime.UtcNow - position.Date).TotalDays);
                    }
                    else
                    {
                        // Reset status to try again
                        position.Status = PositionStatus.New;
                        _tradingService.UpdatePosition(position);
                        _logger.LogInformation("[{Identifier}] Position not yet found on exchange - awaiting fill", 
                            position.Identifier);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error processing position {Identifier}", position.Identifier);
                    // Reset to New status for retry
                    position.Status = PositionStatus.New;
                    _tradingService.UpdatePosition(position);
                }
            }
        }
    }

    private async Task<Account> GetAccount(string accountName)
    {
        var account = _cacheService.GetValue<Account>(accountName);

        if (account == null)
        {
            account = await _accountService.GetAccount(accountName, false, false);
            _cacheService.SaveValue(accountName, JsonConvert.SerializeObject(account));
        }

        return account;
    }
}