package com.qunzhixinxi.hnqz.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.qunzhixinxi.hnqz.admin.api.constant.CacheConstants;
import com.qunzhixinxi.hnqz.admin.api.entity.SysSocialDetails;
import com.qunzhixinxi.hnqz.admin.entity.*;
import com.qunzhixinxi.hnqz.admin.enums.SysWxReplyMessageEnum;
import com.qunzhixinxi.hnqz.admin.service.SysSocialDetailsService;
import com.qunzhixinxi.hnqz.admin.service.SysWxAccountApiService;
import com.qunzhixinxi.hnqz.admin.util.HttpRequest;
import com.qunzhixinxi.hnqz.admin.util.JaxbUtil;
import com.qunzhixinxi.hnqz.admin.util.WXBizMsgCrypt;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信公众号api相关
 *
 * @author lixuesong
 * @date 2021年04月29日 10:24
 */
@Slf4j
@Service
@AllArgsConstructor
public class SysWxAccountApiServiceImpl implements SysWxAccountApiService {

	/**
	 * 获取微信access_token接口url
	 */
	private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

	/**
	 * token过期时间（保证小于微信的默认过期时间7200s）
	 */
	private static final Integer TOKEN_EXPIRES_IN = 7200 - 6 * 60;

	/**
	 * 获取微信access_token接口url参数
	 */
	private static final String TOKEN_URL_PARAM = "grant_type=client_credential&appid=%s&secret=%s";

	/**
	 * 根据media_id获取永久素材接口url
	 */
	private static final String GET_MATERIAL_URL = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s";

	/**
	 * 微信上传图片、视频等素材接口url
	 */
	private static String MEDIA_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s";

	/**
	 * 发送客服消息接口url
	 */
	private static String CUSTOM_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s";

	/**
	 * 系统社交登录账号表type-微信公众号相关配置
	 */
	private final static String SOCIAL_DETAIL_TYPE_WX_SERVER = "WX_SUBSCRIPTION";

	/**
	 * 系统社交登录账号表type-微信账号相关配置
	 */
	private final static String SOCIAL_DETAIL_TYPE_WX_SUBSCRIPTION = "WX_ACCOUNT";

	/**
	 * 微信配置的令牌token
	 */
	private static String TOKEN = null;

	/**
	 * 微信配置的消息加解密密钥
	 */
	private static String ENCODING_AES_KEY = null;

	/**
	 * 微信appid
	 */
	private static String APP_ID = null;

	/**
	 * 微信appsecret
	 */
	private static String APP_SECRET = null;

	private final RedisTemplate redisTemplate;

	private final SysSocialDetailsService sysSocialDetailsService;

	@PostConstruct
	private void init() {
		// 微信公众号-服务器相关配置
		List<SysSocialDetails> subscriptionList = sysSocialDetailsService
				.list(Wrappers.<SysSocialDetails>lambdaQuery().eq(SysSocialDetails::getType, SOCIAL_DETAIL_TYPE_WX_SERVER));
		if (subscriptionList != null && !subscriptionList.isEmpty()) {
			TOKEN = subscriptionList.get(0).getAppId();
			ENCODING_AES_KEY = subscriptionList.get(0).getAppSecret();
		}
		// 微信账号相关配置
		List<SysSocialDetails> accountList = sysSocialDetailsService
				.list(Wrappers.<SysSocialDetails>lambdaQuery().eq(SysSocialDetails::getType, SOCIAL_DETAIL_TYPE_WX_SUBSCRIPTION));
		if (accountList != null && !accountList.isEmpty()) {
			APP_ID = accountList.get(0).getAppId();
			APP_SECRET = accountList.get(0).getAppSecret();
		}
	}

	/**
	 * 获取微信access_token
	 *
	 * @param appid 第三方用户唯一凭证
	 * @param secret 第三方用户唯一凭证密钥，即appsecret
	 * @return
	 */
	@Override
	public String getAccessToken(String appid, String secret) {
		// 先尝试从redis中获取token
		Object tokenObj = redisTemplate.opsForValue().get(CacheConstants.SYS_WX_ACCOUNT_TOKEN_KEY);
		if (tokenObj != null) {
			return tokenObj.toString();
		}
		// redis没有则请求接口获取
		String urlParam = String.format(TOKEN_URL_PARAM, appid, secret);
		log.info("微信获取token接口请求：{}", urlParam);
		String getResult = HttpRequest.sendGet(TOKEN_URL, urlParam);
		log.info("微信获取token接口响应：{}", getResult);
		if (StringUtils.isBlank(getResult)) {
			return null;
		}
		JSONObject jsonResult = JSONObject.parseObject(getResult);
		String accessToken = jsonResult.getString("access_token");
		if (StringUtils.isBlank(accessToken)) {
			return null;
		}
		// 存入redis
		redisTemplate.opsForValue().set(CacheConstants.SYS_WX_ACCOUNT_TOKEN_KEY, accessToken, TOKEN_EXPIRES_IN, TimeUnit.SECONDS);
		return accessToken;
	}

	/**
	 * 获取微信素材
	 *
	 * @param mediaId 要获取的素材的media_id
	 * @return
	 */
	@Override
	public Map<String, Object> getMaterial(String mediaId) {
		String accessToken = getAccessToken(APP_ID, APP_SECRET);
		// 拼接url
		String url = String.format(GET_MATERIAL_URL, accessToken);
		// 拼接json参数
		Map<String, String> param = new HashMap<>();
		param.put("media_id", mediaId);
		log.info("获取微信素材接口请求：accessToken={}, jsonBody={}", accessToken, JSON.toJSONString(param));
		String postResult = HttpRequest.sendPostWithJson(url, JSON.toJSONString(param));
		log.info("获取微信素材接口响应：{}", postResult);
		if (StringUtils.isBlank(postResult)) {
			return null;
		}
		JSONObject jsonResult = JSONObject.parseObject(postResult);
		Map<String, Object> result = new HashMap<>();
		result.put("title", jsonResult.getString("title"));
		result.put("description", jsonResult.getString("description"));
		result.put("url", jsonResult.getString("down_url"));
		return result;
	}

	/**
	 * 验证微信服务器
	 *
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	@Override
	public boolean verifyWxServer(String signature, String timestamp, String nonce) {
		// 1）将token、timestamp、nonce三个参数进行字典序排序
		String[] arr = {TOKEN, timestamp, nonce};
		Arrays.sort(arr);
		// 2）将三个参数字符串拼接成一个字符串进行sha1加密
		String str = String.join("", arr);
		String sha1Str = DigestUtils.sha1Hex(str);
		// 3）开发者获得加密后的字符串可与signature对比，标识该请求来源于微信
		if (sha1Str.equalsIgnoreCase(signature)) {
			return true;
		}
		return false;
	}

	/**
	 * 解密微信返回的报文
	 *
	 * @param msgSignature
	 * @param timestamp
	 * @param nonce
	 * @param postData
	 * @return
	 */
	@Override
	public SysWxReplyMessagePostBody decryptWxMessage(String msgSignature, String timestamp, String nonce, String postData) throws Exception {
		WXBizMsgCrypt bizMsgCrypt = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);
		String decryptMsg = bizMsgCrypt.decryptMsg(msgSignature, timestamp, nonce, postData);
		// xml转为实体对象
		SysWxReplyMessagePostBody postBody = JaxbUtil.converyToJavaBean(decryptMsg, SysWxReplyMessagePostBody.class);
		log.info("微信消息体明文：{}", JSON.toJSONString(postBody));

		return postBody;
	}

	/**
	 * 拼接选择列表信息
	 *
	 * @param messageList
	 * @return
	 */
	@Override
	public String joinWxSelectListMessage(List<SysWxReplyMessage> messageList) {
		StringBuilder replyMessage = new StringBuilder();
		replyMessage.append("点击查看相关问题：\r\n");
		if (messageList.size() > 0) {
			for (int i = 0; i < messageList.size(); i++) {
				String msgContent = messageList.get(i).getId() + "." + messageList.get(i).getKeyword();
				replyMessage.append("  <a href=\"weixin://bizmsgmenu?msgmenucontent=" + msgContent + "&msgmenuid=1\">");
				replyMessage.append(msgContent);
				replyMessage.append("</a>\r\n");
			}
		}
		return replyMessage.toString().trim();
	}

	/**
	 * 加密选择列表信息
	 *
	 * @param messagePostBody
	 * @param selectContent
	 * @return
	 * @throws Exception
	 */
	@Override
	public String encryptWxSelectListMessage(SysWxReplyMessagePostBody messagePostBody, String selectContent) throws Exception {
		SysWxReplyMessagePostBody replyPostBody = new SysWxReplyMessagePostBody();
		replyPostBody.setMsgType(SysWxReplyMessageEnum.WX_MEDIA_TYPE_TEXT.getDescription());
		replyPostBody.setContent(selectContent);
		replyPostBody.setFromUserName(messagePostBody.getToUserName());
		replyPostBody.setToUserName(messagePostBody.getFromUserName());
		replyPostBody.setCreateTime(generateTimeStamp());
		// 转为xml
		String replyXml = JaxbUtil.convertToXml(replyPostBody);
		log.info("返回给微信的明文：{}", replyXml);
		// 加密
		WXBizMsgCrypt bizMsgCrypt = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);

		String encryptMsg = bizMsgCrypt.encryptMsg(replyXml, generateTimeStamp(), generateRandomNum());
		log.info("返回给微信的密文：{}", encryptMsg);
		return encryptMsg;
	}

	/**
	 * 加密文本信息
	 *
	 * @param messagePostBody 微信请求body
	 * @param message
	 * @return
	 * @throws Exception
	 */
	@Override
	public String encryptWxTextMessage(SysWxReplyMessagePostBody messagePostBody, SysWxReplyMessage message) throws Exception {
		SysWxReplyMessagePostBody replyPostBody = new SysWxReplyMessagePostBody();
		// 文本内容拼接
		StringBuilder replyMessage = new StringBuilder();
		if (CollectionUtil.isEmpty(message.getMediaList())) {
			return "";
		}
		for (int i = 0; i < message.getMediaList().size(); i++) {
			replyMessage.append(message.getMediaList().get(i).getContent());
		}
		if (StringUtils.isBlank(replyMessage)) {
			return "";
		}
		replyPostBody.setContent(replyMessage.toString());
		replyPostBody.setMsgType(SysWxReplyMessageEnum.WX_MEDIA_TYPE_TEXT.getDescription());
		replyPostBody.setFromUserName(messagePostBody.getToUserName());
		replyPostBody.setToUserName(messagePostBody.getFromUserName());
		replyPostBody.setCreateTime(generateTimeStamp());
		// 转为xml
		String replyXml = JaxbUtil.convertToXml(replyPostBody);
		log.info("返回给微信的明文：{}", replyXml);
		// 加密
		WXBizMsgCrypt bizMsgCrypt = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);

		String encryptMsg = bizMsgCrypt.encryptMsg(replyXml, generateTimeStamp(), generateRandomNum());
		log.info("返回给微信的密文：{}", encryptMsg);
		return encryptMsg;
	}

	/**
	 * 加密图片信息
	 *
	 * @param messagePostBody 微信请求body
	 * @param message
	 * @return
	 * @throws Exception
	 */
	@Override
	public String encryptWxImageMessage(SysWxReplyMessagePostBody messagePostBody, SysWxReplyMessage message) throws Exception {
		SysWxReplyMessagePostBody replyPostBody = new SysWxReplyMessagePostBody();
		if (message.getMediaList().size() > 0) {
			SysWxReplyMessageImagePostBody imagePostBody = new SysWxReplyMessageImagePostBody();
			imagePostBody.setMediaId(message.getMediaList().get(0).getWxMediaId());
			replyPostBody.setImage(imagePostBody);
		}
		replyPostBody.setMsgType(SysWxReplyMessageEnum.WX_MEDIA_TYPE_IMAGE.getDescription());
		replyPostBody.setFromUserName(messagePostBody.getToUserName());
		replyPostBody.setToUserName(messagePostBody.getFromUserName());
		replyPostBody.setCreateTime(generateTimeStamp());
		// 转为xml
		String replyXml = JaxbUtil.convertToXml(replyPostBody);
		log.info("返回给微信的明文：{}", replyXml);
		// 加密
		WXBizMsgCrypt bizMsgCrypt = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);

		String encryptMsg = bizMsgCrypt.encryptMsg(replyXml, generateTimeStamp(), generateRandomNum());
		log.info("返回给微信的密文：{}", encryptMsg);
		return encryptMsg;
	}

	/**
	 * 加密视频信息
	 *
	 * @param messagePostBody 微信请求body
	 * @param message
	 * @return
	 * @throws Exception
	 */
	@Override
	public String encryptWxVideoMessage(SysWxReplyMessagePostBody messagePostBody, SysWxReplyMessage message) throws Exception {
		SysWxReplyMessagePostBody replyPostBody = new SysWxReplyMessagePostBody();
		if (message.getMediaList().size() > 0) {
			SysWxReplyMessageVideoPostBody videoPostBody = new SysWxReplyMessageVideoPostBody();
			videoPostBody.setMediaId(message.getMediaList().get(0).getWxMediaId());
			videoPostBody.setTitle(message.getMediaList().get(0).getTitle());
			videoPostBody.setDescription(message.getMediaList().get(0).getIntroduction());
			replyPostBody.setVideo(videoPostBody);
		}
		replyPostBody.setMsgType(SysWxReplyMessageEnum.WX_MEDIA_TYPE_VIDEO.getDescription());
		replyPostBody.setFromUserName(messagePostBody.getToUserName());
		replyPostBody.setToUserName(messagePostBody.getFromUserName());
		replyPostBody.setCreateTime(generateTimeStamp());
		// 转为xml
		String replyXml = JaxbUtil.convertToXml(replyPostBody);
		log.info("返回给微信的明文：{}", replyXml);
		// 加密
		WXBizMsgCrypt bizMsgCrypt = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);

		String encryptMsg = bizMsgCrypt.encryptMsg(replyXml, generateTimeStamp(), generateRandomNum());
		log.info("返回给微信的密文：{}", encryptMsg);
		return encryptMsg;
	}

	/**
	 * 发送图文信息（异步调用发送客服消息，防止return响应时间过长导致微信重复请求）
	 *
	 * @param toUser
	 * @param message
	 * @return
	 * @throws Exception
	 */
	@Override
	public boolean sendWxNewsMessage(String toUser, SysWxReplyMessage message) {
		if (CollectionUtil.isNotEmpty(message.getMediaList())) {
			message.getMediaList().stream().forEach(messageMedia -> {
				if (SysWxReplyMessageEnum.WX_MEDIA_TYPE_TEXT.getType().equals(messageMedia.getMediaType())) {
					// 发文本
					sendCustomMessage(toUser, SysWxReplyMessageEnum.WX_MEDIA_TYPE_TEXT.getDescription(), messageMedia.getContent());
				} else if (SysWxReplyMessageEnum.WX_MEDIA_TYPE_IMAGE.getType().equals(messageMedia.getMediaType())) {
					// 发图片
					sendCustomMessage(toUser, SysWxReplyMessageEnum.WX_MEDIA_TYPE_IMAGE.getDescription(), messageMedia.getWxMediaId());
				}
				// 等待一下，解决因网络延迟导致请求接口 返回到微信窗口显示顺序不一致问题
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
		return true;
	}

	/**
	 * 生成10位随机数
	 *
	 * @return
	 */
	private static String generateRandomNum() {
		StringBuilder val = new StringBuilder();
		Random random = new Random();
		for (int i = 0; i < 10; i++) {
			val.append(random.nextInt(10));
		}
		return val.toString();
	}

	/**
	 * 生成10位时间戳
	 *
	 * @return
	 */
	private static String generateTimeStamp() {
		return String.valueOf(System.currentTimeMillis()).substring(0, 10);
	}

	/**
	 * 分割文本内容为多个
	 *
	 * @param selectContent 待分割的文本
	 * @param limitTextLength 每段文本限制长度
	 * @throws IOException
	 * @return
	 */
	@Override
	public SysWxReplyMessage splitContentByLine(String selectContent, Integer limitTextLength) throws IOException {
		SysWxReplyMessage message = new SysWxReplyMessage();
		// 以行为最小粒度进行分割
		BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(selectContent.getBytes(Charset.forName("utf8"))), Charset.forName("utf8")));
		List<String> contentList = new ArrayList<>();
		StringBuilder sb = new StringBuilder();
		String line = "";
		while ((line = br.readLine()) != null) {
			// 设置测试文本长度（拼接了当前这行数据）
			String testLenthStr = sb.toString() + line + "\r\n";
			if (testLenthStr.length() > limitTextLength) {
				// 达到临界值，存入这部分到list
				contentList.add(sb.toString());
				sb.delete(0, sb.length());
			}
			sb.append(line + "\r\n");
		}
		if (sb.length() > 0) {
			contentList.add(sb.toString());
		}
		// 将分割好的内容重新存入mediaList
		message.setMediaList(new ArrayList<>());
		contentList.stream().forEach(s -> {
			SysWxReplyMessageMedia media = new SysWxReplyMessageMedia();
			media.setMediaType(SysWxReplyMessageEnum.MESSAGE_MEDIA_TYPE_TEXT.getType());
			media.setContent(s);
			message.getMediaList().add(media);
		});
		return message;
	}

	/**
	 * 上传微信素材
	 *
	 * @param file
	 * @param mediaType
	 * @param title
	 * @param introduction
	 * @return
	 */
	@Override
	public Map<String, String> uploadMedia(MultipartFile file, Integer mediaType, String title, String introduction) throws Exception {
		String accessToken = getAccessToken(APP_ID, APP_SECRET);
		// 根据mediaType判断上传文件类型：1图片、2视频
		String postResult = null;
		if (SysWxReplyMessageEnum.MESSAGE_MEDIA_TYPE_IMAGE.getType().equals(mediaType)) {
			String url = String.format(MEDIA_UPLOAD_URL, accessToken, SysWxReplyMessageEnum.WX_MEDIA_TYPE_IMAGE.getDescription());
			// 图片格式统一转换为jpg（解决微信上传png格式图片失败的问题）
			InputStream convertInputStream = WXBizMsgCrypt.convertToJPGFormat(file.getInputStream());
			postResult = HttpRequest.sendPostWithFile(url, convertInputStream, file.getOriginalFilename());
		} else if (SysWxReplyMessageEnum.MESSAGE_MEDIA_TYPE_VIDEO.getType().equals(mediaType)) {
			String url = String.format(MEDIA_UPLOAD_URL, accessToken, SysWxReplyMessageEnum.WX_MEDIA_TYPE_VIDEO.getDescription());
			Map<String, Object> params = new HashMap<>();
			params.put("title", title);
			params.put("introduction", introduction);
			log.info("微信上传素材接口请求：fileSize={}，fileName={}，params={}", file.getSize(), file.getOriginalFilename(), params);
			postResult = HttpRequest.sendPostWithFile(url, file.getInputStream(), file.getOriginalFilename(), params);
		}
		log.info("微信上传素材接口响应：{}", postResult);
		if (StringUtils.isBlank(postResult)) {
			return null;
		}
		JSONObject jsonResult = JSONObject.parseObject(postResult);
		if (jsonResult.getString("errcode") != null) {
			return null;
		}
		Map<String, String> result = new HashMap<>();
		// 如果是视频，则再调用接口获取视频url等信息
		if (SysWxReplyMessageEnum.MESSAGE_MEDIA_TYPE_VIDEO.getType().equals(mediaType)) {
			String mediaId = jsonResult.getString("media_id");
			Map<String, Object> materialResult = getMaterial(mediaId);
			result.put("title", String.valueOf(materialResult.get("title")));
			result.put("introduction", introduction);
			result.put("url", String.valueOf(materialResult.get("url")));
		} else {
			result.put("url", jsonResult.getString("url"));
		}
		result.put("mediaId", jsonResult.getString("media_id"));
		return result;
	}

	/**
	 * 发送客服文本消息
	 *
	 * @param toUser
	 * @param content 文本内容或图片media_id
	 * @return
	 */
	@Override
	public boolean sendCustomMessage(String toUser, String msgType, String content) {
		String url = String.format(CUSTOM_SEND_URL, getAccessToken(APP_ID, APP_SECRET));
		// 根据msgType类型封装请求json参数
		SysWxCustomerMessagePostBody postBody = new SysWxCustomerMessagePostBody();
		postBody.setToUser(toUser);
		postBody.setMsgType(msgType);
		if (SysWxReplyMessageEnum.WX_MEDIA_TYPE_TEXT.getDescription().equals(msgType)) {
			// 文本
			SysWxCustomerMessageTextPostBody textPostBody = new SysWxCustomerMessageTextPostBody();
			textPostBody.setContent(content);
			postBody.setText(textPostBody);
		} else if (SysWxReplyMessageEnum.WX_MEDIA_TYPE_IMAGE.getDescription().equals(msgType)) {
			// 图片
			SysWxCustomerMessageImagePostBody imagePostBody = new SysWxCustomerMessageImagePostBody();
			imagePostBody.setMediaId(content);
			postBody.setImage(imagePostBody);
		}

		String jsonParam = JSON.toJSONString(postBody);
		log.info("微信发送客服消息接口请求：{}", jsonParam);
		String result = HttpRequest.sendPostWithJson(url, jsonParam);
		log.info("微信发送客服消息接口响应：{}", result);
		if (StringUtils.isBlank(result)) {
			return false;
		}
		JSONObject jsonResult = JSON.parseObject(result);
		if ("0".equals(jsonResult.getString("errcode"))) {
			return true;
		}
		return false;
	}

}
