package com.cm.common.wechat.manager.officialaccount;

import com.cm.common.constants.ISystemConstant;
import com.cm.common.exception.SearchException;
import com.cm.common.exception.WechatAccessTokenException;
import com.cm.common.exception.WechatAccessTokenForUserException;
import com.cm.common.exception.WechatUserInfoException;
import com.cm.common.utils.AesUtil;
import com.cm.common.wechat.config.pojo.WechatOfficialAccountProperties;
import com.cm.common.wechat.pojo.WechatOfficialAccountAccessToken;
import com.cm.common.wechat.pojo.WechatOfficialAccountAccessTokenForUser;
import com.cm.common.wechat.pojo.WechatOfficialAccountUser;
import com.cm.common.wechat.pojo.WechatOfficialAccountUserInfo;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: WechatOfficialAccountManager
 * @Description: 公众号管理器
 * @Author: WangGeng
 * @Date: 2020/3/1 3:02 下午
 * @Version: 1.0
 **/
public class WechatOfficialAccountManager {

    private static final Logger LOG = LoggerFactory.getLogger(WechatOfficialAccountManager.class);
    private static WechatOfficialAccountManager wechatOfficialAccountManager = WechatOfficialAccountManagerBuilder.wechatOfficialAccountManager;

    private WechatOfficialAccountProperties wechatOfficialAccountProperties;
    private String accessToken;
    private long updateTime = 0L;

    private WechatOfficialAccountManager() {
    }

    public static WechatOfficialAccountManager getInstance() {
        return wechatOfficialAccountManager;
    }

    public String getAccessToken() {
        return accessToken;
    }

    /**
     * 刷新AccessToken，超过1小时30分刷新一次
     */
    public void refreshAccessToken() {
        long currentTime = System.currentTimeMillis();
        if ((currentTime - this.updateTime) >= 5400000) {
            LOG.debug("刷新公众号 AccessToken 开始");
            this.accessToken = getNewAccessToken();
            LOG.debug("accessToken: {}", this.accessToken);
            this.updateTime = System.currentTimeMillis();
            LOG.debug("刷新公众号 AccessToken 结束");
        }
    }

    /**
     * 获得新AccessToken
     *
     * @return
     */
    private String getNewAccessToken() {
        RestTemplate restTemplate = new RestTemplateBuilder().build();
        Map<String, Object> params = new HashMap<>(3);
        params.put("grant_type", wechatOfficialAccountProperties.getGrantType());
        params.put("appid", wechatOfficialAccountProperties.getAppId());
        params.put("secret", wechatOfficialAccountProperties.getAppSecret());
        String url = new StringBuilder(wechatOfficialAccountProperties.getAccessTokenUrl()).append("?grant_type={grant_type}&appid={appid}&secret={secret}").toString();
        WechatOfficialAccountAccessToken wechatOfficialAccountAccessToken = restTemplate.getForObject(url, WechatOfficialAccountAccessToken.class, params);
        if (wechatOfficialAccountAccessToken == null) {
            throw new WechatAccessTokenException("获取 AccessToken 失败");
        }
        if (wechatOfficialAccountAccessToken.getErrcode() != null && wechatOfficialAccountAccessToken.getErrcode() != 0) {
            throw new WechatAccessTokenException(String.format("获取AccessToken失败，错误码：%s，错误信息：%s", wechatOfficialAccountAccessToken.getErrcode(), wechatOfficialAccountAccessToken.getErrmsg()));
        }
        return wechatOfficialAccountAccessToken.getAccess_token();
    }

    public void setWechatOfficialAccountProperties(WechatOfficialAccountProperties wechatOfficialAccountProperties) {
        this.wechatOfficialAccountProperties = wechatOfficialAccountProperties;
    }

    /**
     * 重定向url
     *
     * @param redirectUrl
     * @return
     */
    public String getAuthorizeUrl(String redirectUrl) {
        StringBuilder authorizeUrl = new StringBuilder(wechatOfficialAccountProperties.getAuthorize().getAuthorizeUrl());
        authorizeUrl.append("?appid=").append(wechatOfficialAccountProperties.getAppId());
        authorizeUrl.append("&scope=").append(wechatOfficialAccountProperties.getAuthorize().getScope());
        authorizeUrl.append("&state=").append(wechatOfficialAccountProperties.getAuthorize().getState());
        authorizeUrl.append("&redirect_uri=").append(redirectUrl);
        authorizeUrl.append("&response_type=code#wechat_redirect");
        return authorizeUrl.toString();
    }

    /**
     * 设置用户信息
     *
     * @param code
     * @param state
     * @param session
     */
    public void setUserAccessToken(String code, String state, HttpSession session) throws Exception {
        if (StringUtils.equals(state, wechatOfficialAccountProperties.getAuthorize().getState())) {
            WechatOfficialAccountAccessTokenForUser wechatOfficialAccountAccessTokenForUser = getUserAccessToken(code);
            WechatOfficialAccountUserInfo wechatOfficialAccountUserInfo = getUserInfo(wechatOfficialAccountAccessTokenForUser.getAccess_token(), wechatOfficialAccountAccessTokenForUser.getOpenid());
            WechatOfficialAccountUser wechatOfficialAccountUser = new WechatOfficialAccountUser();
            LOG.debug("绑定用户 | 登录");
            String wechatSignInfo = Base64.encodeBase64String(AesUtil.aesCommonEncoder("WECHAT_SIGN_INFO", new StringBuilder(wechatOfficialAccountAccessTokenForUser.getOpenid()).append("_WenG_").append(wechatOfficialAccountProperties.getAppId()).toString()).getBytes("UTF-8"));
            String token = getAppToken(wechatSignInfo);
            LOG.debug("用户token：{}", token);
            wechatOfficialAccountUser.setToken(token);
            wechatOfficialAccountUser.setWechatOfficialAccountAccessTokenForUser(wechatOfficialAccountAccessTokenForUser);
            wechatOfficialAccountUser.setWechatOfficialAccountUserInfo(wechatOfficialAccountUserInfo);
            session.setAttribute(ISystemConstant.SESSION_WECHAT_ACCESS_TOKEN, wechatOfficialAccountUser);
            return;
        }
    }

    /**
     * 获取AppToken
     *
     * @param wechatSignInfo
     * @return
     */
    public String getAppToken(String wechatSignInfo) {
        RestTemplate restTemplate = new RestTemplate();
        Map<String, Object> params = new HashMap<>(1);
        params.put("signInfo", wechatSignInfo);
        HttpEntity<Map<String, Object>> requestBody = new HttpEntity<>(params);
        ResponseEntity<Map> result = restTemplate.postForEntity(wechatOfficialAccountProperties.getBindUserUrl(), requestBody, Map.class);
        LOG.debug("状态码：{}", result.getStatusCodeValue());
        if (HttpStatus.OK.value() != result.getStatusCodeValue()) {
            throw new SearchException("获取Token失败");
        }
        Map body = result.getBody();
        return (String) body.get("data");
    }

    /**
     * 获取用户AccessToken
     *
     * @param code
     * @return
     */
    public WechatOfficialAccountAccessTokenForUser getUserAccessToken(String code) throws IOException {
        Map<String, Object> params = new HashMap<>(4);
        params.put("appid", wechatOfficialAccountProperties.getAppId());
        params.put("secret", wechatOfficialAccountProperties.getAppSecret());
        params.put("code", code);
        params.put("grant_type", wechatOfficialAccountProperties.getAuthorize().getGrantType());

        StringBuilder url = new StringBuilder(wechatOfficialAccountProperties.getAuthorize().getAccessTokenUrl());
        url.append("?appid={appid}&secret={secret}&code={code}&grant_type={grant_type}");
        RestTemplate restTemplate = new RestTemplate();
        String resultJson = restTemplate.getForObject(url.toString(), String.class, params);
        WechatOfficialAccountAccessTokenForUser wechatOfficialAccountAccessTokenForUser = new ObjectMapper().readValue(resultJson, WechatOfficialAccountAccessTokenForUser.class);
        if (wechatOfficialAccountAccessTokenForUser == null) {
            LOG.error("获取用户AccessToken异常");
            throw new WechatAccessTokenForUserException("获取用户AccessToken异常");
        }
        if (wechatOfficialAccountAccessTokenForUser.getErrcode() != null && wechatOfficialAccountAccessTokenForUser.getErrcode() != 0) {
            throw new WechatAccessTokenForUserException(String.format("获取用户AccessToken失败，错误码：%s，错误信息：%s", wechatOfficialAccountAccessTokenForUser.getErrcode(), wechatOfficialAccountAccessTokenForUser.getErrmsg()));
        }
        return wechatOfficialAccountAccessTokenForUser;
    }

    /**
     * 获取用户信息
     *
     * @param accessToken
     * @param openid
     * @return
     */
    public WechatOfficialAccountUserInfo getUserInfo(String accessToken, String openid) throws IOException {
        Map<String, Object> params = new HashMap<>(2);
        params.put("access_token", accessToken);
        params.put("openid", openid);
        StringBuilder url = new StringBuilder(wechatOfficialAccountProperties.getAuthorize().getUserinfoUrl());
        url.append("?access_token={access_token}&openid={openid}&lang=zh_CN");
        RestTemplate restTemplate = new RestTemplate();
        String resultJson = restTemplate.getForObject(url.toString(), String.class, params);
        WechatOfficialAccountUserInfo wechatOfficialAccountUserInfo = new ObjectMapper().readValue(resultJson, WechatOfficialAccountUserInfo.class);
        if (wechatOfficialAccountUserInfo == null) {
            throw new WechatUserInfoException("获取微信用户信息失败");
        }
        if (wechatOfficialAccountUserInfo.getErrcode() != null && wechatOfficialAccountUserInfo.getErrcode() != 0) {
            throw new WechatAccessTokenForUserException(String.format("获取用户信息失败，错误码：%s，错误信息：%s", wechatOfficialAccountUserInfo.getErrcode(), wechatOfficialAccountUserInfo.getErrmsg()));
        }
        return wechatOfficialAccountUserInfo;
    }

    private static class WechatOfficialAccountManagerBuilder {
        public static WechatOfficialAccountManager wechatOfficialAccountManager = new WechatOfficialAccountManager();
    }

}
