package com.yc.ship.module.trade.service.contract;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.yc.ship.framework.common.lang.Asserts;
import com.yc.ship.framework.common.pojo.CommonResult;
import com.yc.ship.framework.common.pojo.PageResult;
import com.yc.ship.framework.common.util.object.BeanUtils;
import com.yc.ship.framework.tenant.core.context.TenantContextHolder;
import com.yc.ship.module.infra.api.config.ConfigApi;
import com.yc.ship.module.trade.api.contract.dto.*;
import com.yc.ship.module.trade.controller.admin.contract.vo.ContractNoticeReqVO;
import com.yc.ship.module.trade.controller.admin.contract.vo.ContractPageReqVO;
import com.yc.ship.module.trade.controller.admin.contract.vo.ContractReqVO;
import com.yc.ship.module.trade.controller.admin.contract.vo.ContractSaveReqVO;
import com.yc.ship.module.trade.dal.dataobject.contract.ContractDO;
import com.yc.ship.module.trade.dal.mysql.contract.ContractMapper;
import com.yc.ship.module.trade.enums.ApiConstants;
import com.yc.ship.module.trade.enums.ContractStatusEnum;
import com.yc.ship.module.trade.enums.ErrorCodeConstants;
import com.yc.ship.module.trade.enums.InvokeContractCodeEnum;
import com.yc.ship.module.trade.service.contract.bo.ContractApplyReqBO;
import com.yc.ship.module.trade.service.contract.bo.ContractNoticeDataBO;
import com.yc.ship.module.trade.utils.CommonUUCodeUtils;
import com.yc.ship.module.trade.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yc.ship.framework.common.pojo.CommonResult.error;
import static com.yc.ship.framework.common.pojo.CommonResult.success;
import static com.yc.ship.module.trade.enums.ErrorCodeConstants.*;

/**
 * 合同 Service 实现类
 *
 * @author 管理员
 */
@Service
@Validated
@Slf4j
public class ContractServiceImpl implements ContractService {

    @Resource
    private ContractMapper contractMapper;
    @Resource
    private ConfigApi configApi;
    @Resource
    private ContractCommonService contractCommonService;
    @Resource
    private RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<?> applyContract(ContractApplyReqDTO requestDTO) {
        Long orderId = requestDTO.getOrderId();
        String orderNo = requestDTO.getOrderNo();
        List<ContractDO> contractDOList = this.getContractByOrder(orderId);
        if (CollUtil.isNotEmpty(contractDOList)) {
            log.error("合同申报失败，该订单已存在一个有效合同");
            throw exception(ErrorCodeConstants.ORDER_VALID_CONTRACT);
        }
        try {
            ContractApplyReqBO contractRequest = new ContractApplyReqBO();
            //自定义合同编号取订单 ID
            String erpContractId = CommonUUCodeUtils.createERPContractId();
            contractRequest.setERPContractId(erpContractId);
            contractRequest.setTemplateId(requestDTO.getTemplateId());
            //旅行社
            TravelAgency travelAgency = new TravelAgency();
            travelAgency.setTransactorName(configApi.getConfigValueByKey("contract.transactorName"));
            travelAgency.setAgencyName(configApi.getConfigValueByKey("contract.agencyName"));
            travelAgency.setTransactorPhone(configApi.getConfigValueByKey("contract.transactorPhone"));
            travelAgency.setTravelAgencyLicenseNumber(configApi.getConfigValueByKey("contract.travelAgencyLicenseNumber"));
            travelAgency.setBusinessLicenseNumber(configApi.getConfigValueByKey("contract.businessLicenseNumber"));
            travelAgency.setServicePhone(configApi.getConfigValueByKey("contract.servicePhone"));
            contractRequest.setTravelAgency(travelAgency);
            //行程
            Itinerary itinerary = new Itinerary();
            itinerary.setInputType(1);
            Route route = new Route();
            route.setDescription(requestDTO.getTripDescription());
            ArrayList<Route> routes = new ArrayList<>();
            routes.add(route);
            itinerary.setRoutes(routes);
            itinerary.setRouteName(requestDTO.getRouteName());
            itinerary.setRouteName(requestDTO.getTripDescription());
            itinerary.setStartDate(requestDTO.getTravelDate());
            //itinerary.setDescription(requestDTO.getTripDescription());
            contractRequest.setItinerary(itinerary);
            //游客名单
            TouristsInfo touristsInfo = new TouristsInfo();
            ArrayList<Tourist> tourists = new ArrayList<>();
            if (CollUtil.isNotEmpty(requestDTO.getContractTouristList())) {
                touristsInfo.setTotalNumber(requestDTO.getTouristQuantity());
                int number = 0;
                for (ContractTouristDTO touristDTO : requestDTO.getContractTouristList()) {
                    Tourist tourist = new Tourist();
                    tourist.setIsSigner(number == 0 ? true : false);
                    tourist.setNumber(number);
                    tourist.setName(touristDTO.getName());
                    tourist.setGender(touristDTO.getGender());
                    tourist.setPhone(touristDTO.getMobile());
                    IdCard touristIdCard = new IdCard();
                    if(touristDTO.getIdCardType().equals("0")){
                        touristIdCard.setIDType("1");
                    }else {
                        touristIdCard.setIDType("8");
                    }
                    touristIdCard.setIDNumber(touristDTO.getIdCardNo());
                    tourist.setID(touristIdCard);
                    tourists.add(tourist);
                    number++;
                }
            }
            touristsInfo.setTourists(tourists);
            contractRequest.setTouristsInfo(touristsInfo);
            //签署人
            Signatory signatory = new Signatory();
            signatory.setName(requestDTO.getTravelerName());
            IdCard signatoryIdCard = new IdCard();
            if(requestDTO.getTravelerIdCardType().equals("0")){
                signatoryIdCard.setIDType("1");
            }else {
                signatoryIdCard.setIDType("8");
            }
            signatoryIdCard.setIDNumber(requestDTO.getTravelerIdCardNo());
            signatory.setID(signatoryIdCard);
            signatory.setPhone(requestDTO.getTravelerMobile());
            //签约方式允许值：1:短信签约，2:现场签约，3.线下签约
            //固定传 1
            signatory.setMode(1);
            contractRequest.setSignatory(signatory);
            //费用
            Cost cost = new Cost();
            cost.setTotalCost(requestDTO.getOrderAmount());
            cost.setPaymentTime(new Date());
            //cost.setGuideServiceCost(10);
            cost.setPaymentMethod(requestDTO.getPayType());
            contractRequest.setCost(cost);
            //争议解决部分
            //允许值：1：提交仲裁委员会仲裁，2：向人民法院提起诉讼
            //固定传1
            Dispute dispute = new Dispute();
            dispute.setResolution(1);
            dispute.setTribunalName(configApi.getConfigValueByKey("contract.tribunalName"));
            contractRequest.setDispute(dispute);
            //自费项目
            contractRequest.setActivities(Collections.EMPTY_LIST);
            //购物部分
            contractRequest.setHasShopping(false);
            //保存合同信息
            ContractDO contractBo = this.convertContract(requestDTO, orderId,orderNo, erpContractId);
            String key = "order_contract_" + orderId;
            RLock lock = redissonClient.getLock(key);
            try {
                if (lock.tryLock(20, 60, TimeUnit.SECONDS)) {
                    //保存合同信息
                    contractMapper.insert(contractBo);
                    //调用第三方合同服务
                    QueryContractResponse resp = contractCommonService.applyContract(contractRequest);
                    Asserts.isTrue(resp != null, "合同申请失败");
                    if (resp!=null && null != resp.getData()) {
                        contractBo.setContractNo(resp.getData().getContractNumber());
                        contractBo.setContractUrl(resp.getData().getFileURL());
                        contractBo.setViewUrl(resp.getData().getViewURL());
                        contractBo.setSigningUrl(resp.getData().getSigningURL());
                        contractBo.setStatus(ContractStatusEnum.SIGNING.getValue());
                        contractMapper.updateById(contractBo);
                        return success(resp.getData());
                    } else {
                        if (resp != null && null != resp.getError()) {
                            contractBo.setResCode(resp.getError().getCode().toString());
                            contractBo.setResMsg(resp.getError().getMessage());
                            contractMapper.updateById(contractBo);
                        }
                        return error(resp.getError().getCode(),resp.getError().getMessage());
                    }

                } else {
                    return error(REPEAT_APPLY);
                }
            } catch (Exception e) {
                log.error("合同申报异常，：", e);
                throw exception(CONTRACT_APPLY_FAILED);
            } finally {
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } catch (Exception e) {
            log.error("合同申报异常，：", e);
            throw exception(CONTRACT_APPLY_FAILED);
        }
    }

    private ContractDO convertContract(ContractApplyReqDTO requestDTO, Long orderId,String orderNo, String erpContractId) {
        ContractDO contractBo = new ContractDO();
        contractBo.setOrderId(orderId);
        contractBo.setOrderNo(orderNo);
        contractBo.setErpContractId(erpContractId);
        contractBo.setTouristsCount(requestDTO.getTouristQuantity());
        contractBo.setSignerName(requestDTO.getTravelerName());
        contractBo.setSignerMobile(requestDTO.getTravelerMobile());
        contractBo.setOperateName(requestDTO.getOperatorName());
        contractBo.setOperateMobile(requestDTO.getOperatorMobile());
        contractBo.setTemplateId(requestDTO.getTemplateId());
        Long id = IdWorker.getId(contractBo);
        contractBo.setId(id);
        contractBo.setDeleted(false);
        contractBo.setStatus(ContractStatusEnum.FAIL.getValue());
        return contractBo;
    }

    @Override
    public void updateContract(ContractSaveReqVO updateReqVO) {
        // 校验存在
        validateContractExists(updateReqVO.getId());
        // 更新
        ContractDO updateObj = BeanUtils.toBean(updateReqVO, ContractDO.class);
        contractMapper.updateById(updateObj);
    }

    @Override
    public void deleteContract(Long id) {
        // 校验存在
        validateContractExists(id);
        // 删除
        contractMapper.deleteById(id);
    }

    private void validateContractExists(Long id) {
        if (contractMapper.selectById(id) == null) {
            throw exception(CONTRACT_NOT_EXISTS);
        }
    }

    @Override
    public ContractDO getContract(Long id) {
        return contractMapper.selectById(id);
    }

    @Override
    public PageResult<ContractDO> getContractPage(ContractPageReqVO pageReqVO) {
        return contractMapper.selectPage(pageReqVO);
    }

    @Override
    @Transactional
    public Boolean cancel(Long orderId) {
        List<ContractDO> contractDOList = this.getContractByOrder(orderId);

        Asserts.isTrue(CollUtil.isNotEmpty(contractDOList), "订单{}无有效合同", orderId);

        ContractDO contractDO = contractDOList.get(0);

        if (contractDO.getStatus().equals(ContractStatusEnum.CANCELLING.getValue())) {
            throw exception(ErrorCodeConstants.CONTRACT_BEING_INVALIDATED);
        }
        if (contractDO.getStatus().equals(ContractStatusEnum.CANCELED.getValue())) {
            throw exception(ErrorCodeConstants.CONTRACT_HAS_BEEN_INVALIDATED);
        }
        //调用作废合同接口
        InvokeResponse result = contractCommonService.invalidateContract(contractDO.getContractNo());

        if (result.getCode() == InvokeContractCodeEnum.SUCCESS.getCode()) {
            this.updateContractStatus(contractDO.getId(), ContractStatusEnum.CANCELLING.getValue());
            return true;
        }
        return false;
    }

    @Override
    public boolean sendContractSms(ContractReqVO reqVO) {
        ContractDO contractDO = this.getContract(reqVO.getId());
        if (!contractDO.getStatus().equals(ContractStatusEnum.SIGNING.getValue())) {
            throw exception(ErrorCodeConstants.CONTRACT_NOT_SIGNING);
        }

        boolean sendContractSms = contractCommonService.sendContractSms(contractDO.getContractNo(), contractDO.getSignerMobile());

        return sendContractSms;
    }

    @Override
    public QueryContractRespDTO queryContract(Long contractId) {
        ContractDO contractDO = this.getContract(contractId);
        QueryContractRespDTO response = contractCommonService.queryContract(contractDO.getContractNo());
        return response;
    }

    @Override
    @Transactional
    public Integer queryContractStatus(Long id) {
        ContractDO contractDO = this.getContract(id);
        Integer contractState = contractCommonService.queryContractStatus(contractDO.getContractNo());
        if (contractState != null) {
            ContractStatusEnum statusEnum = ContractStatusEnum.queryStateTransform(contractState);
            if (statusEnum != null && !Objects.equals(statusEnum.getValue(), contractDO.getStatus())) {
                this.updateContractStatus(contractDO.getId(), statusEnum.getValue());
            }
        }
        return contractState;
    }

    @Override
    public void updateContractStatus(Long contractId, Integer status) {
        LambdaUpdateWrapper<ContractDO> wrapper = new LambdaUpdateWrapper<>();
        //已签约
        if (status.equals(ContractStatusEnum.SIGNED.getValue())) {
            wrapper.set(ContractDO::getSigningTime, LocalDateTime.now());
        }
        wrapper.set(ContractDO::getStatus, status);
        wrapper.eq(ContractDO::getId, contractId);
        contractMapper.update(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String contractCallBackNotice(String body) {
        ContractNoticeReqVO reqObj = JSONUtil.toBean(body, ContractNoticeReqVO.class);

        if (reqObj.getData() == null || StringUtils.isBlank(reqObj.getData().getContractNumber())) {
            throw exception(ErrorCodeConstants.NOTICE_PARAMETER_IS_EMPTY);
        }
        String contractNumber = reqObj.getData().getContractNumber();
        List<ContractDO> contractDOList = contractMapper.selectContractList(contractNumber);

        if (CollUtil.isEmpty(contractDOList)){
            return ApiConstants.ERROR;
        }
        TenantContextHolder.setTenantId(contractDOList.get(0).getTenantId());
        QueryContractRespDTO respDTO = contractCommonService.queryContract(contractNumber);

        if (respDTO == null) {
            log.error("调用第三方查询合同接口查询合同详情失败");
            return ApiConstants.ERROR;
        }
        if (StringUtils.isBlank(respDTO.getContract().getERPContractId())) {
            log.error("自定义合同编号为空");
            return ApiConstants.ERROR;
        }
        //入参中的sign和本地进行加密的值进行对比
        Boolean checkFlag = null;
        try {
            if (!this.validSign(reqObj.getData(), respDTO)) {
                log.error("合同回调验签失败");
                return ApiConstants.ERROR;
            }
            //合同信息
            LambdaQueryWrapper<ContractDO> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ContractDO::getContractNo, contractNumber);
            wrapper.eq(ContractDO::getErpContractId, respDTO.getContract().getERPContractId());
            wrapper.orderByDesc(ContractDO::getCreateTime);
            ContractDO contract = contractMapper.selectOne(wrapper);

            if (contract == null) {
                return ApiConstants.ERROR;
            }
            ContractStatusEnum status = ContractStatusEnum.callBackStateTransform(reqObj.getData().getState());
            this.updateContractStatus(contract.getId(), status.getValue());
            return ApiConstants.SUCCESS;
        } catch (Exception e) {
            return ApiConstants.ERROR;
        }
    }


    private Boolean validSign(ContractNoticeDataBO data, QueryContractRespDTO queryContract) throws Exception {
        String sign = data.getSign();
        String append = validContractParams(queryContract);
        TreeMap<String, Object> treeMap = new TreeMap<>(String::compareTo);
        if (StringUtils.isNotBlank(data.getSignatoryIDNumber())) {
            treeMap.put("signatoryIDNumber", data.getSignatoryIDNumber());
        }
        if (data.getState() != null) {
            treeMap.put("state", String.valueOf(data.getState()));
        }
        if (StringUtils.isNotBlank(data.getContent())) {
            treeMap.put("content", data.getContent());
        }
        if (StringUtils.isNotBlank(data.getSignatoryPhone())) {
            treeMap.put("signatoryPhone", data.getSignatoryPhone());
        }
        if (StringUtils.isNotBlank(data.getContractNumber())) {
            treeMap.put("contractNumber", data.getContractNumber());
        }
        if (StringUtils.isNotBlank(data.getContractType())) {
            treeMap.put("contractType", data.getContractType());
        }

        StringBuilder dataStr = new StringBuilder();
        for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
            dataStr.append(entry.getValue());
        }
        String signStr = append + dataStr;
        String md5 = SecurityUtils.md5Sign(signStr);

        if (md5.equals(sign)) {
            return true;
        }
        return false;
    }

    private String validContractParams(QueryContractRespDTO queryContract) {
        /**
         * 1.	合同数据部分：这部分字段根据推送内容中的合同编号查找到相应的合同并获取。
         *          旅行社自定义合同编号（ERPContractId），
         *          经办人电话（transactorPhone），
         *          旅行社许可证号（travelAgencyLicenseNumber）。
         *          >> 如果字段没有，则取空字符串。
         * 2.	推送数据部分：结果数据（data）部分除sign、url字段外的其他字段值按ASCII码从小到大顺序。
         *          >>注意: 该部分字段会不定期更新（比如新增字段），处理时如果硬编码可能会导致校验不通过。
         *
         * 将上述两部分字段的值直接拼接取md5值作为签名字段
         */

        StringBuilder builder = new StringBuilder();
        if (queryContract == null || queryContract.getContract() == null) {
            return StringUtils.EMPTY;
        }
        if (queryContract.getContract() != null && StringUtils.isNotBlank(queryContract.getContract().getERPContractId())) {
            String erpContractId = queryContract.getContract().getERPContractId();

            builder.append(erpContractId);
            if (queryContract.getContract().getTravelAgency() != null) {
                if (StringUtils.isNotBlank(queryContract.getContract().getTravelAgency().getTransactorPhone())) {
                    String transactorPhone = queryContract.getContract().getTravelAgency().getTransactorPhone();
                    builder.append(transactorPhone);

                }
                if (StringUtils.isNotBlank(queryContract.getContract().getTravelAgency().getTravelAgencyLicenseNumber())) {
                    String travelAgencyLicenseNumber = queryContract.getContract().getTravelAgency().getTravelAgencyLicenseNumber();

                    builder.append(travelAgencyLicenseNumber);
                }
            }
        }

        return builder.toString();
    }

    /**
     * 通过订单查询合同有效订单
     *
     * @param orderId
     * @return
     */
    @Override
    public List<ContractDO> getContractByOrder(Long orderId) {
        LambdaQueryWrapper<ContractDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ContractDO::getOrderId, orderId);
        queryWrapper.in(ContractDO::getStatus, ContractStatusEnum.SIGNED.getValue(), ContractStatusEnum.SIGNING.getValue());
        return contractMapper.selectList(queryWrapper);
    }
}