using Binance.TradeRobot.Business.Extensions;
using Binance.TradeRobot.Common.DI;
using Binance.TradeRobot.Common.Extensions;
using Binance.TradeRobot.Model.Base;
using Binance.TradeRobot.Model.Db;
using Binance.TradeRobot.Model.Dto;
using Binance.TradeRobot.Model.RuningInfo;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Yitter.IdGenerator;

namespace Binance.TradeRobot.Business
{
    [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Interface)]
    public class D21TradeBusiness : BaseTradeBusiness, ITradeBusiness
    {

        public Enums.TradePolicy TradePolicy => Enums.TradePolicy.D21;

        public D21TradeBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache, DingBusiness dingBusiness, GlobalContext globalContext) : base(fsql, logManager, idGenerator, memoryCache, dingBusiness, globalContext)
        {
        }

        public void TrendChanged<T, T1>(T singalRequest, T1 robot, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
        {
            var logList = new List<ExecutionLog>();
            logList.Add(new ExecutionLog()
            {
                Id = idGenerator.NewLong(),
                RobotId = robot.Id,
                CreateTime = DateTime.Now,
                SourceSingal = singalRequest.SingalType,
                Content = $"收到信号{singalRequest.SingalType}"
            });
            try
            {
                fsql.Insert(logList).ExecuteAffrows();
                var d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };
                d21RuningInfo.RecentSmallTrendSingal = singalRequest.SingalType;
                RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
            }
            catch (Exception ex)
            {
                HandleError(ex, singalRequest.SingalType, logList, robot, string.Empty);
            }
        }

        public void LongCross<T, T1>(T singalRequest, T1 robot, bool isRemedy, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
        {
            string step = string.Empty;
            var logList = new List<ExecutionLog>();
            logList.Add(new ExecutionLog()
            {
                Id = idGenerator.NewLong(),
                SourceSingal = singalRequest.SingalType,
                RobotId = robot.Id,
                CreateTime = DateTime.Now,
                Content = $"收到信号{singalRequest.SingalType}{(isRemedy ? "(补救)" : string.Empty)}"
            });
            try
            {
                var d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };

                #region 验证信号
                step = "验证信号";
                if (!isRemedy)
                {
                    if (d21RuningInfo.ErrorCrossSingal != null)
                    {
                        d21RuningInfo.ErrorCrossSingal = null;
                        d21RuningInfo.ErrorCrossSingalTime = 0;
                        RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
                        throw new BusinessException("前一个信号错误,终止多交叉信号执行");
                    }

                    if (d21RuningInfo.RecentSmallTrendSingal == null)
                        throw new BusinessException("缺少小趋势,终止多交叉信号执行");

                    if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看空)
                    {
                        var errorTimeStamp = DateTime.Now.GetKID(singalRequest.KLinePeriodic, false);
                        Thread.Sleep(5000); //防止多交叉和小趋势看多同时接收造成误判
                        d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };
                        if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看空)
                        {
                            d21RuningInfo.ErrorCrossSingal = singalRequest.SingalType;
                            d21RuningInfo.ErrorCrossSingalTime = errorTimeStamp;
                            RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
                            throw new BusinessException("小趋势看空,终止多交叉信号执行");
                        }
                    }
                }
                #endregion

                #region 限制追高
                var d21Robot = robot as D21PolicyRobotResponse;
                var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? singalRequest.ClosePrice;

                step = "限制追高";
                if (d21RuningInfo.RecentShortCrossSignalTradePrice != null && newestPrice > d21RuningInfo.RecentShortCrossSignalTradePrice)
                {
                    var diffRatio = Math.Round((newestPrice / d21RuningInfo.RecentShortCrossSignalTradePrice.Value - 1) * 100, 2);
                    if (diffRatio > d21Robot.D21Policy.MaxFollowPurchaseRatio)
                    {
                        throw new BusinessException($"触发追高限制,最近空交叉成交价{d21RuningInfo.RecentShortCrossSignalTradePrice},当前价格{newestPrice},最大追高比例{d21Robot.D21Policy.MaxFollowPurchaseRatio}%,当前追高比例{diffRatio}%,终止多交叉信号执行");
                    }
                }
                #endregion

                #region 获取账户余额
                step = "获取账户余额";
                var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey);
                //逐仓杠杆账户余额
                var balance = apiClient.GetIsolatedMarginAccountAssets().FirstOrDefault(m => m.Symbol == robot.Symbol)?.QuoteFree ?? 0M;
                if (balance == 0M)
                    throw new BusinessException("可用资产为0");
                #endregion

                #region 计算下单数量
                step = "计算下单数量";
                var diffAmount = 0M;  //下单缺口金额
                var accountLoanAmount = robot.RobotAccount.LoanAmount;  //账户借币金额
                var previewTradeAmount = d21Robot.D21Policy.Position; //预估交易额

                if (balance < previewTradeAmount)
                {
                    #region 验证借币
                    step = "验证借币";
                    diffAmount = previewTradeAmount - balance;
                    var diffRatio = Math.Round(diffAmount / balance * 100, 2);     //借币比例

                    #region 验证策略中的最大借币比例
                    if (diffRatio > d21Robot.D21Policy.MaxExchangeLoanRatio)
                    {
                        logList.Add(new ExecutionLog()
                        {
                            Id = idGenerator.NewLong(),
                            SourceSingal = Enums.SingalType.多交叉,
                            RobotId = robot.Id,
                            CreateTime = DateTime.Now,
                            Content = $"触发策略中交易所最大借币比例限制,交易所最大借币比例{d21Robot.D21Policy.MaxExchangeLoanRatio}%,当前借币比例{diffRatio}%,下单仓位:{previewTradeAmount},账户余额:{balance},按交易所最大借币比例借币"
                        });
                        //diffAmount = previewTradeAmount * (d21Robot.D21Policy.MaxExchangeLoanRatio / 100M);
                        
                        diffAmount = balance * d21Robot.D21Policy.MaxExchangeLoanRatio;
                        //previewTradeAmount = balance + diffAmount; //在策略允许的借币比例范围内的最大下单金额

                    }
                    #endregion

                    #region 验证交易所的最大可借额度
                    try
                    {
                        step = "验证交易所的最大可借额度";
                        var exchangeMaxLoanAmount = apiClient.QueryMaxLoanAmount(robot.Symbol);
                        if (exchangeMaxLoanAmount < diffAmount)
                        {
                            if (exchangeMaxLoanAmount == 0M)
                                throw new Exception("可借额度为0");
                            diffAmount = exchangeMaxLoanAmount;
                        }
                        previewTradeAmount = balance + diffAmount;
                    }
                    catch (Exception borrowex)
                    {
                        logList.Add(new ExecutionLog()
                        {
                            Id = idGenerator.NewLong(),
                            SourceSingal = Enums.SingalType.多交叉,
                            RobotId = robot.Id,
                            CreateTime = DateTime.Now,
                            Content = $"验证交易所的最大可借额度失败 {borrowex.Message}"
                        });
                        previewTradeAmount = balance;  //无法借币,使用余额下单
                        diffAmount = 0M;
                    }
                    #endregion

                    #endregion
                }
                #endregion

                #region 借币
                if (diffAmount > 0M)
                {
                    step = "借币";
                    try
                    {
                        var loanResponse = apiClient.IsolatedMarginLoan(robot.Symbol, diffAmount);
                        diffAmount = loanResponse.CurrentLoanAmount;
                        accountLoanAmount = loanResponse.AccountLoanAmount;
                    }
                    catch (Exception borrowex)
                    {
                        logList.Add(new ExecutionLog()
                        {
                            Id = idGenerator.NewLong(),
                            SourceSingal = Enums.SingalType.多交叉,
                            RobotId = robot.Id,
                            CreateTime = DateTime.Now,
                            Content = $"借币失败 {borrowex.Message}"
                        });
                        previewTradeAmount = balance;  //无法借币,使用余额下单
                        diffAmount = 0M;
                    }
                }
                #endregion

                #region 下单
                step = "下单";
                var clientOrderId = CreateClientOrderId(robot.Id, robot.TradePolicy);
                var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
                                                                 Enums.TradeDirection.Buy,
                                                                 Enums.OrderType.MARKET,
                                                                 quoteAmount: previewTradeAmount,
                                                                 newClientOrderId: clientOrderId);
                var buyOrder = new SpotOrder()
                {
                    Id = orderId,
                    ClientOrderId = clientOrderId,
                    CreateTime = DateTime.Now,
                    ExchangeId = robot.ExchangeId,
                    LoanAmount = diffAmount,
                    OrderType = Enums.OrderType.MARKET,
                    PolicyType = Enums.TradePolicy.D21,
                    RobotId = robot.Id,
                    State = Enums.SpotOrderState.Created,
                    Symbol = robot.Symbol,
                    TradeDirection = Enums.TradeDirection.Buy
                };

                logList.Add(new ExecutionLog()
                {
                    Id = idGenerator.NewLong(),
                    SourceSingal = singalRequest.SingalType,
                    RobotId = robot.Id,
                    OrderId = orderId,
                    CreateTime = DateTime.Now,
                    Content = $"市价买单挂单成功,订单号:{orderId},挂单金额:{previewTradeAmount},借币金额:{diffAmount}"
                });
                fsql.Transaction(() =>
                {
                    fsql.Insert(logList).ExecuteAffrows();
                    fsql.Insert(buyOrder).ExecuteAffrows();
                    if (diffAmount > 0M)
                        fsql.Update<RobotAccount>(robot.RobotAccount.Id).Set(ra => ra.LoanAmount, accountLoanAmount).ExecuteAffrows();
                    if (previewTradeAmount != d21Robot.D21Policy.Position)   //借币失败 仓位减少
                        fsql.Update<D21Policy>(d21Robot.D21Policy.Id).Set(d => d.Position, previewTradeAmount).ExecuteAffrows();
                });
                #endregion

            }
            catch (Exception ex)
            {
                HandleError(ex, singalRequest.SingalType, logList, robot, step);
            }
        }

        public void ShortCross<T, T1>(T singalRequest, T1 robot, bool isRemedy, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
        {
            string step = string.Empty;
            var logList = new List<ExecutionLog>();
            logList.Add(new ExecutionLog()
            {
                Id = idGenerator.NewLong(),
                SourceSingal = singalRequest.SingalType,
                RobotId = robot.Id,
                CreateTime = DateTime.Now,
                Content = $"收到信号{singalRequest.SingalType}{(isRemedy ? "(补救)" : string.Empty)}"
            });

            try
            {
                var d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };

                #region 验证信号
                step = "验证信号";
                if (!isRemedy)
                {
                    if (d21RuningInfo.ErrorCrossSingal != null)
                    {
                        d21RuningInfo.ErrorCrossSingal = null;
                        d21RuningInfo.ErrorCrossSingalTime = 0;
                        RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
                        throw new BusinessException("前一个信号错误,终止空交叉信号执行");
                    }

                    if (d21RuningInfo.RecentSmallTrendSingal == null)
                        throw new BusinessException("缺少小趋势,终止空交叉信号执行");

                    if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看多)
                    {
                        var errorTimeStamp = DateTime.Now.GetKID(singalRequest.KLinePeriodic, false);
                        Thread.Sleep(5000); //防止空交叉和小趋势看空同时接收造成误判
                        d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };
                        if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看多)
                        {
                            d21RuningInfo.ErrorCrossSingal = singalRequest.SingalType;
                            d21RuningInfo.ErrorCrossSingalTime = errorTimeStamp;
                            RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
                            throw new BusinessException("小趋势看多,终止空交叉信号执行");
                        }
                    }
                }
                #endregion

                #region 取消止损限价单
                step = "取消止损限价单";
                CancelStopLossOrder(robot);
                #endregion

                #region 查询卖币数量
                step = "查询卖币数量";
                Thread.Sleep(1000);
                var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey);
                var isolatedMarginAccountAssetList = apiClient.GetIsolatedMarginAccountAssets();
                var currentAsset = isolatedMarginAccountAssetList.FirstOrDefault(x => x.Symbol == robot.Symbol);
                var saleQuantity = currentAsset.BaseFree.CutDecimal(symbolInfo.SaleQuantityAccuracy);
                if (saleQuantity == 0M)
                    throw new BusinessException("没有足够的卖币数量");

                //var saleQuantity = robot.RobotAccount.SpotCurrencyQuantity.CutDecimal(symbolInfo.SaleQuantityAccuracy);
                //if (saleQuantity == 0M)
                //    throw new BusinessException("没有足够的卖币数量");
                #endregion

                #region 下单
                step = "下单";
                var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? singalRequest.ClosePrice;


                var clientOrderId = CreateClientOrderId(robot.Id, robot.TradePolicy);
                var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
                                                                 Enums.TradeDirection.Sell,
                                                                 Enums.OrderType.MARKET,
                                                                 quantity: saleQuantity,
                                                                 newClientOrderId: clientOrderId);

                var sellOrder = new SpotOrder()
                {
                    Id = orderId,
                    ClientOrderId = clientOrderId,
                    CreateTime = DateTime.Now,
                    ExchangeId = robot.ExchangeId,
                    OrderType = Enums.OrderType.MARKET,
                    PolicyType = Enums.TradePolicy.D21,
                    RobotId = robot.Id,
                    State = Enums.SpotOrderState.Created,
                    Symbol = robot.Symbol,
                    TradeDirection = Enums.TradeDirection.Sell
                };

                logList.Add(new ExecutionLog()
                {
                    Id = idGenerator.NewLong(),
                    SourceSingal = singalRequest.SingalType,
                    RobotId = robot.Id,
                    OrderId = orderId,
                    CreateTime = DateTime.Now,
                    Content = $"市价卖单挂单成功,订单号:{orderId},挂单数量:{saleQuantity}"
                });
                fsql.Transaction(() =>
                {
                    fsql.Insert(logList).ExecuteAffrows();
                    fsql.Insert(sellOrder).ExecuteAffrows();
                });
                #endregion

                #region 更新空交叉卖出成功时的成交价
                d21RuningInfo.RecentShortCrossSignalTradePrice = newestPrice;
                RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
                #endregion
            }
            catch (Exception ex)
            {
                HandleError(ex, singalRequest.SingalType, logList, robot, step);
            }
        }
    }
}