package com.liquidnet.service.adam.service.impl;

import com.alibaba.fastjson.JSON;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.account.wallet.vo.WalletAccountInfoVo;
import com.liquidnet.service.adam.common.AdamErrorCode;
import com.liquidnet.service.adam.constant.*;
import com.liquidnet.service.adam.dto.*;
import com.liquidnet.service.adam.dto.base.AdamResultDto;
import com.liquidnet.service.adam.entity.AdamAccountWallet;
import com.liquidnet.service.adam.entity.AdamConversion;
import com.liquidnet.service.adam.entity.AdamTransaction;
import com.liquidnet.service.adam.incrementer.CustomIdGenerator;
import com.liquidnet.service.adam.mapper.AdamConversionMapper;
import com.liquidnet.service.adam.service.*;
import com.liquidnet.service.adam.service.feign.bank.IAdamFeignBankCcConversionService;
import com.liquidnet.service.adam.service.feign.fin.IAdamFeignAccountService;
import com.liquidnet.service.adam.service.sys.IAdamSysEmailService;
import com.liquidnet.service.adam.service.sys.IAdamSystemService;
import com.liquidnet.service.adam.util.PDFUtil;
import com.liquidnet.service.bank.currencycloud.dto.BankCcConversionDatesDto;
import com.liquidnet.service.bank.currencycloud.dto.BankCcConversionDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 换汇 服务实现类
 * </p>
 *
 * @author liquidnet
 * @since 2020-11-10
 */
@Slf4j
@Service
public class AdamConversionApiServiceImpl implements IAdamConversionApiService {

    @Autowired
    private CustomIdGenerator customIdGenerator;

    @Autowired
    private IAdamFeignAccountService adamFeignAccountService;

    @Autowired
    private IAdamFeignBankCcConversionService adamFeignBankCcConversionService;

    @Autowired
    private IAdamTransactionService adamTransactionService;

    @Autowired
    private IAdamTransactionApiService adamTransactionApiService;

    @Autowired
    private IAdamConversionService adamConversionService;

    @Autowired
    private IAdamAccountApiService adamAccountApiService;

    @Autowired
    private IAdamAccountWalletService adamAccountWalletService;

    @Autowired
    private IAdamPayoutApiService adamPayoutApiService;

    @Autowired
    private AdamConversionMapper adamConversionMapper;

    @Autowired
    private IAdamSysEmailService adamSysEmailService;

    @Autowired
    private IAdamSystemService adamSystemService;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public AdamResultDto<AdamConversion> createConversion(AdamConversionParam param) {

        // 参数检查
        AdamResultDto checkResult = checkParam(param);
        if (!checkResult.isSuccess()) {
            return checkResult;
        }
        // 初始化数据库数据
        AdamResultDto<AdamConversion> resultDto = initConversion(param);

        String conversionId = resultDto.getData().getId();
        param.setId(conversionId);
        // 向资金账户推送交易
        AdamResultDto pushResult = ((AdamConversionApiServiceImpl) AopContext.currentProxy()).pushFinConversion(conversionId);
        if (!pushResult.isSuccess()) {
            return pushResult;
        }

        // 渠道创建换汇
        if (AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode().equals(resultDto.getData().getBankChannel())) {
            AdamResultDto<BankCcConversionDto> ccConversionResult = this.createCcConversion(param);
            resultDto.setCode(ccConversionResult.getCode());
            resultDto.setMessage(ccConversionResult.getMessage());
        }

        return resultDto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public AdamResultDto<AdamConversion> initConversion(AdamConversionParam param) {

        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 新增conversion记录
        AdamConversion conversion = new AdamConversion();
        conversion.setId(adamTransactionApiService.generateNumber(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode()));
        conversion.setComId(param.getComId());
        conversion.setUserId(param.getUserId());
        conversion.setStatus(AdamConversionConstants.StatusEnum.READY_TO_SETTLE.getCode());
        conversion.setFromWalletNo(param.getFromWalletNo());
        conversion.setToWalletNo(param.getToWalletNo());
        conversion.setSellAmount(param.getSellAmount());
        conversion.setSellCurrency(param.getSellCurrency());
        conversion.setBuyCurrency(param.getBuyCurrency());
        conversion.setBuyAmount(param.getBuyAmount());
        conversion.setFixedSide(param.getFixedSide());
        conversion.setQuoteRate(param.getQuoteRate());
        conversion.setExecutionRate(param.getExecutionRate());
        conversion.setConversionDate(LocalDateTime.parse(param.getConversionDateAt(), df));
        conversion.setSettlementDate(LocalDateTime.parse(param.getSettlementDateAt(), df));
        conversion.setBankChannel(AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode());
        conversion.setBankConversionId("");
        conversion.setCreateTime(now);
        int i = adamConversionService.insert(conversion);

        AdamTransaction fromTransaction = new AdamTransaction();
        fromTransaction.setId(String.valueOf(customIdGenerator.nextId(fromTransaction)));
        fromTransaction.setNumber(conversion.getId());
        fromTransaction.setComId(conversion.getComId());
        fromTransaction.setType(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode());
        fromTransaction.setStatus(conversion.getStatus());
        fromTransaction.setCurrency(conversion.getSellCurrency());
        fromTransaction.setFundAmount(conversion.getSellAmount());
        fromTransaction.setFundDirection(AdamTransactionConstants.FundsDirectionEnum.DEBIT.getCode());
        fromTransaction.setRelatedId(conversion.getId());
        fromTransaction.setRelatedReference("");
        fromTransaction.setBankTransactionId("");
        fromTransaction.setBankChannel(conversion.getBankChannel());
        fromTransaction.setFinWalletNo(conversion.getFromWalletNo());
        fromTransaction.setFinWalletName(param.getFromWalletName());
        fromTransaction.setFinWalletTransactionId("");
        fromTransaction.setFinWalletAvailableBalance(new BigDecimal("0"));
        fromTransaction.setFinWalletTotalBalance(new BigDecimal("0"));
        fromTransaction.setCreateTime(now);
        int i1 = adamTransactionService.insert(fromTransaction);

        AdamTransaction toTransaction = new AdamTransaction();
        toTransaction.setId(String.valueOf(customIdGenerator.nextId(toTransaction)));
        toTransaction.setNumber(conversion.getId());
        toTransaction.setComId(conversion.getComId());
        toTransaction.setType(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode());
        toTransaction.setStatus(conversion.getStatus());
        toTransaction.setCurrency(conversion.getBuyCurrency());
        toTransaction.setFundAmount(conversion.getBuyAmount());
        toTransaction.setFundDirection(AdamTransactionConstants.FundsDirectionEnum.CREDIT.getCode());
        toTransaction.setRelatedId(conversion.getId());
        toTransaction.setRelatedReference("");
        toTransaction.setBankTransactionId("");
        toTransaction.setBankChannel(conversion.getBankChannel());
        toTransaction.setFinWalletNo(conversion.getToWalletNo());
        toTransaction.setFinWalletName(param.getToWalletName());
        toTransaction.setFinWalletTransactionId("");
        toTransaction.setFinWalletAvailableBalance(new BigDecimal("0"));
        toTransaction.setFinWalletTotalBalance(new BigDecimal("0"));
        toTransaction.setCreateTime(now);

        int i2 = adamTransactionService.insert(toTransaction);

        return AdamResultDto.success(conversion);
    }

    @Override
    public AdamResultDto<BankCcConversionDto> createCcConversion(AdamConversionParam param) {

        String conversionId = param.getId();

        AdamResultDto<BankCcConversionDto> ccResult = adamFeignBankCcConversionService.createConversion(param);
        if (!ccResult.isSuccess()) {
            AdamTransactionParam transactionParam = new AdamTransactionParam();
            transactionParam.setRelatedId(conversionId);
            transactionParam.setType(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode());
            List<AdamTransaction> list = adamTransactionService.selectList(transactionParam);
            if (CollectionUtils.isEmpty(list)) {
                return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008002);
            }
            for (AdamTransaction transaction : list) {

                AdamConversion c = new AdamConversion();
                c.setId(conversionId);
                c.setStatus(AdamConversionConstants.StatusEnum.FAILED.getCode());
                c.setFailureReason(ccResult.getMessage());
                c.setUpdateTime(LocalDateTime.now());

                AdamTransaction t = new AdamTransaction();
                t.setId(transaction.getId());
                t.setStatus(c.getStatus());
                t.setFailureReason(c.getFailureReason());
                t.setUpdateTime(LocalDateTime.now());
                adamTransactionApiService.updateConversion(t, c);
            }
            this.pushFinConversion(conversionId);
            return AdamResultDto.success();

        } else {
            return this.bankCcConversion(ccResult.getData());
        }

    }

    @Override
    public AdamResultDto bankCcConversion(BankCcConversionDto param) {
        String conversionId = param.getUniqueRequestId();
        AdamConversion conversion = adamConversionService.selectById(conversionId);
        if (conversion == null) {
            AdamConversion selectParam = new AdamConversion();
            selectParam.setBankConversionId(param.getId());
            conversion = adamConversionService.selectOne(selectParam);
            conversionId = conversion.getId();
        }

        String selectStatus = conversion.getStatus();

        // 数据库最终状态直接返回
        if (AdamConversionConstants.StatusEnum.COMPLETED.getCode().equals(selectStatus) || AdamConversionConstants.StatusEnum.FAILED.getCode().equals(selectStatus)) {
            return AdamResultDto.success();
        }
        String byBankStatus = AdamConversionConstants.StatusEnum.getByBankStatus(AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode(), param.getStatus());

        AdamTransactionParam transactionParam = new AdamTransactionParam();
        transactionParam.setRelatedId(conversionId);
        transactionParam.setType(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode());
        List<AdamTransaction> list = adamTransactionService.selectList(transactionParam);
        if (CollectionUtils.isEmpty(list)) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008002);
        }

        AdamConversion c = new AdamConversion();
        c.setId(conversionId);
        c.setExecutionRate(param.getClientRate());
        c.setStatus(byBankStatus);
        c.setBankChannel(AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode());
        c.setBankConversionId(param.getId());
        c.setFailureReason(param.getFailureReason());
        c.setUpdateTime(LocalDateTime.now());

        if (AdamConversionConstants.StatusEnum.COMPLETED.getCode().equals(byBankStatus)) {
            c.setCompleteTime(LocalDateTime.now());
        }
        adamConversionService.updateById(c);

        for (AdamTransaction transaction : list) {
            AdamTransaction t = new AdamTransaction();
            t.setId(transaction.getId());
            t.setStatus(byBankStatus);
            t.setUpdateTime(LocalDateTime.now());
            t.setCompleteTime(c.getCompleteTime());
            adamTransactionService.updateById(t);
        }

        // CC最终状态需要再一次给资金账户推送交易信息
        if (AdamConversionConstants.StatusEnum.COMPLETED.getCode().equals(byBankStatus) || AdamConversionConstants.StatusEnum.FAILED.getCode().equals(byBankStatus)) {
            this.pushFinConversion(conversionId);
        }
        // 换汇入金执行下发
        if (AdamConversionConstants.StatusEnum.COMPLETED.getCode().equals(byBankStatus)) {
            adamPayoutApiService.asyncAwaitingFundsDoPayout(conversion.getToWalletNo());
        }
        return AdamResultDto.success(param);
    }

    @Override
    public AdamResultDto pushFinConversion(String conversionId) {
        AdamConversion conversion = adamConversionService.selectById(conversionId);
        if (conversion == null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008002);
        }
        log.info("push fin conversion,status=[{}],conversion=[{}]", conversion.getStatus(), JSON.toJSONString(conversion));


        AdamTransactionParam param = new AdamTransactionParam();
        param.setRelatedId(conversionId);
        param.setType(AdamTransactionConstants.TypeEnum.FX_CONVERSION.getCode());
        List<AdamTransaction> list = adamTransactionService.selectList(param);
        if (CollectionUtils.isEmpty(list)) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008002);
        }
        // 推送交易更新
        for (AdamTransaction adamTransaction : list) {
            AdamResultDto<String> dto = adamTransactionApiService.pushFinTransaction(adamTransaction);
            AdamTransaction t = new AdamTransaction();
            t.setUpdateTime(LocalDateTime.now());
            t.setId(adamTransaction.getId());
            if (dto.isSuccess()) {
                t.setFinWalletTransactionId(dto.getData());
                adamTransactionService.updateById(t);
            }
        }

        return AdamResultDto.success();
    }

    @Override
    public AdamResultDto<AdamConversionDateDto> getConversionDates(String bankChannel, String conversionPair, String startDate) {

        AdamConversionDateDto adamDate = new AdamConversionDateDto();
        if (AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode().equals(bankChannel)) {
            AdamResultDto<BankCcConversionDatesDto> resultDto = adamFeignBankCcConversionService.getConversionDates(conversionPair, startDate);
            if (!resultDto.isSuccess()) {
                return AdamResultDto.failure(resultDto.getCode(), resultDto.getMessage());
            }
            BankCcConversionDatesDto bankDate = resultDto.getData();
            BeanUtils.copyProperties(bankDate, adamDate);

            Map<Date, String> map = bankDate.getInvalidConversionDates();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            List<String> collect = map.keySet().stream().map(s -> simpleDateFormat.format(s)).collect(Collectors.toList());
            adamDate.setInvalidConversionDateList(collect);

        }

        return AdamResultDto.success(adamDate);
    }

    @Override
    public AdamResultDto sendConfirm1(String conversionId, String comId) {
        try {
            AdamConversion param = new AdamConversion();
            param.setId(conversionId);
            param.setComId(comId);
            AdamConversion adamConversion = adamConversionService.selectOne(param);
            if (adamConversion == null) {
                return AdamResultDto.failure(AdamErrorCode.PARAM_ERROR);
            }
            String userId = adamConversion.getUserId();
            // 按照当前的时间段,进行存储到redis中
            String sendConfirmKey = sendConfirmGetKey(0);
            HashSet<String> set = (HashSet<String>) redisUtil.hget(sendConfirmKey, userId);
            if (set == null) {
                set = new HashSet<>();
            }
            set.add(conversionId);
            redisUtil.hset(sendConfirmKey, userId, set, 7200);
        } catch (Exception e) {
            log.info("adam conversion sendConfirm1 error", e);
        }
        return AdamResultDto.success();
    }

    @Override
    public AdamResultDto sendConfirm2() {
        String sendConfirmKey = sendConfirmGetKey(30);
        HashSet<String> hkeys = (HashSet<String>) redisUtil.hkeys(sendConfirmKey);

        for (String userId : hkeys) {
            HashSet<String> setList = (HashSet<String>) redisUtil.hget(sendConfirmKey, userId);
            List<String> list = new ArrayList<>(setList);
            int index = adamSystemService.conversionPdfPageSize();
            int total = list.size();
            int count = total / index;
            int ays = total % index;
            //分100条分批发邮件
            for (int i = 0; i <= count; i++) {
                List<String> subList = new ArrayList<>();
                if (i < count) {
                    subList = new ArrayList(list.subList(i * index, (i + 1) * index));
                } else if (ays != 0) {
                    subList = new ArrayList(list.subList(i * index, i * index + ays));
                }
                if (!CollectionUtils.isEmpty(subList)) {
                    List<AdamConversion> conversionList = adamConversionMapper.selectBatchIds(subList);
                    String mkdir = sendConfirmKey.replaceAll(":", File.separator);
                    // 测试时候,路径需要修改成本地的
                    String file = "/data/pdf/" + mkdir + "/" + userId + "/" + i + ".pdf";
//                    String file = "/Users/lichen/Downloads/" + mkdir + "/" + userId + "/" + i + ".pdf";
                    // 创建PDF文件
                    if (sendConfirmCreatePdf(conversionList, file)) {
                        // 发送带PDF文件的邮件
                        //            adamSysEmailService.asyncSendEmail("","","");
                    }

                }
            }
        }

        return AdamResultDto.success();
    }

    private static boolean sendConfirmCreatePdf(List<AdamConversion> list, String file) {
        boolean b = false;
        if (CollectionUtils.isEmpty(list)) {
            return b;
        }
        try {
            List<Map> paramMapList = new ArrayList<>();
            for (AdamConversion conversion : list) {
                Map<String, Object> paramMap = new HashMap<>();
                paramMap.put("transactionType", AdamTransactionConstants.TypeEnum.FX_CONVERSION.getDesc());
                paramMap.put("transactionNumber", conversion.getId());
                paramMap.put("transactionCreateTime", DateUtil.format(conversion.getCreateTime(), DateUtil.Formatter.english));
                paramMap.put("transactionCompleteTime", DateUtil.format(conversion.getCompleteTime(), DateUtil.Formatter.english));
                AdamConversionConstants.StatusEnum statusEnum = AdamConversionConstants.StatusEnum.getEnumByCode(conversion.getStatus());
                paramMap.put("transactionStatus", statusEnum != null ? statusEnum.getDesc() : "");
                paramMap.put("sellAmount", conversion.getSellCurrency() + " " + conversion.getSellAmount());
                paramMap.put("buyAmount", conversion.getBuyCurrency() + " " + conversion.getBuyAmount());
                paramMap.put("executionRate", conversion.getExecutionRate());
                paramMap.put("conversionDate", DateUtil.format(conversion.getConversionDate(), DateUtil.Formatter.english));
                paramMap.put("settlementDate", DateUtil.format(conversion.getSettlementDate(), DateUtil.Formatter.english));
                paramMap.put("fromWalletNo", conversion.getFromWalletNo());
                paramMap.put("toWalletNo", conversion.getToWalletNo());
                paramMapList.add(paramMap);
            }

            // 本地生成一个多页合并的pdf文件
            b = PDFUtil.createPdfMerger(paramMapList, file, "fx_conversion.vm");

        } catch (Exception e) {
            log.error("adam conversion send email pdf error", e);
        }
        return b;

    }

    /**
     * 获取换汇发送邮件的时间key
     *
     * @param minusMinutes 减少多少分钟
     */
    private static String sendConfirmGetKey(int minusMinutes) {

        LocalDateTime now = LocalDateTime.now();

        if (minusMinutes != 0) {
            now = now.minusMinutes(minusMinutes);
        }
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = now.format(dateTimeFormatter);

        String hour = String.valueOf(now.getHour());

        if (hour.length() == 1) {
            hour = "0" + hour;
        }

        String minute = "00";
        if (now.getMinute() >= 30) {
            minute = "30";
        }
        String s = date + hour + minute;
        return AdamRedisConstants.CONVERSION_SEND_CONFIRM_KEY + s;

    }

    /**
     * 参数校验
     */
    private AdamResultDto checkParam(AdamConversionParam param) {
        String comId = param.getComId();
        String userId = param.getUserId();
        String fromWalletNo = param.getFromWalletNo();
        String toWalletNo = param.getToWalletNo();
        String buyCurrency = param.getBuyCurrency();
        BigDecimal buyAmount = param.getBuyAmount();

        String sellCurrency = param.getSellCurrency();
        BigDecimal sellAmount = param.getSellAmount();

        // 校验from币种账号是否存在
        AdamResultDto<WalletAccountInfoVo> fromWalletResult = adamFeignAccountService.getWallet(comId, fromWalletNo);
        if (!fromWalletResult.isSuccess()) {
            return fromWalletResult;
        }
        // 校验from币种账户金额
        WalletAccountInfoVo fromWalletInfo = fromWalletResult.getData();
        if (fromWalletInfo.getBalanceAvailable().compareTo(sellAmount) < 0) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008003.getCode(), AdamErrorCode.ADAM_CONVERSION_008003.getVal() + "[" + fromWalletNo + "]");
        }
        // 校验from账户币种与sellCurrency币种是否一致
        if (!fromWalletInfo.getCurrency().equals(sellCurrency)) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008004.getCode(), AdamErrorCode.ADAM_CONVERSION_008004.getVal() + "[" + fromWalletNo + "]");
        }

        // 校验to币种, 和toWalllet
        AdamAccountWalletParam param1 = new AdamAccountWalletParam();
        param1.setComId(comId);
        param1.setState(AdamAccountConstants.StateEnum.S2.getCode());
        param1.setCurrency(buyCurrency);
        AdamAccountWallet toWallet = adamAccountWalletService.selectOne(param1);
        if (toWallet == null) {
            AdamAccountCreateParam createWallet = new AdamAccountCreateParam();
            createWallet.setComId(comId);
            createWallet.setUserId(userId);
            createWallet.setCurrency(buyCurrency);
            createWallet.setName("");
            createWallet.setBankChannel(AdamBankConstants.BankChannelEnum.CURRENCY_CLOUD.getCode());
            createWallet.setSource("conversion_create");
            AdamResultDto<AdamAccountWallet> accountWallet = adamAccountApiService.createAccountWallet(createWallet);
            if (!accountWallet.isSuccess()) {
                log.error("conversion create buy currency wallet faild. accountWallet=[{}]", JsonUtils.toJson(accountWallet));
                return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008005);
            }
            toWalletNo = accountWallet.getData().getFinWalletNo();
            param.setToWalletNo(toWalletNo);
        } else if (!StringUtils.isEmpty(toWalletNo) && !toWallet.getFinWalletNo().equals(toWalletNo)) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008006);
        } else {
            param.setToWalletNo(toWallet.getFinWalletNo());
        }

        AdamConversionConstants.FixedSideEnum fixedSideEnum = AdamConversionConstants.FixedSideEnum.getEnumByCode(param.getFixedSide());
        if (fixedSideEnum == null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_CONVERSION_008001);
        }

        return AdamResultDto.success();
    }

}
