package com.codvision.auth.service.third;

import com.codvision.auth.entity.ding.DingConfig;
import com.codvision.auth.entity.ding.DingEnv;
import com.codvision.base.service.ISysConfigService;
import com.codvision.cachecore.utils.RedisUtil;
import com.codvision.commoncore.exception.BusinessException;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.*;
import com.dingtalk.api.response.*;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.codvision.auth.support.CacheContants.DING_TALK_JSAPI_TICKET;
import static com.codvision.auth.support.CacheContants.DING_TALK_TOKEN;


/**
 * 普通钉钉服务
 *
 * @author hxl
 */
@Slf4j
@Service
public class DingTalkService {

    @Resource
    private ISysConfigService configService;

    private static DingEnv dingEnv;

    @PostConstruct
    public void init() {
        dingEnv = DingEnv.builder().dingAppKey(configService.getValueByKey("DING_APP_KEY")).dingAppSecret(configService.getValueByKey("DING_APP_SECRET"))
                .dingAgentId(configService.getValueByKey("DING_AGENT_ID")).dingCorpId(configService.getValueByKey("DING_CORP_ID")).build();
    }

    public String getAccessToken() throws ApiException {
        String dingTalkToken = RedisUtil.get(DING_TALK_TOKEN);
        if (StringUtils.isNotBlank(dingTalkToken)) {
            return dingTalkToken;
        }

        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(dingEnv.getDingAppKey());
        request.setAppsecret(dingEnv.getDingAppSecret());
        request.setHttpMethod("GET");
        OapiGettokenResponse rsp = client.execute(request);
        if (rsp.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", rsp.getErrmsg());
            throw new BusinessException(rsp.getErrmsg());
        }

        dingTalkToken = rsp.getAccessToken();
        // access_token的有效期为7200秒（2小时），有效期内重复获取会返回相同结果并自动续期，过期后获取会返回新的access_token。
        // 防止临界过期问题，缓存失效时间比实际过期时间设置的偏低
        RedisUtil.set(DING_TALK_TOKEN, dingTalkToken, 119, TimeUnit.MINUTES);
        return dingTalkToken;
    }

    public String getJsapiTicket() throws ApiException {
        String dingTalkJsapiTicket = RedisUtil.get(DING_TALK_JSAPI_TICKET);
        if (StringUtils.isNotBlank(dingTalkJsapiTicket)) {
            return dingTalkJsapiTicket;
        }

        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/get_jsapi_ticket");
        OapiGetJsapiTicketRequest req = new OapiGetJsapiTicketRequest();
        req.setHttpMethod("GET");
        OapiGetJsapiTicketResponse rsp = client.execute(req, getAccessToken());
        if (rsp.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", rsp.getErrmsg());
            throw new BusinessException(rsp.getErrmsg());
        }

        dingTalkJsapiTicket = rsp.getTicket();
        RedisUtil.set(DING_TALK_JSAPI_TICKET, dingTalkJsapiTicket, 2, TimeUnit.HOURS);
        return dingTalkJsapiTicket;
    }


    public DingConfig getConfig(String url) {
        DingConfig config;
        try {
            config = DingConfig.builder().url(url).jsticket(getJsapiTicket()).nonceStr(UUID.randomUUID().toString()).timeStamp(System.currentTimeMillis() / 1000)
                    .corpId(dingEnv.getDingCorpId()).agentId(dingEnv.getDingAgentId()).appkey(dingEnv.getDingAppKey()).build();
            config.setSignature(getSign(config.getJsticket(), config.getNonceStr(), config.getTimeStamp(), url));
        } catch (ApiException e) {
            return new DingConfig();
        }

        return config;
    }

    private String getSign(String jsTicket, String nonceStr, Long timeStamp, String url) {
        String plainTex = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(plainTex.getBytes(StandardCharsets.UTF_8));
            return byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String byteToHex(byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    public OapiV2UserGetResponse getUserInfo(String code) throws ApiException {
        String accessToken = getAccessToken();
        String userId = getUerIdByAccessTokenAndCode(accessToken, code);
        OapiV2UserGetResponse rsp = getUerDetailByAccessTokenAndUserId(accessToken, userId);
        if (rsp.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", rsp.getErrmsg());
            throw new BusinessException(rsp.getErrmsg());
        }
        return rsp;
    }

    public String getUerIdByAccessTokenAndCode(String accessToken, String code) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
        OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest();
        req.setCode(code);
        OapiV2UserGetuserinfoResponse rsp = client.execute(req, accessToken);
        if (0 != rsp.getErrcode()) {
            log.error("The errMsg is 【{}】", rsp.getErrmsg());
            throw new BusinessException(rsp.getErrmsg());
        }
        return rsp.getResult().getUserid();
    }

    public OapiV2UserGetResponse getUerDetailByAccessTokenAndUserId(String accessToken, String userId) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
        OapiV2UserGetRequest req = new OapiV2UserGetRequest();
        req.setUserid(userId);
        req.setLanguage("zh_CN");
        return client.execute(req, accessToken);
    }

    public OapiV2UserGetResponse getUserInfoByScan(String tmpAuthCode) throws ApiException {
        String accessToken = getAccessToken();
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
        OapiSnsGetuserinfoBycodeRequest req = new OapiSnsGetuserinfoBycodeRequest();
        req.setTmpAuthCode(tmpAuthCode);
        OapiSnsGetuserinfoBycodeResponse byCodeResponse = client.execute(req, dingEnv.getDingAppKey(), dingEnv.getDingAppSecret());
        if (byCodeResponse.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", byCodeResponse.getErrmsg());
            throw new BusinessException(byCodeResponse.getErrmsg());
        }

        String unionid = byCodeResponse.getUserInfo().getUnionid();
        DingTalkClient clientDingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
        OapiUserGetbyunionidRequest reqGetByUnionidRequest = new OapiUserGetbyunionidRequest();
        reqGetByUnionidRequest.setUnionid(unionid);
        OapiUserGetbyunionidResponse oapiUserGetbyunionidResponse = clientDingTalkClient.execute(reqGetByUnionidRequest, accessToken);
        if (oapiUserGetbyunionidResponse.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", oapiUserGetbyunionidResponse.getErrmsg());
            throw new BusinessException(oapiUserGetbyunionidResponse.getErrmsg());
        }
        OapiV2UserGetResponse rspGetResponse = getUerDetailByAccessTokenAndUserId(accessToken, oapiUserGetbyunionidResponse.getResult().getUserid());
        if (rspGetResponse.getErrcode() != 0) {
            log.error("The errMsg is 【{}】", rspGetResponse.getErrmsg());
            throw new BusinessException(rspGetResponse.getErrmsg());
        }

        return rspGetResponse;
    }

}
