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 Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Yitter.IdGenerator;

namespace Binance.TradeRobot.Business
{
    [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)]
    public class UserBusiness : BaseBusiness
    {
        private IConfiguration configuration;

        public UserBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IConfiguration configuration, IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache)
        {
            this.configuration = configuration;
        }

        public LoginResponse Login(LoginRequest loginRequest)
        {
            var pwd = loginRequest.Pwd.ToMD5();
            var user = fsql.Select<User>().Where(u => u.UserName == loginRequest.UserName && u.Pwd == pwd).ToOne();
            if (user == null)
                throw new BusinessException("用户名或密码错误");

            #region 签发token
            var claims = new List<Claim>()
            {
                new Claim("Id",user.Id.ToString()),
                new Claim("UserName",user.UserName),
            };

            var jwtSecret = configuration.GetSection("JwtConfig:Secret").Value;
            var jwtIssuer = configuration.GetSection("JwtConfig:Issuer").Value;
            var jwtAudience = configuration.GetSection("JwtConfig:Audience").Value;
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret));
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var jwtToken = new JwtSecurityToken(jwtIssuer, jwtAudience, claims, expires: DateTime.Now.AddDays(30), signingCredentials: credentials);
            var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
            #endregion
            return new LoginResponse()
            {
                Id = user.Id,
                UserName = user.UserName,
                Token = token
            };
        }

        public IList<UserResponse> GetUserList(bool multiplyBy100 = true)
        {
            var userList = fsql.Select<User>().ToList().Map<IList<UserResponse>>();
            userList.CalculateRatio(multiplyBy100);
            return userList;
        }

        #region 用户资产

        /// <summary>
        /// 单向资产变更
        /// </summary>
        /// <param name="operationUserId"></param>
        /// <param name="changeAmount"></param>
        /// <param name="changeUserId"></param>
        /// <param name="capitalChangeType"></param>
        /// <param name="remark"></param>
        /// <exception cref="BusinessException"></exception>
        private void OneWayAssetChange(long operationUserId, decimal changeAmount, long changeUserId, Enums.CapitalChangeType capitalChangeType, string remark)
        {
            var changeUser = fsql.Select<User>(changeUserId).ToOne().Map<UserResponse>();
            if (changeUser == null)
                throw new BusinessException("资金变更用户不存在");

            if (capitalChangeType == Enums.CapitalChangeType.Reduce)
            {
                if (changeUser.TotalAssets < changeAmount)
                    throw new BusinessException("用户资产小于减持资金,不能提现");
            }

            changeUser.ChangeAmount(capitalChangeType, changeAmount, true);

            fsql.Transaction(() =>
            {
                fsql.Update<User>(changeUser.Id).Set(u => u.CostAmount, changeUser.CostAmount)
                                                .Set(u => u.TotalAssets, changeUser.TotalAssets)
                                                .Set(u => u.WithdrawAmount, changeUser.WithdrawAmount)
                                                .ExecuteAffrows();

                fsql.Insert(new UserAccountFundChangeRecord()
                {
                    Id = idGenerator.NewLong(),
                    ChangeAmount = changeAmount,
                    UserId = changeUserId,
                    OperationType = capitalChangeType,
                    OperationUserId = operationUserId,
                    Remark = remark,
                    Direction = capitalChangeType == Enums.CapitalChangeType.Add ? Enums.FundDirection.In : Enums.FundDirection.Out
                }).ExecuteAffrows();
            });
        }

        public void AddFunds(long operationUserId, FundsChangeRequest fundsChangeRequest)
        {
            if (fundsChangeRequest.ChangeAmount <= 0)
                throw new BusinessException("无效金额");
            OneWayAssetChange(operationUserId, fundsChangeRequest.ChangeAmount, fundsChangeRequest.UserId, Enums.CapitalChangeType.Add, fundsChangeRequest.Remark);
        }

        public void ReduceFunds(long operationUserId, FundsChangeRequest fundsChangeRequest)
        {
            if (fundsChangeRequest.ChangeAmount <= 0)
                throw new BusinessException("无效金额");
            OneWayAssetChange(operationUserId, fundsChangeRequest.ChangeAmount, fundsChangeRequest.UserId, Enums.CapitalChangeType.Reduce, fundsChangeRequest.Remark);
        }

        public void TransferFunds(long operationUserId, FundsTransferRequest fundsTransferRequest)
        {
            if (fundsTransferRequest.ChangeAmount <= 0)
                throw new BusinessException("无效金额");

            var userList = fsql.Select<User>().ToList().Map<IList<UserResponse>>();
            var fromUser = userList.FirstOrDefault(u => u.Id == fundsTransferRequest.FromUserId);
            if (fromUser == null)
                throw new BusinessException("资金变更用户不存在");
            var toUser = userList.FirstOrDefault(u => u.Id == fundsTransferRequest.ToUserId);
            if (toUser == null)
                throw new BusinessException("资金变更用户不存在");

            if (fromUser.TotalAssets < fundsTransferRequest.ChangeAmount)
                throw new BusinessException("用户资产小于转移资金,不能转移");

            fromUser.ChangeAmount(Enums.CapitalChangeType.Reduce, fundsTransferRequest.ChangeAmount, true);
            toUser.ChangeAmount(Enums.CapitalChangeType.Add, fundsTransferRequest.ChangeAmount, true);


            fsql.Transaction(() =>
            {
                fsql.Update<User>(fromUser.Id).Set(u => u.CostAmount, fromUser.CostAmount)
                                              .Set(u => u.TotalAssets, fromUser.TotalAssets)
                                              .Set(u => u.WithdrawAmount, fromUser.WithdrawAmount)
                                              .ExecuteAffrows();

                fsql.Update<User>(toUser.Id).Set(u => u.CostAmount, toUser.CostAmount)
                                            .Set(u => u.TotalAssets, toUser.TotalAssets)
                                            .ExecuteAffrows();

                fsql.Insert(new UserAccountFundChangeRecord()
                {
                    Id = idGenerator.NewLong(),
                    ChangeAmount = fundsTransferRequest.ChangeAmount,
                    UserId = fundsTransferRequest.FromUserId,
                    OperationType = Enums.CapitalChangeType.Transfer,
                    OperationUserId = operationUserId,
                    Remark = fundsTransferRequest.Remark,
                    Direction = Enums.FundDirection.Out,
                    ToUserId = fundsTransferRequest.ToUserId
                }).ExecuteAffrows();

                fsql.Insert(new UserAccountFundChangeRecord()
                {
                    Id = idGenerator.NewLong(),
                    ChangeAmount = fundsTransferRequest.ChangeAmount,
                    UserId = fundsTransferRequest.ToUserId,
                    OperationType = Enums.CapitalChangeType.Transfer,
                    OperationUserId = operationUserId,
                    Remark = fundsTransferRequest.Remark,
                    Direction = Enums.FundDirection.In,
                    ToUserId = fundsTransferRequest.FromUserId
                }).ExecuteAffrows();
            });
        }

        public UserAccountChangeRecordListResponse GetUserAccountFundChangeRecordList(QueryUserAccountRequest queryUserAccountRequest)
        {
            var list = fsql.Select<UserAccountFundChangeRecord, User, User, User>()
                           .InnerJoin((ucr, u, ou, tu) => ucr.UserId == u.Id)
                           .InnerJoin((ucr, u, ou, tu) => ucr.OperationUserId == ou.Id)
                           .LeftJoin((ucr, u, ou, tu) => ucr.ToUserId == tu.Id)
                           .Where((ucr, u, ou, tu) => ucr.UserId == queryUserAccountRequest.UserId)
                           .OrderByDescending((ucr, u, ou, tu) => ucr.CreateTime)
                           .Count(out var recordCount)
                           .Page(queryUserAccountRequest.PageIndex, queryUserAccountRequest.PageSize)
                           .ToList((ucr, u, ou, tu) => new UserAccountFundChangeRecordResponse()
                           {
                               Id = ucr.Id,
                               ChangeAmount = ucr.ChangeAmount,
                               CreateTime = ucr.CreateTime,
                               OperationType = ucr.OperationType,
                               OperationUserId = ucr.OperationUserId,
                               OperationUserName = ou.UserName,
                               UserId = ucr.UserId,
                               UserName = u.UserName,
                               Remark = ucr.Remark,
                               Direction = ucr.Direction,
                               ToUserId = tu.Id,
                               ToUserName = tu.UserName
                           });
            return new UserAccountChangeRecordListResponse()
            {
                List = list,
                RecordCount = recordCount
            };
        }

        public UserAccountProfitLossRecordListResponse GetUserAccountProfitLossRecordList(QueryUserAccountRequest queryUserAccountRequest)
        {
            var list = fsql.Select<Model.Db.UserAccountProfitLossRecord, Model.Db.User, Model.Db.Robot>()
                           .InnerJoin((p, u, r) => p.UserId == u.Id)
                           .InnerJoin((p, u, r) => p.RobotId == r.Id)
                           .Where((p, u, r) => p.UserId == queryUserAccountRequest.UserId)
                           .OrderByDescending((p, u, r) => p.CreateTime)
                           .Count(out var recordCount)
                           .Page(queryUserAccountRequest.PageIndex, queryUserAccountRequest.PageSize)
                           .ToList((p, u, r) => new UserAccountProfitLossRecordResponse()
                           {
                               Id = p.Id,
                               ChangeAmount = p.ChangeAmount,
                               CreateTime = p.CreateTime,
                               DividendRatio = p.DividendRatio,
                               OrderProfit = p.OrderProfit,
                               RobotId = p.RobotId,
                               OrderId = p.OrderId,
                               Symbol = r.Symbol,
                               UserId = p.UserId,
                               UserName = u.UserName,
                               CumulativeProfitAndLoss = p.CumulativeProfitAndLoss
                           });
            return new UserAccountProfitLossRecordListResponse()
            {
                List = list,
                RecordCount = recordCount
            };
        }

        #endregion
    }
}