package com.clx.performance.service.impl.loan;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clx.message.feign.ClxMessageOpenapiFeign;
import com.clx.message.req.message.AliSmsMessageReq;
import com.clx.order.enums.ResultEnum;
import com.clx.performance.component.IdGenerateSnowFlake;
import com.clx.performance.config.ClxMessageConfig;
import com.clx.performance.config.loan.ClxPayeeConfig;
import com.clx.performance.config.loan.PaymentFromConfig;
import com.clx.performance.config.nbbank.NbBankConfig;
import com.clx.performance.dao.loan.*;
import com.clx.performance.enums.PerformanceResultEnum;
import com.clx.performance.enums.loan.OwnerLoanRecordEnum;
import com.clx.performance.enums.loan.OwnerPaymentEnum;
import com.clx.performance.enums.loan.OwnerRePaymentEnum;
import com.clx.performance.enums.nbbank.NbBankStatusEnum;
import com.clx.performance.event.OwnerLoanFlagEvent;
import com.clx.performance.event.OwnerRepaymentUpdateEvent;
import com.clx.performance.extranal.user.impl.OwnerInfoServiceImpl;
import com.clx.performance.model.loan.*;
import com.clx.performance.param.pc.loan.carrier.PageCarrierOwnerRepaymentParam;
import com.clx.performance.param.pc.loan.carrier.PageOwnerRepaymentOfOwner;
import com.clx.performance.param.pc.loan.owner.ExportPaymentApplicationFormParam;
import com.clx.performance.service.loan.OwnerRepaymentService;
import com.clx.performance.service.thirdparty.nbbank.NbBankService;
import com.clx.performance.struct.loan.OwnerRepaymentStruct;
import com.clx.performance.vo.pc.loan.carrier.OwnerRepaymentVO;
import com.clx.performance.vo.pc.loan.owner.ExportOwnerRepaymentVO;
import com.clx.performance.vo.pc.loan.owner.OwnerCashierInfoVO;
import com.clx.performance.vo.pc.loan.owner.OwnerOrderPaymentDetailVO;
import com.clx.performance.vo.pc.loan.owner.OwnerTransferPaymentDetailVO;
import com.clx.performance.vo.pc.nbbank.NbBankOrderResultVO;
import com.clx.user.vo.feign.OwnerInfoFeignVO;
import com.clx.user.vo.pc.owner.OwnerBindCardVO;
import com.msl.common.base.Optional;
import com.msl.common.exception.ServiceSystemException;
import com.msl.common.result.Result;
import com.msl.common.utils.DateUtils;
import com.msl.document.api.feign.ContractEvidenceFeign;
import com.msl.document.api.feign.ContractTemplateFeign;
import com.msl.document.api.param.GenerateContractParam;
import com.msl.document.api.vo.ContractEvidenceRecordVo;
import com.msl.user.data.UserSessionData;
import com.msl.user.utils.TokenUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author kavin
 * Date 2024-01-20
 * Time 13:31
 */
@Service
@Slf4j
@AllArgsConstructor
public class OwnerRepaymentServiceImpl  implements OwnerRepaymentService {

    private final OwnerRepaymentDao ownerRepaymentDao;
    private final OwnerRepaymentStruct ownerRepaymentStruct;
    private final ContractEvidenceFeign contractEvidenceFeign;
    private final ContractTemplateFeign contractTemplateFeign;
    private final PaymentFromConfig paymentFromConfig;
    private final OwnerInfoServiceImpl ownerInfoService;
    private final NbBankConfig nbBankConfig;
    private final ClxMessageOpenapiFeign clxMessageOpenapiFeign;
    private final ClxMessageConfig messageConfig;
    private final NbBankService bankService;
    private final BankTradeDao bankTradeDao;
    private final IdGenerateSnowFlake idGenerateSnowFlake;
    private final OwnerPaymentDao ownerPaymentDao;
    private final OwnerLoanRecordDao ownerLoanRecordDao;
    private final ApplicationEventPublisher applicationEventPublisher;
    private final OwnerLoanAccountDao ownerLoanAccountDao;

    @Override
    public IPage<OwnerRepaymentVO> pageOwnerRepayment(PageCarrierOwnerRepaymentParam param) {
        IPage<OwnerRepayment> page = ownerRepaymentDao.pageOwnerRepayment(param);
        List<OwnerRepaymentVO> records = ownerRepaymentStruct.convertList(page.getRecords());
        IPage<OwnerRepaymentVO> returnPage = new Page<>();
        returnPage.setPages(page.getPages());
        returnPage.setTotal(page.getTotal());
        returnPage.setRecords(records);
        return returnPage;
    }

    @Override
    public IPage<OwnerRepaymentVO> ownerRepaymentOverdueList() {
        UserSessionData loginUserInfo = TokenUtil.getLoginUserInfo();
        PageCarrierOwnerRepaymentParam param = new PageCarrierOwnerRepaymentParam();
        param.setOwnerUserNo(String.valueOf(loginUserInfo.getUserNo()));
        List<Integer> inStatusList = new ArrayList<>();
        inStatusList.add(OwnerRePaymentEnum.Status.PAYING.getCode());
        inStatusList.add(OwnerRePaymentEnum.Status.PAY_WAIT.getCode());
        inStatusList.add(OwnerRePaymentEnum.Status.PAY_FAIL.getCode());
        param.setInStatusList(inStatusList);
        param.setBeOverdue(OwnerRePaymentEnum.BeOverdue.YES.getCode());

        IPage<OwnerRepayment> page = ownerRepaymentDao.pageOwnerRepayment(param);
        List<OwnerRepaymentVO> records = ownerRepaymentStruct.convertList(page.getRecords());
        IPage<OwnerRepaymentVO> returnPage = new Page<>();
        returnPage.setPages(page.getPages());
        returnPage.setTotal(page.getTotal());
        returnPage.setRecords(records);
        return returnPage;
    }


    @Override
    public IPage<OwnerRepaymentVO> pageOwnerRepaymentOfOwner(PageOwnerRepaymentOfOwner param) {
        return ownerRepaymentDao.pageByParam(param);
    }

    @Override
    public OwnerCashierInfoVO getCashierInfo(Integer id) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getEntityByKey(id).orElseThrow(
                PerformanceResultEnum.DATA_NOT_FIND);
        OwnerCashierInfoVO vo = ownerRepaymentStruct.convertCashierInfo(ownerRepayment);

        OwnerInfoFeignVO ownerInfoFeignVO = ownerInfoService.getOwnerInfo(ownerRepayment.getOwnerUserNo());
        String ownerAccountBank = Objects.isNull(ownerInfoFeignVO.getOwnerAccountBank())? StringUtils.EMPTY:ownerInfoFeignVO.getOwnerAccountBank().trim();
        vo.setOrderPayWay(false);
        if(nbBankConfig.getOrderSupportBank().contains(ownerAccountBank)){
            vo.setOrderPayWay(true);
        }
        vo.setBankName(ownerAccountBank);
        vo.setPayAccount(ownerInfoFeignVO.getOwnerBankAccount());
        return vo;
    }

    @Override
    public OwnerOrderPaymentDetailVO getOrderPaymentDetail(Integer id) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getEntityByKey(id).orElseThrow(
                PerformanceResultEnum.DATA_NOT_FIND);
        OwnerOrderPaymentDetailVO vo = ownerRepaymentStruct.convertOrderPaymentDetail(ownerRepayment);

        ClxPayeeConfig.PayeeConfig payeeMap = ClxPayeeConfig.getPayee(ClxPayeeConfig.XXH_ID);
        vo.setPayee(payeeMap.getName());
        vo.setPayeeAccount(payeeMap.getAccount());
        //计算倒计时时间，下单时间 + 30天 为截止时间
        LocalDateTime endTime = ownerRepayment.getCreateTime().plusDays(30);
        vo.setFinalPaymentTime(DateUtils.formatDateTime(endTime).get());
        return vo;
    }

    @Override
    public OwnerTransferPaymentDetailVO getTransferPaymentDetail(Integer id) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getEntityByKey(id).orElseThrow(
                PerformanceResultEnum.DATA_NOT_FIND);
        ClxPayeeConfig.PayeeConfig payeeMap = ClxPayeeConfig.getPayee(ClxPayeeConfig.XXH_ID);
        return OwnerTransferPaymentDetailVO.builder().payee(payeeMap.getName())
                .payeeAccount(payeeMap.getAccount()).payeeBank(payeeMap.getBank()).payeeBankNo(payeeMap.getBankNo())
                .repaymentBalance(ownerRepayment.getRepaymentBalance())
                .remittanceIdentificationCode(ownerRepayment.getRemittanceIdentificationCode())
                .status(ownerRepayment.getStatus()).finalPaymentTime(ownerRepayment.getCloseOrderTime())
                .repaymentNo(ownerRepayment.getRepaymentNo()).build();
    }

    @Override
    public String savePaymentApplicationForm(ExportPaymentApplicationFormParam param) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getEntityByKey(param.getId()).orElseThrow(
                PerformanceResultEnum.DATA_NOT_FIND);
        ExportOwnerRepaymentVO exportOwnerRepaymentVO = ownerRepaymentStruct.convert(ownerRepayment);
        Map<String,String> map = JSON.parseObject(JSON.toJSONString(exportOwnerRepaymentVO), new TypeReference<Map<String,String>>(){});
        // 创建合同
        GenerateContractParam feignParam = new GenerateContractParam();
        if(Objects.equals(OwnerLoanRecordEnum.PayChannel.ORDER_DIRECT_PAY.getCode(),param.getPayWay())){
            feignParam.setTemplateNo(Long.valueOf(paymentFromConfig.getRepaymentOrderTemplateNo()));
        }else{
            feignParam.setTemplateNo(Long.valueOf(paymentFromConfig.getRepaymentTransferTemplateNo()));
        }

        feignParam.setParametersValueMap(map);
        Result<Long> longResult = contractTemplateFeign.generateContract(feignParam);
        Result<ContractEvidenceRecordVo> contractEvidenceDetail = contractEvidenceFeign.getContractEvidenceDetail(longResult.getData());
        return contractEvidenceDetail.getData().getFileUrl();
    }

    @Override
    public String sendPaymentSms(String mobile,Long repaymentNo) {
        AliSmsMessageReq req = new AliSmsMessageReq();
        req.setTemplateCode(messageConfig.getRepaymentTemplateCode());
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("repaymentNo", repaymentNo);
        req.setChannelId(messageConfig.getChannelId());
        req.setAppId(messageConfig.getAppId().toString());
        req.setMobile(mobile);
        req.setContent(jsonObject.toString());
        req.setExpire(300L);
        clxMessageOpenapiFeign.sendAliSms(req);

        return UUID.randomUUID().toString();
    }

    @Override
    public String sendOrderPaySms(String mobile,String payee, String payeeAccount) {
        AliSmsMessageReq req = new AliSmsMessageReq();
        req.setTemplateCode(messageConfig.getOrderPayTemplateCode());
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("companyName", payee);
        jsonObject.set("bankAccount", payeeAccount);
        req.setChannelId(messageConfig.getChannelId());
        req.setAppId(messageConfig.getAppId().toString());
        req.setMobile(mobile);
        req.setContent(jsonObject.toString());
        req.setExpire(300L);
        clxMessageOpenapiFeign.sendAliSms(req);

        return UUID.randomUUID().toString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void ownerRepaymentCancelPay(String repaymentNo) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getOneByField(OwnerRepayment::getRepaymentNo, repaymentNo)
                .orElseThrow(PerformanceResultEnum.DATA_NOT_FIND);
        if (!ownerRepayment.getStatus().equals(OwnerRePaymentEnum.Status.PAYING.getCode())) {
            log.info("还款记录状态异常,不是付款中，不能取消");
            throw new ServiceSystemException(PerformanceResultEnum.OWNER_LOAN_RECORD_CANCEL_STATUS_ERROR);
        }
        BankTrade bankTrade = bankTradeDao.selectByRelationNo(repaymentNo).get();

        NbBankOrderResultVO result = bankService.getResult(bankTrade.getMerchantRunningWaterNo());
        Integer status = result.getStatus();

        if (NbBankStatusEnum.Status.FAIL.getCode().equals(status)
                || NbBankStatusEnum.Status.NOT_FOUND.getCode().equals(status)
        ) {
            log.info("宁波银行响应当前业务，不能取消{}", JSONUtil.parse(result));
            throw new ServiceSystemException(PerformanceResultEnum.OWNER_LOAN_RECORD_CANCEL_STATUS_ERROR);
        }
        log.info("还款记录取消，更新状态为待审批");
        ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_WAIT.getCode());
        ownerRepaymentDao.updateStatusById(ownerRepayment);
        if (OwnerLoanRecordEnum.PayChannel.ORDER_DIRECT_PAY.getCode().equals(ownerRepayment.getPayChannel())) {
            Integer tradeType = bankTrade.getTradeType();
            // 调用宁波退款
            bankService.refund(tradeType, bankTrade.getMerchantRunningWaterNo(), Integer.valueOf(ownerRepayment.getRepaymentBalance().movePointRight(2).toString()), bankTrade.getRunningWaterOpenNo());

        }

    }

    @Override
    public void ownerRepaymentRetryPay(String repaymentNo) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getOneByField(OwnerRepayment::getRepaymentNo, repaymentNo)
                .orElseThrow(PerformanceResultEnum.DATA_NOT_FIND);
        ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_WAIT.getCode());
        // 更新借款信息
        ownerRepaymentDao.updateStatusById(ownerRepayment);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void paySuccess(String merSeqNo) {
        // 查询交易流水
        BankTrade bankTrade = bankTradeDao.getOneByField(BankTrade::getMerchantRunningWaterNo, merSeqNo)
                .orElseThrow(PerformanceResultEnum.DATA_NOT_FIND);
        // 查询还款信息
        Optional<OwnerRepayment> optional = ownerRepaymentDao.getOneByField(OwnerRepayment::getRepaymentNo, bankTrade.getRelationNo());
        if (!optional.isPresent()){
            log.info("还款信息不存在,通过流水号查询的bankTrade的relationNo未在还款中查询到,流水号{}", merSeqNo);
            return;
        }
        OwnerRepayment ownerRepayment = optional.get();
        if (!ownerRepayment.getStatus().equals(OwnerRePaymentEnum.Status.PAYING.getCode())) {
            log.info("当前还款信息状态不是付款中,数据为{}", JSONUtil.parse(ownerRepayment));
            return;
        }

        ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_SUCCESS.getCode());
        // 更新还款信息
        ownerRepaymentDao.updateStatusById(ownerRepayment);

        OwnerLoanRecord ownerLoanRecord = ownerLoanRecordDao.getOneByField(OwnerLoanRecord::getLoanNo,
                ownerRepayment.getLoanNo()).orElseThrow(PerformanceResultEnum.DATA_NOT_FIND);


        while (true) {
            OwnerLoanAccount ownerLoanAccount = ownerLoanAccountDao.getOneByField(OwnerLoanAccount::getOwnerUserNo,
                    ownerLoanRecord.getOwnerUserNo()).get();
            OwnerLoanAccount entity = new OwnerLoanAccount();
            if (OwnerLoanRecordEnum.LoanType.FUND.getCode().equals(ownerLoanRecord.getLoanType())) {
                entity.setFundingArrears(ownerLoanRecord.getLoanBalance());
                entity.setVirtuallyArrears(BigDecimal.ZERO);
            } else {
                entity.setFundingArrears(BigDecimal.ZERO);
                entity.setVirtuallyArrears(ownerLoanRecord.getLoanBalance());
            }
            entity.setFundingFrozenBalance(BigDecimal.ZERO);
            entity.setFundingUsableBalance(BigDecimal.ZERO);
            entity.setFundingAccountBalance(BigDecimal.ZERO);

            entity.setVirtuallyFrozenBalance(BigDecimal.ZERO);
            entity.setVirtuallyUsableBalance(BigDecimal.ZERO);
            entity.setVirtuallyAccountBalance(BigDecimal.ZERO);
            entity.setId(ownerLoanAccount.getId());

            entity.setModifiedTime(ownerLoanAccount.getModifiedTime());
            log.info("还款成功,清除货主欠款,当前货主虚拟币欠款={},当前货主资金账户欠款={},",
                    ownerLoanAccount.getVirtuallyAccountBalance(), ownerLoanAccount.getFundingAccountBalance()
            );

            log.info("还款更新账户参数为：{}", JSONUtil.parse(entity));
            Integer flag = ownerLoanAccountDao.updateAccountCAS(entity, LocalDateTime.now(), false);
            if (flag > 0) {
                break;
            }
        }

        //生成付款记录
        OwnerPayment ownerPayment = new OwnerPayment();
        ownerPayment.setOwnerUserNo(ownerRepayment.getOwnerUserNo());
        ownerPayment.setOwnerUserName(ownerRepayment.getOwnerUserName());
        ownerPayment.setMobile(ownerRepayment.getMobile());
        ownerPayment.setPaymentNo(idGenerateSnowFlake.nextId(1L));
        ownerPayment.setRepaymentNo(ownerRepayment.getRepaymentNo());
        ownerPayment.setPaymentBalance(ownerRepayment.getRepaymentBalance());
        //付款单的付款方取借款单的付款方
        ownerPayment.setPayment(ownerRepayment.getPayee());
        ownerPayment.setPaymentAccount(ownerRepayment.getPayeeAccount());
        //付款单的收款方取借款单的借出方
        ownerPayment.setPayee(ownerLoanRecord.getBorrower());
        ownerPayment.setPayeeAccount(ownerLoanRecord.getBorrowerAccount());
        ownerPayment.setPayeeBankCode(ownerLoanRecord.getBorrowerBankCode());

        ownerPayment.setStatus(OwnerPaymentEnum.Status.PAY_WAIT.getCode());
        ownerPayment.setPayChannel(OwnerLoanRecordEnum.PayChannel.ASSET.getCode());
        ownerPayment.setCreateBy("系统");
        ownerPaymentDao.saveEntity(ownerPayment);

        applicationEventPublisher.publishEvent(new OwnerLoanFlagEvent(this, ownerRepayment.getLoanNo()));

    }

    @Override
    public void payFail(String merSeqNo) {
        // 查询交易流水
        BankTrade bankTrade = bankTradeDao.getOneByField(BankTrade::getMerchantRunningWaterNo, merSeqNo)
                .orElseThrow(PerformanceResultEnum.DATA_NOT_FIND);
        // 查询还款信息
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getOneByField(OwnerRepayment::getRepaymentNo, bankTrade.getRelationNo()).get();
        ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_FAIL.getCode());
        // 更新借款信息
        ownerRepaymentDao.updateStatusById(ownerRepayment);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjust(Integer id) {
        OwnerRepayment ownerRepayment = ownerRepaymentDao.getEntityByKey(id).orElseThrow(ResultEnum.DATA_NOT_FIND);
        if (!ownerRepayment.getStatus().equals(OwnerRePaymentEnum.Status.PAYING.getCode())) {
            log.info("还款记录状态异常,不是付款中，不能核销");
            throw new ServiceSystemException(PerformanceResultEnum.OWNER_LOAN_RECORD_ADJUST_STATUS_ERROR);
        }

        NbBankOrderResultVO resultVO = bankService.adjust(ownerRepayment.getMerchantRunningWaterNo(),ownerRepayment.getRepaymentBalance().intValue());
        if(Objects.equals(resultVO.getStatus(),NbBankStatusEnum.Status.SUCCESS.getCode())){
            ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_SUCCESS.getCode());
            this.paySuccess(ownerRepayment.getMerchantRunningWaterNo());

        }else{
            ownerRepayment.setStatus(OwnerRePaymentEnum.Status.PAY_FAIL.getCode());
        }
        ownerRepayment.setOperateBy(TokenUtil.getLoginUserInfo().getUserName());
        // 更新借款信息
        ownerRepaymentDao.updateStatusById(ownerRepayment);
    }


}
