using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Exchanges.Abstractions;
using Managing.Infrastructure.Exchanges.Exchanges;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Exchanges
{
    public class ExchangeService : IExchangeService
    {
        private readonly ILogger<ExchangeService> _logger;
        private readonly ICandleRepository _candleRepository;
        private readonly IEnumerable<IExchangeProcessor> _exchangeProcessor;

        public ExchangeService(ILogger<ExchangeService> logger, ICandleRepository candleRepository,
            IEnumerable<IExchangeProcessor> processor)
        {
            _logger = logger;
            _candleRepository = candleRepository;
            _exchangeProcessor = processor;
        }

        #region Trades

        public async Task<Trade> OpenTrade(
            Account account,
            Ticker ticker,
            TradeDirection direction,
            decimal price,
            decimal quantity,
            decimal? leverage = null,
            TradeType tradeType = TradeType.Limit,
            bool reduceOnly = false,
            bool isForPaperTrading = false,
            DateTime? currentDate = null,
            bool ioc = true,
            decimal? stopLossPrice = null,
            decimal? takeProfitPrice = null)
        {
            _logger.LogInformation(
                $"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage} - SL: {stopLossPrice} - TP: {takeProfitPrice}");

            if (isForPaperTrading)
            {
                return BuildEmptyTrade(ticker, price, quantity, direction, leverage, tradeType, currentDate.Value,
                    reduceOnly ? TradeStatus.PendingOpen : TradeStatus.Filled);
            }

            var processor = GetProcessor(account);
            return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType,
                reduceOnly, isForPaperTrading, currentDate, ioc, stopLossPrice, takeProfitPrice);
        }

        private IExchangeProcessor GetProcessor(Account account)
        {
            var exchange = EvmTradingExchangesList.Contains(account.Exchange) ? TradingExchanges.Evm : account.Exchange;
            return _exchangeProcessor.First(e => e.Exchange() == exchange);
        }

        private static List<TradingExchanges> EvmTradingExchangesList
        {
            get
            {
                var eligibileProcessors = new List<TradingExchanges>()
                {
                    TradingExchanges.Evm, TradingExchanges.GmxV2
                };
                return eligibileProcessors;
            }
        }


        private bool IsEvmExchange(Account account)
        {
            return EvmTradingExchangesList.Contains(account.Exchange);
        }

        public Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction,
            decimal? leverage, TradeType tradeType,
            DateTime dateTime, TradeStatus tradeStatus)
        {
            return new Trade(dateTime, direction, tradeStatus, tradeType, ticker, quantity, price, leverage,
                Guid.NewGuid().ToString(), "EmptyTrade");
        }

        public async Task<Trade> OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection,
            decimal stopLossPrice,
            decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null)
        {
            return await OpenTrade(
                account,
                ticker,
                originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
                stopLossPrice,
                quantity,
                tradeType: TradeType.StopLoss,
                isForPaperTrading: isForPaperTrading,
                currentDate: currentDate,
                reduceOnly: true);
        }

        public async Task<Trade> OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection,
            decimal takeProfitPrice,
            decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null)
        {
            return await OpenTrade(
                account,
                ticker,
                originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
                takeProfitPrice,
                quantity,
                tradeType: TradeType.TakeProfit,
                isForPaperTrading: isForPaperTrading,
                currentDate: currentDate,
                reduceOnly: true);
        }

        public async Task<Trade> ClosePosition(Account account, Position position, decimal lastPrice,
            bool isForPaperTrading = false)
        {
            var direction = position.OriginDirection == TradeDirection.Long
                ? TradeDirection.Short
                : TradeDirection.Long;

            if (isForPaperTrading)
            {
                var fake = BuildEmptyTrade(position.Open.Ticker,
                    lastPrice,
                    position.Open.Quantity,
                    direction,
                    position.Open.Leverage,
                    TradeType.Market,
                    position.Open.Date,
                    TradeStatus.Filled);
                return fake;
            }

            var processor = GetProcessor(account);
            var closedTrade = await processor.OpenTrade(
                account,
                position.Ticker,
                direction,
                lastPrice,
                position.Open.Quantity,
                position.Open.Leverage,
                tradeType: TradeType.Market,
                reduceOnly: true);

            if (!IsEvmExchange(account))
            {
                // Only use for non-EVM exchanges since the call to the blockchain is async
                closedTrade.Price = processor.GetTrade(account, closedTrade.ExchangeOrderId, closedTrade.Ticker).Result
                    .Price;
            }

            return closedTrade;
        }

        #endregion


        public async Task<bool> CancelOrder(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return await processor.CancelOrder(account, ticker);
        }

        public async Task<Trade> GetTrade(Account account, string orderId, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return await processor.GetTrade(account, orderId, ticker);
        }

        public async Task<Trade> GetTrade(string reference, string orderId, Ticker ticker)
        {
            var processor = _exchangeProcessor.First(e => e.Exchange() == TradingExchanges.Evm);
            return await processor.GetTrade(reference, orderId, ticker);
        }

        public Task<List<FundingRate>> GetFundingRates()
        {
            var processor = _exchangeProcessor.First(e => e.Exchange() == TradingExchanges.Evm);
            return processor.GetFundingRates();
        }

        public Task<IEnumerable<Position>> GetBrokerPositions(Account account)
        {
            var processor = _exchangeProcessor.First(e => e.Exchange() == TradingExchanges.Evm);
            return processor.GetPositions(account);
        }

        public async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return await processor.GetTrades(account, ticker);
        }

        public async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
            Timeframe timeframe, bool isFirstCall)
        {
            var processor = GetProcessor(account);
            // Only EvmProcessor supports isFirstCall
            if (processor is EvmProcessor evmProcessor)
            {
                return await evmProcessor.GetCandles(account, ticker, startDate, timeframe, isFirstCall);
            }

            // Fallback to default behavior for other processors
            return await processor.GetCandles(account, ticker, startDate, timeframe);
        }

        public async Task<HashSet<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker,
            DateTime startDate,
            Timeframe timeframe, int? limit = null)
        {
            var candlesFromRepo = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, limit);
            return candlesFromRepo;
        }

        public async Task<HashSet<Candle>> GetCandlesInflux(TradingExchanges exchange, Ticker ticker,
            DateTime startDate,
            Timeframe timeframe, DateTime endDate, int? limit = null)
        {
            var candlesFromRepo =
                await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate, limit);
            return candlesFromRepo;
        }

        public async Task<decimal> GetBalance(Account account, bool isForPaperTrading = false)
        {
            if (isForPaperTrading)
            {
                return 1000;
            }

            var processor = GetProcessor(account);
            return await processor.GetBalance(account);
        }

        public decimal GetFee(Account account, bool isForPaperTrading = false)
        {
            var processor = GetProcessor(account);
            return processor.GetFee(account);
        }

        public async Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date)
        {
            var processor = GetProcessor(account);
            return await processor.GetPrice(account, ticker, date);
        }

        public async Task<Candle> GetCandle(Account account, Ticker ticker, DateTime date)
        {
            var processor = GetProcessor(account);
            return await processor.GetCandle(account, ticker, date);
        }

        public async Task<decimal> GetQuantityInPosition(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return await processor.GetQuantityInPosition(account, ticker);
        }

        public decimal GetVolume(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return processor.GetVolume(account, ticker);
        }

        public async Task<List<Ticker>> GetTickers(Timeframe timeframe)
        {
            var tickers =
                await _candleRepository.GetTickersAsync(TradingExchanges.Evm, timeframe, DateTime.UtcNow.AddDays(-2));
            return tickers.ToList();
        }

        public async Task<decimal> GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity,
            TradeDirection direction)
        {
            if (IsEvmExchange(account))
            {
                return await GetPrice(account, ticker, DateTime.UtcNow);
            }

            return GetOrderbook(account, ticker).GetBestPrice(direction, quantity);
        }

        public Orderbook GetOrderbook(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return processor.GetOrderbook(account, ticker);
        }

        public async Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false)
        {
            var processor = GetProcessor(account);
            return await processor.GetBalances(account, isForPaperTrading);
        }

        public async Task<List<Trade>> GetOpenOrders(Account account, Ticker ticker)
        {
            var processor = GetProcessor(account);
            return await processor.GetOrders(account, ticker);
        }
    }
}