package com.pj.api.wx.service;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.pj.api.wx.WxUtils;
import com.pj.api.wx.bo.*;
import com.pj.api.wx.vo.PrePayVO;
import com.pj.current.config.MyConfig;
import com.pj.current.config.PartConfig;
import com.pj.current.config.WxConfig;
import com.pj.project.tb_account.TbAccountService;
import com.pj.project.tb_business.TbBusiness;
import com.pj.project.tb_business.TbBusinessService;
import com.pj.project.tb_business_car.TbBusinessCar;
import com.pj.project.tb_business_car.TbBusinessCarService;
import com.pj.project.tb_business_item.TbBusinessItem;
import com.pj.project.tb_business_item.TbBusinessItemService;
import com.pj.project.tb_charge_record.TbChargeRecord;
import com.pj.project.tb_costomer.TbCostomer;
import com.pj.project.tb_costomer.TbCostomerService;
import com.pj.project.tb_fee_details.TbFeeDetailsService;
import com.pj.project.tb_fee_statistics.TbFeeStatisticsService;
import com.pj.project.tb_order.TbOrder;
import com.pj.project.tb_order.TbOrderService;
import com.pj.project.tb_pay_record.TbPayRecord;
import com.pj.project.tb_pay_record.TbPayRecordService;
import com.pj.project4sp.global.BusinessException;
import com.pj.utils.cache.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.nio.charset.Charset;

import java.util.*;

@Service
@Transactional
@Slf4j
public class WxService {
    @Resource
    @Lazy
    private TbBusinessService tbBusinessService;
    @Resource
    WxConfig wxConfig;
    @Resource
    MyConfig myConfig;
    @Resource
    PartConfig partConfig;

    @Resource
    WxUtils wxUtils;
    @Resource
    TbBusinessCarService tbBusinessCarService;
    @Resource
    @Lazy
    TbBusinessItemService tbBusinessItemService;
    @Resource
    TbFeeStatisticsService tbFeeStatisticsService;
    @Resource
    private TbAccountService tbAccountService;
    @Resource
    TbFeeDetailsService tbFeeDetailsService;
    @Resource
    private TbOrderService tbOrderService;

    private final List<String> CAR_LIST = StrUtil.splitTrim("浙,粤,京,津,冀,晋,蒙,辽,黑,沪,吉,苏,皖,赣,鲁,豫,鄂,湘,桂,琼,渝,川,贵,云,藏, 陕, 甘, 青, 宁", ",");

    /**
     * 统一下单接口
     */
    public Map<String, String> prePay(HttpServletRequest request) throws Exception {
        String tradeType = request.getParameter("tradeType");
        String money = request.getParameter("money");
        if (Double.valueOf(money) <= 0) {
            throw new BusinessException("金额不正确");
        }
        String type = request.getParameter("type");
        if (StrUtil.isEmpty(type)) {
            throw new BusinessException("参数异常");
        }
        String openid = request.getParameter("openid");
        String desc = request.getParameter("desc");
//        String openid = "oDWvn5w-hVkzUuKeY7OBXBV_l1rU";
        String businessId = request.getParameter("b");
        String c = request.getParameter("c");
        String a = request.getParameter("a");
        Attach atchMap = new Attach();
        atchMap.setC(c).setB(businessId).setA(a);
        String out_trade_no = RandomUtil.randomString(32);
        Map<String, String> params = new HashMap<>();
        params.put("appid", wxConfig.getAppId());
        params.put("mch_id", wxConfig.getMachId());
        params.put("openid", openid);
        params.put("nonce_str", RandomUtil.randomString(32));
        params.put("body", desc);
        params.put("out_trade_no", out_trade_no);
        // params.put("attach", JSONUtil.toJsonStr(atchMap));
        String total_free = partConfig.isTestEnv() ? "1" : NumberUtil.mul(money, 100 + "").intValue() + "";
        log.info("pay free:{}", total_free);
        params.put("total_fee", total_free);
//        params.put("total_fee", "1");
        params.put("spbill_create_ip", getIpAddress(request));
        params.put("notify_url", myConfig.getDomain() + "/wx/notify");
        params.put("trade_type", tradeType);
        params.put("scene_info", SceneInfoBO.getSceneInfo(myConfig.getDomain(), wxConfig.getTitle()));
        String sign = wxUtils.sign(params, wxConfig.getKey());
        log.info("wx pay sign result:{}", sign);
        params.put("sign", sign);
        String prePayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String xmlParam = XmlUtil.mapToXmlStr(params);
        log.info("wx pay xml params:{}", xmlParam);
        String result = HttpUtil.createPost(prePayUrl).header("Content-Type", "text/xml").body(xmlParam)
                .execute().body();
        Map<String, Object> resultMap = XmlUtil.xmlToMap(result);
        log.info("request pre pay url result:{}", resultMap);
        PrePayVO vo = new PrePayVO();
        if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) {
            log.info("生成预支付订单成功,mweb_url:{}", resultMap.get("mweb_url"));
            Object webUrl = resultMap.get("mweb_url");
            Object prePayId = resultMap.get("prepay_id");
            String preId = prePayId == null ? "" : prePayId.toString();
            vo.setPayUrl(webUrl == null ? "" : webUrl.toString())
                    .setPrePayId(preId)
                    .setTradeType(tradeType);
            Map<String, String> payParams = getPayParams(openid, preId);
            payParams.put("outTradeNo", out_trade_no);
            TbOrder tbOrder = new TbOrder();
            tbOrder.setAttach(JSONUtil.toJsonStr(atchMap))
                    .setOpenid(openid)
                    .setOrderTime(new Date()).setBusinessType(Integer.valueOf(type))
                    .setOutTradeNo(out_trade_no).setPrice(money);
            return payParams;
        }
        throw new Exception("支付信息有误");
    }


    public Map<String, String> getPayParams(String openid, String prePayId) {
        Map<String, String> signMap = new HashMap<>(10);
        signMap.put("appId", wxConfig.getAppId());
        signMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
        signMap.put("nonceStr", RandomUtil.randomString(32));
        signMap.put("signType", "MD5");
        signMap.put("package", "prepay_id=" + prePayId);
        String sign = wxUtils.sign(signMap, wxConfig.getKey());
        signMap.put("paySign", sign);
        signMap.put("openId", openid);
        return signMap;
    }

    public Map<String, String> getPayP(String timeStamp, String nonceStr, String openid, String pack) {
        Map<String, String> signMap = new HashMap<>(10);
        signMap.put("appId", wxConfig.getAppId());
        signMap.put("timeStamp", timeStamp);
        signMap.put("nonceStr", nonceStr);
        signMap.put("signType", "MD5");
        signMap.put("package", pack);
        String sign = wxUtils.sign(signMap, wxConfig.getKey());
        signMap.put("paySign", sign);
        signMap.put("openId", openid);
        return signMap;
    }

    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip.contains(",")) {
            return ip.split(",")[0];
        } else {
            return ip;
        }
    }


    public void WxNotify(NotifyBO notifyBO) {
        String outTradeNo = notifyBO.getOutTradeNo();
        if (StrUtil.isNotEmpty(RedisUtil.get(outTradeNo))) {
            log.error("========订单已处理==========:{}", outTradeNo);
            return;
        }
        RedisUtil.setByHour(outTradeNo, DateUtil.now(), 1);
        TbOrder tbOrder = tbOrderService.findByOutTradeNo(outTradeNo);
        if (tbOrder == null) {
            log.error("========订单不存在==========:{}", outTradeNo);
            return;
        }
        if (StrUtil.equals("SUCCESS", tbOrder.getOrderStatus())) {
            log.error("========订单已支付==========:{}", outTradeNo);
            return;
        }
        String total_fee = notifyBO.getTotalFee();
        BigDecimal money = new BigDecimal(total_fee).divide(new BigDecimal(100), 2, BigDecimal.ROUND_UP);
        String attachStr = tbOrder.getAttach();
        String transactionId = notifyBO.getTransactionId();
        Date now = new Date();
        if (StrUtil.isNotEmpty(attachStr)) {
            Attach attach = JSONUtil.toBean(attachStr, Attach.class);
            List<PriceBO> cars = JSONUtil.toList(attach.getC(), PriceBO.class);
            tbFeeStatisticsService.countParkFee(cars);//停车费日统计
            for (PriceBO bo1 : cars) {
                TbBusinessCar car = tbBusinessCarService.getById(bo1.getId());
                BigDecimal price = bo1.getP();
                if (price == null || price.doubleValue() <= 0) {
                    log.error("付款金额不正确:{}", JSONUtil.toJsonStr(notifyBO));
                    continue;
                }
                car.setPay(1).setMoney(car.getMoney().add(price)).setPayTime(now);
                tbBusinessCarService.updateById(car);
                TbBusiness business = tbBusinessService.getById(car.getBusinessId());
                if (business != null) {
                    business.setPayMoney(business.getPayMoney().add(price));
                    business.setPayStatus(business.getPayMoney().equals(business.getTotalMoney()) ? 3 : 4);
                    tbBusinessService.updateById(business);
                }
            }
            tbFeeDetailsService.chargeParkFee(cars, transactionId, outTradeNo);//添加cars的收费明细
            String businessId = attach.getB();
            if (StrUtil.isNotEmpty(businessId)) {
                List<TbBusinessItem> items;
                List<TbBusiness> businessList = new ArrayList<>();
                if (TbBusiness.BusinessType.CAR_DISINCLE.getCode().equals(tbOrder.getBusinessType())) {
                    List<String> businessIds = StrUtil.splitTrim(businessId, ",");
                    items = tbBusinessItemService.findByBusinessIdList(businessIds);
                    businessList = tbBusinessService.listByIds(businessIds);
                } else {
                    items = tbBusinessItemService.findByBusinessId(businessId);
                    TbBusiness tbBusiness = tbBusinessService.getById(businessId);
                    businessList.add(tbBusiness);
                    //整车越南车变成已支付=免费
                    List<TbBusinessCar> carList = tbBusinessCarService.findByBusinessId(businessId);
                    carList.forEach(tbBusinessCar -> {
                        String carNo = tbBusinessCar.getCarNo();
                        String carNoStr = StrUtil.sub(carNo, 0, 1);
                        if (!CAR_LIST.contains(carNoStr)) {
                            tbBusinessCar.setPay(1).setPayTime(new Date());
                            tbBusinessCarService.updateById(tbBusinessCar);
                        }
                    });
                }
                tbFeeStatisticsService.countBusinessFee(items, transactionId, outTradeNo);//业务费日统计
                items.forEach(tbBusinessItem -> tbBusinessItem.setPayStatus(1).setPayTime(now));
                tbBusinessItemService.updateBatchById(items);
                businessList.forEach(tbBusiness -> {
                    tbBusiness.setPayTime(now).setPayType(3).setConfirmInput(1).setConfirmInputTime(now)
                            .setPayMoney(tbBusiness.getPayMoney().add(tbBusiness.getItemPrice()))
                            .setPayNo(transactionId);
                    tbBusiness.setPayStatus(tbBusiness.getPayMoney().equals(tbBusiness.getTotalMoney()) ? 3 : 4);
                    tbBusinessService.updateById(tbBusiness);
                });
            }
            String a = attach.getA();
            if (StrUtil.isNotEmpty(a)) {
                AccountChargeBO chargeBO = JSONUtil.toBean(a, AccountChargeBO.class);
                tbAccountService.recharge(chargeBO.getId(), chargeBO.getC(), TbChargeRecord.Ch.WECHAT.getType(), money);
            }
        }
        tbOrder.setOrderStatus("SUCCESS")
                .setTransactionId(notifyBO.getTransactionId())
                .setCompleteDate(notifyBO.getTimeEnd());
        tbOrderService.updateById(tbOrder);
    }


    public String getRedirectUrl(String path, String state) {
        String redirectUrl = myConfig.getWebDomain() + path;
        String encoderUrl = URLDecoder.decode(redirectUrl, Charset.forName("utf-8"));
        String url = wxConfig.getAuthLoginUrl().replace("REDIRECT_URI", encoderUrl);
        if (StrUtil.isNotEmpty(state)) {
            url = url.replace("STATE", state);
        }
        log.info("get redirect url:{}", url);
        return url;
    }

    public String getOpenidByCode(String code, String openid) {
        String openidUrl = wxConfig.getOpenidUrl().replace("CODE", code);
        String resp = HttpUtil.get(openidUrl);
        JSONObject result = JSONUtil.parseObj(resp);
        log.info("get openid result:{}", resp);
        String newOpenid = result.getStr("openid");
        if (StrUtil.isNotEmpty(newOpenid)) {
            return newOpenid;
        }
        return openid;
    }

    public SortedMap getWxConfig(String url) {
        Long nowTime = System.currentTimeMillis() / 1000;
        String jsTicket = RedisUtil.get(wxConfig.getJsApiTicketKey());
        SortedMap<String, Object> params = new TreeMap<>();
        params.put("noncestr", RandomUtil.randomString(32));
        params.put("jsapi_ticket", jsTicket);
        params.put("timestamp", nowTime);
        params.put("url", url);
        String sign = SecureUtil.sha1(params.toString().substring(1, params.toString().lastIndexOf("}")).replaceAll(", ", "&"));
        params.put("sign", sign);
        params.put("appId", wxConfig.getAppId());
        return params;
    }

    @Async
    public void sendTemplateMsg(String templateId, String openid, MsgDataBO data) {
        String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
        BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, data);
        String json = JSON.toJSONString(baseTemplate);
        String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
        String resp = HttpUtil.post(url, json);
        log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
    }

    @Async
    public void sendTemplateMsg(String templateId, String openid, MsgDataBO data, String detailUrl) {
        String accessToken = RedisUtil.get(wxConfig.getAccessTokenKey());
        BaseTemplate baseTemplate = new BaseTemplate(openid, templateId, detailUrl, data);
        String json = JSON.toJSONString(baseTemplate);
        String url = wxConfig.getSendMsgUrl().replace("ACCESS_TOKEN", accessToken);
        String resp = HttpUtil.post(url, json);
        log.info("send wx msg{},{},{};return :{}", templateId, openid, JSONUtil.toJsonStr(data), resp);
    }
}
