package com.sonic.lion.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sonic.lion.enums.*;
import com.sonic.lion.utils.KeyGenerator;
import com.sonic.lion.dao.*;
import com.sonic.lion.domain.bo.WithdrawTradeExtendBo;
import com.sonic.lion.domain.entity.*;
import com.sonic.lion.domain.enums.CallChannelStatus;
import com.sonic.lion.domain.input.BuffChangeInput;
import com.sonic.lion.domain.input.ChannelPayoutInput;
import com.sonic.lion.domain.input.WithdrawFeeInput;
import com.sonic.lion.domain.output.ChannelPayoutOutput;
import com.sonic.lion.service.*;
import com.sonic.lion.enums.ToastResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

import static com.sonic.lion.enums.PayGenrtatorCodeType.TRADE;

@Slf4j
@Service
public class WithdrawServiceImpl implements WithdrawService {

    @Value("${site.type}")
    private String siteType;
    @Autowired
    private PayCallChannelService payCallChannelService;

    @Autowired
    private AccountBuffService accountBuffService;

    @Autowired
    private FreeWithdrawConfigService freeWithdrawConfigService;

    @Autowired
    private PayTradeDao payTradeDao;

    @Autowired
    private PayTradeService payTradeService;

    @Autowired
    private AccountBuffBillService accountBuffBillService;

    @Autowired
    private AccountBuffDao accountBuffDao;

    @Autowired
    private FreeWithdrawFeeBillDao freeWithdrawFeeBillDao;

    @Autowired
    private ProcessingWithdrawReviewDao processingWithdrawReviewDao;
    @Autowired
    private ProcessingWithdrawDao processingWithdrawDao;

    @Autowired
    private PayAccountFundThirdService payAccountFundThirdService;

    @Override
    public void checkAmount(PayChannel payChannel, Long amount, Long userId) {
        if (PayChannel.STRIPE.equals(payChannel)) {
            ToastResultCode.AMOUNT_LESS_THAN_N.check(amount < 100, "1.00");
        }
    }

    /**
     * 提现审核 提现先审核一天时间
     * 后定时任务 再 进行提现。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void withdrawReview(WithdrawRequest input) {
        String tradeNo = KeyGenerator.instance().generatorUniqueKey(TRADE);
        String outTradeNo = tradeNo;


        BizType bizType = BizType.WITHDRAW;
        PaymentType paymentType = PaymentType.BALANCE;
        //入参金额为扣款金额，真实能到用户账上的金额需从里面减去手续费

        PayTrade payTrade = PayTrade.builder()
                .id(IdWorker.getId())
                .platform(input.getPlatform())
                .tradeNo(tradeNo)
                .outTradeNo(outTradeNo)
                .srcAccountId(input.getSrcAccountId())
                .srcAccountName(input.getSrcAccountName())
                .desAccountNo(input.getDesAccountNo())
                .desAccountName(input.getDesAccountName())
                .payChannel(PayChannel.get(input.getPayChannel()))
                .channelName(PayChannel.get(input.getPayChannel()).getDesc())
                .bizType(bizType)
                .status(TradeStatus.PROCESSING)
                .name(input.getName())
                .occurAmount(input.getAmount())
                .paymentType(paymentType)
                .coinType(input.getCoinType())
                .createTime(input.getCreateTime())
                .build();

        // 计算手续费
        WithdrawFeeInput fee = freeWithdrawConfigService.calculateWithdrawFee(payTrade);
        payTrade.setAmount(input.getAmount() - fee.getWithdrawFee());
        payTrade.setFee(fee.getWithdrawFee());
        payTrade.setThirdFee(fee.getThirdFee());
        payTrade.setPlatformFee(fee.getPlatformFee());

        if(fee.getFreeWithdrawBillId() != null || StringUtils.isNotEmpty(input.getCurrencyType())) {
            WithdrawTradeExtendBo bo = new WithdrawTradeExtendBo();
            //如果有免手续费记录 就写入记录ID
            if (fee.getFreeWithdrawBillId() != null) {
                bo.setFreeWithdrawBillId(fee.getFreeWithdrawBillId());
            }
            //设置扩展字段，设置目标用户的提现货币类型
            if(StringUtils.isNotEmpty(input.getCurrencyType())) {
                bo.setCurrencyType(input.getCurrencyType());
            }
            payTrade.setExtend(JSONObject.toJSONString(bo));
        }

        //写入流水表 状态为 处理中。
        AccountBuff accountBuff = accountBuffService.getByUid(input.getSrcAccountId());
        Long billId = accountBuffBillService.insertBillWhenWithdraw(payTrade, accountBuff);
        //关联下流水表
        payTrade.setOutTradeNoRelationNo(billId.toString());
        payTradeDao.insert(payTrade);

        int n = accountBuffDao.decWithdrawableIncome(accountBuff.getId(), payTrade.getOccurAmount());

        ToastResultCode.INSUFFICIENT_BALANCE.check(n != 1);
        //更新 提现在途资金
        accountBuffService.addWithdrawOnGoing(accountBuff.getId(), payTrade.getOccurAmount());

        ProcessingWithdrawReview processingWithdraw = ProcessingWithdrawReview.builder()
                .tradeId(payTrade.getId())
                .tradeNo(tradeNo)
                .createTime(LocalDateTime.now())
                .payChannel(payTrade.getPayChannel())
                .build();
        processingWithdrawReviewDao.insert(processingWithdraw);
    }

    /**
     * 过了审核期 的进行 提现操作。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handWithdrawWhenReviewTimeOut(PayTrade payTrade) {
        //调用 三方渠道进行提现。 去掉了之前的发送MQ逻辑 简化代码。因为这里是定时任务处理 没有客户直接请求 不同步返回数据。 不影响用户体验。
        PayTrade db = payTradeService.getByTradeNo(payTrade.getTradeNo());
        ChannelPayoutInput input = ChannelPayoutInput.builder()
                .amount(payTrade.getAmount())
                .thirdFee(payTrade.getThirdFee())
                .desAccountNo(payTrade.getDesAccountNo())
                //提现渠道暂时写死 目前只支持PAYPAL
                //增加了新的提现渠道
                .payChannel(db.getPayChannel())
                .remark(payTrade.getRemark())
                .tradeNo(payTrade.getTradeNo())
                .payTrade(payTrade)
                .bizType(payTrade.getBizType())
                .userId(payTrade.getSrcAccountId())
                .tradeId(payTrade.getId())
                .build();
        //根据渠道调用结果 更新状态 或 回滚资金
        try {
            ChannelPayoutOutput payoutOutput = payCallChannelService.callPayout(input);
            log.info("提现渠道最终状态: {}", payoutOutput);
            if (CallChannelStatus.FAIL.equals(payoutOutput.getStatus())) {
                log.info("提现调用失败开始回滚资金, tradeNo: {}", input.getTradeNo());
                withdrawFail(input.getTradeNo(), payTrade.getBizType(), payoutOutput.getErrorMessage());

            } else if (CallChannelStatus.PROCESSING.equals(payoutOutput.getStatus())) {
                accountBuffBillService.updateWithdrawStatus(Long.valueOf(payTrade.getOutTradeNoRelationNo()), AccountBuffBill.WithdrawStatus.WITHDRAW_ING, "");
            } else if (CallChannelStatus.SUCC.equals(payoutOutput.getStatus())) {

                AccountBuff accountBuff = accountBuffService.getByUid(payTrade.getSrcAccountId());
                accountBuffService.decWithdrawOnGoing(accountBuff.getId(), payTrade.getOccurAmount());
                accountBuffBillService.updateWithdrawStatus(Long.valueOf(payTrade.getOutTradeNoRelationNo()), AccountBuffBill.WithdrawStatus.WITHDRAW_SUCCESS, "");

                accountBuffBillService.updateWithdrawBillAmount(Long.valueOf(payTrade.getOutTradeNoRelationNo()), payTrade.getOccurAmount());
                //提现成功，发送系统消息
            }
            log.info("提现逻辑执行完成, tradeNo: {}", input.getTradeNo());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawFail(@NonNull String tradeNo, BizType bizType, String message) {
        PayTrade payTrade = payTradeService.getByTradeNo(tradeNo);
        ToastResultCode.DATA_NOT_EXITS.check(payTrade == null);
        ToastResultCode.UNSUPPORTED_BUSINESS_TYPE.check(payTrade.getBizType() != BizType.WITHDRAW);
        ToastResultCode.DATA_STATUS_INCORRECT.check(payTrade.getStatus() != TradeStatus.PROCESSING);
        //确认支付渠道提现状态
        PayCallChannelRecord payCallChannelRecord = payCallChannelService.getByTradeNoAndBizTypeLast(payTrade.getTradeNo(), payTrade.getBizType());
        ToastResultCode.DATA_STATUS_INCORRECT.check(payCallChannelRecord != null && payCallChannelRecord.getStatus() != CallChannelStatus.FAIL);
        if (!BizType.WITHDRAW.equals(bizType)) {
            return;
        }
        AccountBuff accountBuff = accountBuffService.getByUid(payTrade.getSrcAccountId());
        accountBuffService.withdrawOnGoingRollback(accountBuff.getId(), payTrade.getOccurAmount());
        //更新状态为 关闭
        accountBuffBillService.updateWithdrawStatus(Long.valueOf(payTrade.getOutTradeNoRelationNo()), AccountBuffBill.WithdrawStatus.WITHDRAW_FAIL, message);
        //提现失败，插入一条返还的流水
        accountBuffBillService.insertBillWhenWithdrawFail(Long.valueOf(payTrade.getOutTradeNoRelationNo()), accountBuff);
        //更新支付交易状态为CLOSED
        payTradeService.updateStatus(payTrade.getId(), TradeStatus.CLOSED, TradeStatus.PROCESSING);
        try {
            //如果之前免手续费 , 回滚手续费记录表
            if (StringUtils.isNotEmpty(payTrade.getExtend())) {
                //解析出实体对象
                WithdrawTradeExtendBo bo = JSONObject.parseObject(payTrade.getExtend(), WithdrawTradeExtendBo.class);
                if(bo.getFreeWithdrawBillId() != null) {
                    //构造更新数据
                    FreeWithdrawFeeBill updater = FreeWithdrawFeeBill.builder().id(bo.getFreeWithdrawBillId()).deleted(Boolean.TRUE).build();
                    freeWithdrawFeeBillDao.updateById(updater);
                }
            }
        } catch (Exception e) {
            //因为有老数据，所以这里先吃掉异常进行处理（主要目的是为了兼容老数据）
            log.error("===> withdrawFail handler FreeWithdrawFeeBill error : ", e);
            if (StringUtils.isNotEmpty(payTrade.getExtend())) {
                //构造更新数据（当老数据在解析json时出现异常，所以在这里直接拿扩展字段值进行处理）
                FreeWithdrawFeeBill updater = FreeWithdrawFeeBill.builder().id(Long.valueOf(payTrade.getExtend())).deleted(Boolean.TRUE).build();
                freeWithdrawFeeBillDao.updateById(updater);
            }
        }
        //提现失败，发送系统消息
    }


    /**
     * 提现成功
     *
     * @param tradeNo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawSuccess(@NonNull String tradeNo) {
        PayTrade payTrade = payTradeService.getByTradeNo(tradeNo);
        if (!BizType.WITHDRAW.equals(payTrade.getBizType())) {
            return;
        }
        String outTradeNoRelationNo = payTrade.getOutTradeNoRelationNo();
        Long billId = Long.valueOf(outTradeNoRelationNo);
        accountBuffBillService.updateWithdrawStatus(billId, AccountBuffBill.WithdrawStatus.WITHDRAW_SUCCESS, "");
        payTradeService.updateStatus(payTrade.getId(), TradeStatus.FINISHED, TradeStatus.PROCESSING);
        AccountBuff accountBuff = accountBuffService.getByUid(payTrade.getSrcAccountId());
        accountBuffService.decWithdrawOnGoing(accountBuff.getId(), payTrade.getOccurAmount());
        accountBuffBillService.updateWithdrawBillAmount(billId, payTrade.getOccurAmount());
        //提现成功，发送系统消息
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void mockWithdrawToFinalState(String tradeNo, ChannelPayoutOutput payoutOutput) {
        PayTrade db = payTradeService.getByTradeNo(tradeNo);
        //找不到数据，抛出异常
        ToastResultCode.DATA_NOT_EXITS.check(db == null || BizType.WITHDRAW != db.getBizType());
        //判断交易状态，只能为处理中、处理成功的状态下才允许进行处理。如果状态为成功但入参状态不为失败时也不能进行处理
        if((TradeStatus.PROCESSING != db.getStatus() && TradeStatus.FINISHED != db.getStatus()) || (CallChannelStatus.FAIL != payoutOutput.getStatus() && TradeStatus.FINISHED == db.getStatus())) {
            ToastResultCode.PAY_TRADE_STATUS_CHANGE.check(true);
        }
        log.info("提现渠道最终状态: {}", payoutOutput);
        if (CallChannelStatus.FAIL.equals(payoutOutput.getStatus())) {
            //这里分为处理中的失败和 成功后的失败，成功后的失败就是走补偿的逻辑
            if(TradeStatus.PROCESSING == db.getStatus()) {
                //重新从三方渠道获取详情，并更新渠道调用表的状态为失败
                PayCallChannelRecord record = payCallChannelService.getByTradeNoAndBizTypeLast(db.getTradeNo(), db.getBizType());
                if(record == null) {
                    log.info("===> PayCallChannelRecord not found");
                    return;
                }
                //先将渠道表的处理状态更新成失败
                payCallChannelService.updateStatus(record.getId(), CallChannelStatus.FAIL, CallChannelStatus.PROCESSING);
                //走失败的逻辑
                withdrawFail(tradeNo, db.getBizType(), payoutOutput.getErrorMessage());
            } else if(TradeStatus.FINISHED == db.getStatus()) {
                //重新从三方渠道获取详情，并更新渠道调用表的状态为失败
                PayCallChannelRecord record = payCallChannelService.getByTradeNoAndBizTypeLast(db.getTradeNo(), db.getBizType());
                //渠道调用记录不存在 或者 状态不为已成功 直接快速返回不处理
                if(record == null || CallChannelStatus.SUCC != record.getStatus()) {
                    log.info("===> PayCallChannelRecord not found");
                    return;
                }
                //渠道调用表 修改成失败
                payCallChannelService.updateStatus(record.getId(), CallChannelStatus.FAIL, CallChannelStatus.SUCC);
                //交易表 修改成失败
                payTradeService.updateStatus(db.getId(), TradeStatus.CLOSED, TradeStatus.FINISHED);

                BuffChangeInput input = new BuffChangeInput();
                input.setUid(db.getSrcAccountId());
                input.setBuff(db.getOccurAmount());
                input.setTradeNo(db.getTradeNo());
                input.setErrorMessage(payoutOutput.getErrorMessage());
                //回滚资金，并将交易号对应的流水状态更新为失败
                accountBuffService.addWithdrawableIncome(input);

                AccountBuff accountBuff = accountBuffService.getByUid(db.getSrcAccountId());
                //提现失败，再插入一条返还的流水
                accountBuffBillService.insertBillWhenWithdrawFail(Long.valueOf(db.getOutTradeNoRelationNo()), accountBuff);
            }
        } else if (CallChannelStatus.SUCC.equals(payoutOutput.getStatus())) {
            AccountBuff accountBuff = accountBuffService.getByUid(db.getSrcAccountId());
            accountBuffService.decWithdrawOnGoing(accountBuff.getId(), db.getOccurAmount());
            accountBuffBillService.updateWithdrawStatus(Long.valueOf(db.getOutTradeNoRelationNo()), AccountBuffBill.WithdrawStatus.WITHDRAW_SUCCESS, "");

            accountBuffBillService.updateWithdrawBillAmount(Long.valueOf(db.getOutTradeNoRelationNo()), db.getOccurAmount());
            //提现成功，发送系统消息
        }
        log.info("提现逻辑执行完成, tradeNo: {}", db.getTradeNo());
        //删除处理中的数据
        ProcessingWithdraw processingWithdraw = processingWithdrawDao.selectOne(Wrappers.<ProcessingWithdraw>lambdaQuery()
                .eq(ProcessingWithdraw::getTradeNo, db.getTradeNo()));
        if(processingWithdraw != null) {
            processingWithdrawDao.deleteById(processingWithdraw.getId());
        }
    }


}
