package com.usoftchina.qywx.sdk;

import com.usoftchina.qywx.sdk.config.Agent;
import com.usoftchina.qywx.sdk.config.QywxProperties;
import com.usoftchina.qywx.sdk.dto.BaseResp;
import com.usoftchina.qywx.sdk.dto.GetAccessTokenResp;
import com.usoftchina.qywx.sdk.exception.WeiXinAccessException;
import com.usoftchina.qywx.sdk.exception.WeiXinInvokeException;
import com.usoftchina.qywx.sdk.util.HttpUtils;
import com.usoftchina.qywx.sdk.util.QywxConst;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.ModelMap;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * @author yingp
 */
public abstract class BaseSdk {
    protected String baseUrl = QywxConst.API_BASE_URL;
    private QywxProperties properties;
    /**
     * <私钥, Token>
     */
    private final Map<String, AccessToken> accessTokenMap = new HashMap<>();
    protected final static RestTemplate restTemplate = HttpUtils.createTemplate();

    public BaseSdk(QywxProperties properties) {
        this.properties = properties;
    }

    public String getCorpId() {
        return properties.getCorpId();
    }

    protected Map<String, Agent> getAgentMap() {
        return properties.getAgentMap();
    }

    protected void assertOK(ResponseEntity<? extends BaseResp> resp) {
        if (resp.getStatusCode() != HttpStatus.OK) {
            throw new WeiXinAccessException(resp);
        }
        if (resp.getBody().getErrcode() != 0) {
            throw new WeiXinInvokeException(resp.getBody());
        }
    }

    /**
     * 处理微信系统繁忙导致的异常
     * errcode = -1, errmsg = 服务器暂不可用，建议稍候重试。建议重试次数不超过3次
     *
     * @param callable
     * @param <R>
     * @return
     */
    protected <R extends ResponseEntity<? extends BaseResp>> R tryAndIgnoreSystemError(Callable<R> callable) {
        for (int i = 0; i < 3; i++) {
            try {
                R resp = callable.call();
                if (resp.getStatusCode() != HttpStatus.OK) {
                    throw new WeiXinAccessException(resp);
                }
                if (resp.getBody().getErrcode() == 0) {
                    return resp;
                } else if (resp.getBody().getErrcode() == -1 && i < 2) {
                    continue;
                } else {
                    throw new WeiXinInvokeException(resp.getBody());
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    /**
     * @param agentCode
     * @return
     */
    protected synchronized String getAccessToken(String agentCode) {
        Agent agent = getAgentMap().get(agentCode);
        if (null == agent) {
            throw new RuntimeException("没有找到应用" + agentCode);
        }
        if (!accessTokenMap.containsKey(agent.getSecret()) || accessTokenMap.get(agent.getSecret()).expired()) {
            ResponseEntity<GetAccessTokenResp> resp = restTemplate.getForEntity(
                    baseUrl + "/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}",
                    GetAccessTokenResp.class,
                    new ModelMap("corpid", getCorpId()).addAttribute("corpsecret", agent.getSecret()));
            assertOK(resp);
            accessTokenMap.put(agent.getSecret(), new AccessToken(resp.getBody()));
        }
        return accessTokenMap.get(agent.getSecret()).getToken();
    }

    public boolean isAgentEnabled(String agentCode) {
        return null != getAgentMap() && getAgentMap().containsKey(agentCode) && getAgentMap().get(agentCode).getSecret() != null;
    }

    class AccessToken {
        private final String token;
        private final long endTime;

        public AccessToken(GetAccessTokenResp resp) {
            this.token = resp.getAccess_token();
            this.endTime = System.currentTimeMillis() + resp.getExpires_in() * 1000 - 5000;
        }

        public boolean expired() {
            return System.currentTimeMillis() > endTime;
        }

        public String getToken() {
            return token;
        }
    }
}
