using Binance.Net.Clients;
using Binance.Net.Enums;
using Binance.Net.Interfaces.Clients;
using CryptoExchange.Net.Authentication;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Exchanges.Helpers;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Exchanges.Exchanges;

public class BinanceProcessor : BaseProcessor
{
    private ILogger<BinanceProcessor> _logger;
    private IBinanceRestClient _binanceClient;

    public BinanceProcessor(ILogger<BinanceProcessor> logger)
    {
        _logger = logger;
    }

    public override async Task<bool> CancelOrder(Account account, Ticker ticker)
    {
        var binanceResult =
            await _binanceClient.UsdFuturesApi.Trading.CancelAllOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
        return binanceResult.Success;
    }

    public override Enums.TradingExchanges Exchange() => Enums.TradingExchanges.Binance;

    public override async Task<decimal> GetBalance(Account account, bool isForPaperTrading = false)
    {
        var balance = 0m;
        var binanceBalance = await _binanceClient.UsdFuturesApi.Account.GetBalancesAsync();
        foreach (var item in binanceBalance.Data)
        {
            balance += item.AvailableBalance;
        }

        return balance;
    }

    public override Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false)
    {
        throw new NotImplementedException();
    }

    public override Candle GetCandle(Account account, Ticker ticker, DateTime date)
    {
        throw new NotImplementedException();
    }

    public override async Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate,
        Timeframe timeframe)
    {
        var binanceCandles = await _binanceClient.UsdFuturesApi.ExchangeData.GetKlinesAsync(
            BinanceHelpers.ToBinanceTicker(ticker),
            BinanceHelpers.Map(timeframe), startDate);

        return (List<Candle>)binanceCandles.Data.Select(binanceKline =>
            BinanceHelpers.Map(binanceKline, ticker, account.Exchange));
    }

    public override decimal GetFee(Account account, bool isForPaperTrading = false)
    {
        throw new NotImplementedException();
    }

    public override Orderbook GetOrderbook(Account account, Ticker ticker)
    {
        throw new NotImplementedException();
    }

    public override Task<List<Trade>> GetOrders(Account account, Ticker ticker)
    {
        throw new NotImplementedException();
    }

    public override decimal GetPrice(Account account, Ticker ticker, DateTime date)
    {
        var binancePrice = _binanceClient.UsdFuturesApi.ExchangeData
            .GetPriceAsync(BinanceHelpers.ToBinanceTicker(ticker)).Result.Data;
        return binancePrice.Price;
    }

    public override async Task<decimal> GetQuantityInPosition(Account account, Ticker ticker)
    {
        throw new NotImplementedException();
    }

    public override async Task<Trade> GetTrade(Account account, string order, Ticker ticker)
    {
        var binanceOrder =
            await _binanceClient.UsdFuturesApi.Trading.GetOrderAsync(BinanceHelpers.ToBinanceTicker(ticker),
                origClientOrderId: order);
        return BinanceHelpers.Map(binanceOrder.Data);
    }

    public override Task<Trade> GetTrade(string reference, string orderId, Ticker ticker)
    {
        throw new NotImplementedException();
    }

    public override Task<List<FundingRate>> GetFundingRates()
    {
        throw new NotImplementedException();
    }

    public override Task<IEnumerable<Position>> GetPositions(Account account)
    {
        throw new NotImplementedException();
    }

    public override async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
    {
        var binanceOrder =
            await _binanceClient.UsdFuturesApi.Trading.GetOrdersAsync(BinanceHelpers.ToBinanceTicker(ticker));
        return (List<Trade>)binanceOrder.Data.Select(o => BinanceHelpers.Map(o));
    }

    public override decimal GetVolume(Account account, Ticker ticker)
    {
        throw new NotImplementedException();
    }

    public override void LoadClient(Account account)
    {
        var credentials = new ApiCredentials(account.Key, account.Secret);
        _binanceClient = new BinanceRestClient((options) => { options.ApiCredentials = credentials; });
    }

    public override 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)
    {
        var trade = new Trade(DateTime.Now, direction, TradeStatus.PendingOpen, tradeType, ticker, quantity, price,
            leverage, "", "");
        trade.SetQuantity(quantity, GetQuantityPrecision(account, ticker));
        trade.SetPrice(price, GetPricePrecision(account, ticker));

        if (trade.Quantity <= 0)
            throw new InvalidOperationException(
                $"Minimum quantity not match, cannot execute trade {direction} for {ticker}.");

        var binanceOrderType = BinanceHelpers.BinanceOrderTypeMap(tradeType);
        var binanceResult = await _binanceClient.UsdFuturesApi.Trading.PlaceOrderAsync(
            BinanceHelpers.ToBinanceTicker(ticker),
            direction != TradeDirection.Long ? OrderSide.Sell : OrderSide.Buy,
            binanceOrderType,
            price: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null,
            quantity: trade.Quantity,
            reduceOnly: reduceOnly,
            priceProtect: true,
            timeInForce: binanceOrderType == FuturesOrderType.Limit ? TimeInForce.GoodTillCanceled : null,
            activationPrice: binanceOrderType == FuturesOrderType.Limit ? trade.Price : null,
            stopPrice: binanceOrderType == FuturesOrderType.StopMarket ? trade.Price : null);

        _logger.LogInformation("Exchange result : {0}", JsonConvert.SerializeObject(binanceResult));

        var binanceOrderExecuted = BinanceHelpers.Map(binanceResult, leverage);

        if (binanceResult.Success)
        {
            var binanceOrder = await GetTrade(account, "", ticker);

            trade.Price = binanceOrder.Price;
            trade.SetStatus(binanceOrder.Status);
            trade.SetExchangeOrderId("");
            trade.SetMessage("");
        }

        return trade;
    }

    private int GetPricePrecision(Account account, Ticker ticker)
    {
        var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data;
        var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker))
            .PriceFilter.TickSize;
        return MathHelpers.GetDecimalPlaces(precision);
    }

    private int GetQuantityPrecision(Account account, Ticker ticker)
    {
        var binanceFutureInfo = _binanceClient.UsdFuturesApi.ExchangeData.GetExchangeInfoAsync().Result.Data;
        var precision = binanceFutureInfo.Symbols.Single(p => p.Name == BinanceHelpers.ToBinanceTicker(ticker))
            .QuantityPrecision;
        return Convert.ToInt32(precision);
    }
}