package com.yeejoin.precontrol.common.service.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.util.*;
import java.util.stream.Collectors;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletResponse;

import com.yeejoin.precontrol.common.entity.WechatSendMessage;
import com.yeejoin.precontrol.common.service.IWechatSendMessageService;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.yeejoin.amos.feign.systemctl.Systemctl;
import com.yeejoin.amos.feign.systemctl.model.DictionarieValueModel;
import com.yeejoin.precontrol.common.exception.BaseException;
import com.yeejoin.precontrol.common.exception.errorEnum.BaseExceptionEnum;
import com.yeejoin.precontrol.common.service.SmallProService;
import com.yeejoin.precontrol.common.utils.HttpUtils;
import com.yeejoin.precontrol.common.utils.RedisUtil;
import com.yeejoin.precontrol.common.utils.StringUtil;
import com.yeejoin.precontrol.common.vo.ResponeVo;
import com.yeejoin.precontrol.common.vo.SmallProQrCodeVo;
import com.yeejoin.precontrol.common.vo.SmallProTokenVo;

import lombok.extern.slf4j.Slf4j;

/**
 * @description: 获取公共的小程序token
 * @author: duanwei
 * @date: 2020-07-02 12:12
 **/
@Service
@Slf4j
public class SmallProServiceImpl implements SmallProService {
    final String SMALL_PRO_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?";
    final String SMALL_PRO_TOKEN = "wxToken";
    final String SMALL_PRO_QRCODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?";
    @Autowired
    RedisUtil redisUtil;
    @Value("${smallProgram.appid}")
    private String appId;
    @Value("${smallProgram.grant_type}")
    private String grant_type;
    @Value("${smallProgram.appid}")
    private String appid;
    @Value("${smallProgram.secret}")
    private String secret;
    @Value("${smallProgram.token.expiresTime}")
    private Long expiresTime;
    @Value("${smallProgram.name}")
    private String smallPrograName;
    /**
     * 公众号appid
     */
    @Value("${smallProgram.public.appid}")
    private String publicAppId;
    /**
     * 公众号secret
     */
    @Value("${smallProgram.public.secret}")
    private String publicSecret;
    @Autowired
    private IWechatSendMessageService wechatSendMessageService;

    @Override
    public String getSmallProToken() {
        String smallToken = SMALL_PRO_TOKEN;
        // 坑，redis取出来的值 微信说失效了 现在解决方案 是改小失效时间
        if (redisUtil.hasKey(smallToken)) {
            return redisUtil.get(smallToken).toString();
        }
        SmallProTokenVo smallProTokenVo = buildTokenParam(SMALL_PRO_TOKEN_URL, grant_type, appid, secret);
        if (smallProTokenVo.getErrcode() == null) {
            redisUtil.set(smallToken, smallProTokenVo.getAccess_token(), expiresTime);
        }
        return smallProTokenVo.getAccess_token();
    }

    @Override
    public String getSmallProQrCode(String token, String scene, String page, Long width, Boolean auto_color,
                                    JSONObject line_color, Boolean is_hyaline) {
        SmallProQrCodeVo smallProQrCodeVo = buildQrParam(SMALL_PRO_QRCODE_URL, token, scene, page, width, auto_color,
                line_color, is_hyaline);
        // 小程序二维码
        byte[] buffer = smallProQrCodeVo.getBuffer();
        return JSON.toJSONString(buffer);
    }

    @Override
    public byte[] getSmallProQrCodeByte(String token, String scene, String page, Long width, Boolean auto_color,
                                        JSONObject line_color, Boolean is_hyaline) {
        SmallProQrCodeVo smallProQrCodeVo = buildQrParam(SMALL_PRO_QRCODE_URL, token, scene, page, width, auto_color,
                line_color, is_hyaline);
        // 小程序二维码 byte
        return smallProQrCodeVo.getBuffer();
    }

    /**
     * 构建小程序token请求
     *
     * @param url
     * @param grant_type
     * @param appid
     * @param secret
     * @return
     */
    SmallProTokenVo buildTokenParam(String url, String grant_type, String appid, String secret) {
        SmallProTokenVo smallProTokenVo;
        StringBuffer buildUrl = new StringBuffer();
        buildUrl.append(url).append("grant_type=" + grant_type).append("&appid=" + appid).append("&secret=" + secret);
        ResponeVo responeVo;
        try {
            responeVo = HttpUtils.get(buildUrl.toString());
            String content = responeVo.getContent();
            smallProTokenVo = JSON.parseObject(content, SmallProTokenVo.class);
        } catch (IOException e) {
            log.error("getSmallPro token is error url:{}", buildUrl.toString());
            throw new BaseException(BaseExceptionEnum.REQUEST_ERROR);
        }
        return smallProTokenVo;
    }

    /**
     * @param url
     * @param access_token 接口调用凭证
     * @param scene        最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用
     *                     urlencode 处理，请使用其他编码方式）
     * @param page         必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index, 根路径前不要填加
     *                     /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param width        二维码的宽度，单位 px，最小 280px，最大 1280px
     * @param auto_color   自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调，默认 false
     * @param line_color   auto_color 为 false 时生效，使用 rgb 设置颜色 例如
     *                     {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示
     * @param is_hyaline   是否需要透明底色，为 true 时，生成透明底色的小程序
     * @return
     */
    SmallProQrCodeVo buildQrParam(String url, String access_token, String scene, String page, Long width,
                                  Boolean auto_color, JSONObject line_color, Boolean is_hyaline) {
        SmallProQrCodeVo smallProQrCodeVo = new SmallProQrCodeVo();
        StringBuffer buildUrl = new StringBuffer();
        buildUrl.append(url).append("access_token=" + access_token);
        JSONObject param = new JSONObject();
        param.put("scene", scene);
        param.put("page", page);
        param.put("width", width);
        param.put("auto_color", auto_color);
        param.put("line_color", line_color);
        param.put("is_hyaline", is_hyaline);
        try {
            RestTemplate rest = new RestTemplate();
            log.info("request wxQrCode start.....");
            ResponseEntity<Resource> entity = rest.postForEntity(buildUrl.toString(), param.toJSONString(),
                    Resource.class);
            log.info("get wxQrCode entity:{}", entity.toString());
            InputStream in = entity.getBody().getInputStream();
            smallProQrCodeVo.setBuffer(inputStream2byte(in));
        } catch (Exception e) {
            log.error("getSmallProQrCode is error url:{} param:{}", buildUrl.toString(), JSON.toJSON(param));
            throw new BaseException(BaseExceptionEnum.REQUEST_ERROR);
        }
        return smallProQrCodeVo;
    }

    /**
     * 功能描述:
     *
     * @param inputStream 输入流
     * @return byte[] 数组
     * @author sqy
     * @date 2019/3/28 16:03
     * @version 1.0
     */
    public static byte[] inputStream2byte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while ((rc = inputStream.read(buff, 0, 100)) > 0) {
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public void getSmallProQrCodeResponse(String access_token, String scene, String page, Long width,
                                          Boolean auto_color, JSONObject line_color, Boolean is_hyaline, HttpServletResponse response,
                                          String fileName, String fileType) {

        SmallProQrCodeVo smallProQrCodeVo = buildQrParam(SMALL_PRO_QRCODE_URL, access_token, scene, page, width,
                auto_color, line_color, is_hyaline);
        byte[] smallProQrCode = smallProQrCodeVo.getBuffer();
        response.setContentType("image/" + fileType);
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "." + fileType);
        response.addHeader("Access-Control-Allow-Headers", "Content-Disposition");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");

        OutputStream os;
        try {
            os = response.getOutputStream();
            os.write(smallProQrCode);
            os.flush();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public JSONObject getCode2Session(String code) {
        String url = buildOpenIdUrl(appId, secret, code);
        ResponeVo responeVo = null;
        try {
            responeVo = HttpUtils.get(url);
            log.error("手机号解析结果" + JSONObject.toJSONString(responeVo));
            System.out.println("手机号解析结果" + JSONObject.toJSONString(responeVo));
        } catch (IOException e) {
            throw new BaseException("微信接口调用失败");
        }
        JSONObject jsonObject = JSONObject.parseObject(responeVo.getContent());
        if (jsonObject != null) {
            int errcode = jsonObject.getIntValue("errcode");
            if (errcode == 0) {
                // session_key 会话秘钥
                // String openId = jsonObject.getString("openid");
                return jsonObject;
            } else {
                throw new BaseException(jsonObject.getString("errmsg"));
            }
        } else {
            throw new BaseException("微信接口调用失败");
        }
    }

    private String buildOpenIdUrl(String appId, String secret, String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + secret + "&js_code="
                + code + "&grant_type=authorization_code";
        return url;
    }

    @Override
    public String getOpenId(String code) {
        JSONObject jsonObject = getCode2Session(code);
        String openId = null;
        if (jsonObject != null) {
            openId = jsonObject.getString("openid");
        }
        return openId;
    }

    @Override
    public String getSessionKey(String code) {
        JSONObject jsonObject = getCode2Session(code);
        String sessionKey = null;
        if (jsonObject != null) {
            sessionKey = jsonObject.getString("session_key");
        }
        return sessionKey;
    }

    @Override
    public String getPhoneNumber(String sessionkey, String encryptedData, String iv) {
//		 // 被加密的数据
//        byte[] dataByte = Base64.decodeBase64(encryptedData);
//        // 加密秘钥
//        byte[] keyByte = Base64.decodeBase64(session_key);
//        // 偏移量
//        byte[] ivByte = Base64.decodeBase64(iv);
//        try {
//            // 如果密钥不足16位，那么就补足.  这个if 中的内容很重要
//            int base = 16;
//            if (keyByte.length % base != 0) {
//                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
//                byte[] temp = new byte[groups * base];
//                Arrays.fill(temp, (byte) 0);
//                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
//                keyByte = temp;
//            }
//            // 初始化
//            Security.addProvider(new BouncyCastleProvider());
//            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
//            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
//            parameters.init(new IvParameterSpec(ivByte));
//            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
//            byte[] resultByte = cipher.doFinal(dataByte);
//			if (null != resultByte && resultByte.length > 0) {
//				String result = new String(resultByte, "UTF-8");
//				JSONObject Obj = JSONObject.parseObject(result);
//				if (Obj != null && Obj.containsKey("phoneNumber")) {
//					return Obj.getString("phoneNumber");
//				}
//			}
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return null;
        byte[] encrypData = java.util.Base64.getDecoder().decode(encryptedData);
        byte[] ivData = java.util.Base64.getDecoder().decode(iv);
        byte[] sessionKey = java.util.Base64.getDecoder().decode(sessionkey);
        String resultString = null;
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
        SecretKeySpec keySpec = new SecretKeySpec(sessionKey, "AES");
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            resultString = new String(cipher.doFinal(encrypData), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
//			Cipher cipher;
//			try {
//				cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
//				resultString = new String(cipher.doFinal(encrypData), "UTF-8");
//				cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
//			} catch (Exception e1) {
//				e1.printStackTrace();
//			}
        }
        JSONObject object = JSONObject.parseObject(resultString);
        if (object != null) {
            // 拿到手机号码
            String phone = object.getString("phoneNumber");
            return phone;
        }
        return null;
    }

    /*
     * 获取公众号 accesstoken
     * (non-Javadoc)
     * @see com.yspro.service.PubUserService#getAccessToken()
     */
    public String getAccessToken() throws Exception {
        String accessToken = (String) redisUtil.get("accessToken");
        if (com.github.pagehelper.util.StringUtil.isNotEmpty(accessToken)) {
            return accessToken;
        } else {
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + publicAppId + "&secret=" + publicSecret;
            ResponeVo responeVo = HttpUtils.post(url, "");
            JSONObject jsonResult = JSONObject.parseObject(responeVo.getContent());
            // 成功后 返回参数中没有 errcode
            if (jsonResult != null) {
                accessToken = jsonResult.getString("access_token");
                if (com.github.pagehelper.util.StringUtil.isNotEmpty(accessToken)) {
                    // 有效时长 7200s == 2h
                    String expires_in = jsonResult.getString("expires_in");
                    redisUtil.set("accessToken", accessToken, Long.valueOf(expires_in));
                    log.info("accessToken的值为：{} ", accessToken);
                } else {
                    log.info("获取公众号 accessToken 失败， {}", jsonResult.toJSONString());
                }
            } else {
                log.info("获取公众号 accessToken 失败， {}", jsonResult.toJSONString());
            }
        }
        return accessToken;
    }

    @Override
    public void sendWeChatUpcomingMessage(List<String> openIds, String template, List<String> message) {
        try {
            if (openIds != null && openIds.size() > 0 && StringUtil.isNotEmpty(template) && message != null
                    && message.size() > 0) {
                // 处理模板数据
                List<DictionarieValueModel> templateIds = Systemctl.dictionarieClient
                        .dictValues("WECHAT_MESSAGE_TEMPLATE_ID").getResult();
                String tempId = "";
                if (templateIds != null && templateIds.size() > 0) {
                    tempId = templateIds.stream().filter(item -> template.equals(item.getDictDataKey()))
                            .collect(Collectors.toList()).get(0).getDictDataValue();
                }
                if (StringUtils.isEmpty(tempId)) {
                    log.info("缺失模板id");
                    return;
                }
                Map<String, Object> dataMap = getMessage(template, message);
                if (dataMap == null) {
                    log.info("缺失模板字段");
                    return;
                }
                List<WechatSendMessage> wechatSendMessagesList = new ArrayList<>();
                for (String openId : openIds) {
                    String accessToken = getAccessToken();
                    String tmpurl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
                    JSONObject json = new JSONObject();
                    //所要发送的用户 公众号的 openId
                    json.put("touser", openId);
                    json.put("template_id", tempId);
                    json.put("page", "view/home/Home");//点击模板可以跳转到小程序的具体界面
                    //json.put("form_id", formId);//用户的fromId或者预订单Id
                    // 配置小程序信息
                    JSONObject jo = new JSONObject();
                    jo.put("appid", appId); // 小程序APPID
                    jo.put("pagepath", "view/home/Home");
                    json.put("miniprogram", jo);
                    json.put("topcolor", "#173177");
                    json.put("data", dataMap);//这个data可以直接调用上文的JsonMsg方法生成所需要发送给用户的信息
                    ResponeVo post = HttpUtils.post(tmpurl, json.toJSONString());
                    log.info("微信数据:" + post.getContent());
                    JSONObject resultJson = JSONObject.parseObject(post.getContent());
                    log.info("模板消息返回数据: {}", resultJson);
                    String errmsg = resultJson.getString("errmsg");
                    String status = "";
                    if ("ok".equals(errmsg)) {
                        //如果为errmsg为ok，则代表发送成功，公众号推送信息给用户了。
                        status = "发送成功";
                    } else {
                        status = "发送失败";
                    }
                    WechatSendMessage wechatSendMessage = new WechatSendMessage();
                    wechatSendMessage.setOpenId(openId);
                    wechatSendMessage.setMessage(json.toJSONString());
                    wechatSendMessage.setStatus(status);
                    wechatSendMessage.setTemplateId(tempId);
//                    wechatSendMessage.setFailMessage(failMessage);
                    wechatSendMessage.setResult(post.getContent());
                    wechatSendMessage.setCreateDate(new Date());
                    wechatSendMessagesList.add(wechatSendMessage);
                }
                if (wechatSendMessagesList.size() > 0) {
                    wechatSendMessageService.saveBatch(wechatSendMessagesList);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

    }

    /**
     * 消息模板和内容组装
     *
     * @param key
     * @param message
     * @return
     */
    private Map<String, Object> getMessage(String key, List<String> message) throws Exception {
        Map<String, Object> dataMap = new HashMap<String, Object>();
        ClassPathResource resource = new ClassPathResource("json/wechatMessageTemplateField.json");
        InputStream inputStream = resource.getInputStream();
        String result = IOUtils.toString(inputStream, String.valueOf(StandardCharsets.UTF_8));
        JSONObject fields = JSONObject.parseObject(result, JSONObject.class);
        String vals = "";
        if (fields != null) {
            vals = (String) fields.get(key);
        }
        if (StringUtils.isEmpty(vals)) {
            return null;
        }
        String[] strKeys = vals.split(",");
        if (message != null && message.size() > 0 && strKeys.length > 0) {
            for (int i = 0; i < message.size(); i++) {
                Map<String, String> map = new HashMap<String, String>();
                map.put("value", message.get(i));
//                map.put("color", "#173177");
                dataMap.put(strKeys[i], map);
            }
        }
        return dataMap;
    }

    @Override
    public String getName() {
        return smallPrograName;
    }
}
