package thyyxxk.webserver.service.outpatient.wxapi;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import thyyxxk.webserver.config.exception.BizException;
import thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.constants.Capacity;
import thyyxxk.webserver.dao.his.outpatient.WxApiDao;
import thyyxxk.webserver.entity.ResultVo;
import thyyxxk.webserver.entity.outpatient.wxapi.QueryWxOrderParam;
import thyyxxk.webserver.entity.outpatient.wxapi.RefundParam;
import thyyxxk.webserver.entity.outpatient.wxapi.WxPayOrder;
import thyyxxk.webserver.utils.*;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * @author dj
 */
@Slf4j
@Service
public class WxApiService {
    private final WxApiDao dao;

    @Autowired
    public WxApiService(WxApiDao dao) {
        this.dao = dao;
    }

    public ResultVo<Map<String, Object>> selectOrdersByConditions(QueryWxOrderParam param) {
        log.info("查询微信订单数据：{}", JSON.toJSONString(param));
        Map<String, Object> map = new HashMap<>(Capacity.FOUR);
        if (StringUtil.notBlank(param.getTradeNo())) {
            List<WxPayOrder> list = dao.selectOrdersByTradeNo(param.getTradeNo());
            if (null == list || list.isEmpty()) {
                return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST);
            }
            map.put("list", list);
            map.put("totalSize", 1);
            return ResultVoUtil.success(map);
        }
        IPage<WxPayOrder> iPage = getWxPayOrderPage(param);
        if (iPage.getTotal() == 0) {
            return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST);
        }
        map.put("totalSize", iPage.getTotal());
        map.put("currentPage", param.getCurrentPage());
        map.put("pageSize", param.getPageSize());
        map.put("list", iPage.getRecords());
        return ResultVoUtil.success(map);
    }

    @Transactional(rollbackFor = Exception.class)
    public String refundOrder(RefundParam param) throws Exception {
        log.info("退款申请：{}", JSON.toJSONStringWithDateFormat(param, "yyyy-MM-dd HH:mm:ss"));
        String nonceStr = SnowFlakeId.instance().nextId();
        String outRefundNo = SnowFlakeId.instance().nextId();
        String refundAmt = DecimalUtil.moneyYuanToFen(param.getRefundAmt());
        String totalFee = DecimalUtil.moneyYuanToFen(param.getTotalFee());
        TreeMap<String, String> map = new TreeMap<>();
        map.put("appid", WxCertUtil.APP_ID);
        map.put("mch_id", WxCertUtil.MERCHANT_ID);
        map.put("nonce_str", nonceStr);
        map.put("out_refund_no", outRefundNo);
        map.put("out_trade_no", param.getTradeNo());
        map.put("refund_fee", refundAmt);
        map.put("total_fee", totalFee);
        String refundSign = Md5Util.createWxPaySign(map);
        String xml = "<xml>" +
                "<appid>" + WxCertUtil.APP_ID + "</appid>" +
                "<mch_id>" + WxCertUtil.MERCHANT_ID + "</mch_id>" +
                "<nonce_str>" + nonceStr + "</nonce_str>" +
                "<out_refund_no>" + outRefundNo + "</out_refund_no>" +
                "<out_trade_no>" + param.getTradeNo() + "</out_trade_no>" +
                "<refund_fee>" + refundAmt + "</refund_fee>" +
                "<total_fee>" + totalFee + "</total_fee>" +
                "<sign>" + refundSign + "</sign>" +
                "</xml>";
        String str = requestWithSsl(xml);
        Document document = DocumentHelper.parseText(str);
        Element root = document.getRootElement();
        if ("SUCCESS".equals(root.element("return_code").getStringValue())) {
            Element refundIdEle = root.element("refund_id");
            if (null == refundIdEle) {
                String msg = root.element("err_code_des").getStringValue();
                log.info("微信退款失败：{}", msg);
                if ("订单已全额退款".equals(msg)) {
                    dao.alreadyRefund(param.getTradeNo(), msg);
                }
                throw new BizException(msg);
            }
            param.setRefundId(refundIdEle.getStringValue());
            dao.updateRefundId(param);
            log.info("微信退款成功：{}", param);
            return param.getRefundId();
        }
        final String message = root.element("return_msg").getStringValue();
        log.info("微信退款失败：{}", message);
        throw new BizException(message);
    }

    private String requestWithSsl(String xml) throws Exception {
        BasicHttpClientConnectionManager connManager;
        WxCertUtil wxCertUtil = new WxCertUtil();
        char[] password = WxCertUtil.MERCHANT_ID.toCharArray();
        InputStream certStream = wxCertUtil.getCertStream();
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(certStream, password);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, password);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                sslContext, new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
        connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", sslConnectionSocketFactory).build(),
                null, null, null);
        CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
        String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        HttpPost httpPost = new HttpPost(url);
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(6000).build();
        httpPost.setConfig(requestConfig);
        StringEntity postEntity = new StringEntity(xml, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");
    }

    public void exportExcel(HttpServletResponse response, QueryWxOrderParam param) {
        log.info("导出excel: {}", JSON.toJSONString(param));
        IPage<WxPayOrder> iPage = getWxPayOrderPage(param);
        List<WxPayOrder> list = iPage.getRecords();
        String[] title = {"商品描述","订单类型","总金额","报销金额","个账金额","优惠金额","实付金额",
                "姓名","门诊号","住院号","住院次数","订单号", "流水号","订单创建时间","订单支付时间",
                "订单支付状态","退款原因","退款操作员","退款时间"};
        String[][] content = new String[list.size()][];
        String tradeStateName = null;
        boolean multiple = true;
        if (!param.getTradeState().contains(",")) {
            multiple = false;
            tradeStateName = FilterUtil.filterTradeState(param.getTradeState());
        }
        for (int i = 0; i < list.size(); i++) {
            content[i] = new String[title.length];
            WxPayOrder order = list.get(i);
            content[i][0] = order.getBody();
            content[i][1] = FilterUtil.filterOrderType(order.getOrderType());
            content[i][2] = String.valueOf(order.getTotalFee());
            content[i][3] = String.valueOf(order.getFundpayAmt());
            content[i][4] = String.valueOf(order.getAcctpayAmt());
            content[i][5] = String.valueOf(order.getCouponAmt());
            content[i][6] = String.valueOf(order.getCashpayAmt());
            content[i][7] = order.getPatientName();
            content[i][8] = order.getPatientId();
            content[i][9] = null == order.getInpatientNo() ? "" : order.getInpatientNo();
            content[i][10] = null == order.getAdmissTimes() ? "" : String.valueOf(order.getAdmissTimes());
            content[i][11] = order.getTradeNo();
            content[i][12] = order.getSerialNo();
            content[i][13] = DateUtil.formatDatetime(order.getCreateDatetime());
            content[i][14] = null == order.getPayDatetime() ? "" : DateUtil.formatDatetime(order.getPayDatetime());
            content[i][15] = multiple ? FilterUtil.filterTradeState(order.getTradeState()) : tradeStateName;
            content[i][16] = null == order.getRefundReason() ? "" : order.getRefundReason();
            content[i][17] = null == order.getRefundOpName() ? "" : order.getRefundOpName();
            content[i][18] = null == order.getRefundOpDatetime() ? "" : DateUtil.formatDatetime(order.getRefundOpDatetime());
        }
        log.info("{}\n{}", title, content);
        ExcelUtil.exportExcel(response, title, content);
    }

    private IPage<WxPayOrder> getWxPayOrderPage(QueryWxOrderParam param) {
        IPage<WxPayOrder> iPage = new Page<>(param.getCurrentPage(), param.getPageSize());
        String patientId = "%" + param.getPatientId() + "%";
        String patientName = "%" + param.getPatientName() + "%";
        String orderType = param.getOrderType() == -1 ? "%%" : "%" + param.getOrderType() + "%";
        String start = param.getStart() + " 00:00:00";
        String end = param.getEnd() + " 23:59:59";
        if ("ALL".equals(param.getTradeState())) {
            return dao.selectOrdersWithoutStatus(iPage, patientId, patientName, orderType, start, end);
        }
        String[] statuses = param.getTradeState().split(",");
        return dao.selectOrdersByConditions(iPage, patientId, patientName, orderType, statuses, start, end);
    }

}
