package com.nokia.common.dingtalk;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import org.springframework.core.io.FileSystemResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nokia.common.dingtalk.exception.DingTalkApiException;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/*
 * 钉钉API封装
 * 采用RestTemplate调用API接口的方式进行封装
 * 官方SDK分为新版与旧版，使用起来比较复杂
 */
@Slf4j
public class DingTalkUtil {

    private static final String API_ACCESS_TOKEN_REDIS_KEY = "API_DING_TALK_ACCESS_TOKEN";
    private static final String OAPI_ACCESS_TOKEN_REDIS_KEY = "OAPI_DING_TALK_ACCESS_TOKEN";

    private final String appKey;
    private final String appSecret;
    private final RestTemplate restTemplate;
    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;

    @Setter
    private String conversationId;

    public DingTalkUtil(String appKey, String appSecret, RestTemplate restTemplate,
            RedisTemplate<String, Object> redisTemplate, ObjectMapper objectMapper) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.restTemplate = restTemplate;
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
    }

    /*
     * 上传文件，返回mediaid
     */
    public String upload(String filePath, String type) throws DingTalkApiException {
        String url = "https://oapi.dingtalk.com/media/upload?access_token={0}";
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);

        MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
        FileSystemResource fileSystemResource = new FileSystemResource(filePath);
        multiValueMap.add("media", fileSystemResource);
        multiValueMap.add("type", type);

        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, httpHeaders);

        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class,
                getOapiToken());
        if (responseEntity.getStatusCode().is2xxSuccessful()) {
            try {
                JsonNode node = objectMapper.readTree(responseEntity.getBody());
                int errcode = node.get("errcode").asInt(-1);
                if (errcode != 0) {
                    throw new DingTalkApiException(node.get("errmsg").asText());
                } else {
                    return node.get("media_id").asText();
                }
            } catch (JsonProcessingException e) {
                throw new DingTalkApiException(e.getMessage());
            }
        } else {
            throw new DingTalkApiException(
                    "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
        }
    }

    /**
     * 使用钉钉机器人发送图片消息
     * 
     * @param msgParam
     * @throws DingTalkApiException
     */
    public void sendImageMsgWithRobot(String mediaId) throws DingTalkApiException {
        String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());
        Map<String, Object> msgParam = new HashMap<>();
        msgParam.put("photoURL", mediaId);

        Map<String, Object> request = new HashMap<>();
        try {
            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
            request.put("msgKey", "sampleImageMsg");
            request.put("openConversationId", conversationId);
            request.put("robotCode", appKey);

            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);

            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                JsonNode node = objectMapper.readTree(responseEntity.getBody());
                if (node.get("processQueryKey") != null) {
                    return;
                } else {
                    throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
                }
            }
        } catch (JsonProcessingException e) {
            throw new DingTalkApiException("JSON转化失败--" + msgParam);
        }
    }

    /**
     * 使用钉钉机器人发送File消息
     * 
     * @param mediaId
     * @param fileName
     * @throws DingTalkApiException
     */
    public void sendFileMsgWithRobot(String mediaId, String fileName) throws DingTalkApiException {
        String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());

        Map<String, Object> msgParam = new HashMap<>();
        msgParam.put("mediaId", mediaId);
        msgParam.put("fileName", fileName);
        msgParam.put("fileType", fileName.substring(fileName.lastIndexOf('.') + 1));

        Map<String, Object> request = new HashMap<>();
        try {
            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
            request.put("msgKey", "sampleFile");
            request.put("openConversationId", conversationId);
            request.put("robotCode", appKey);

            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);

            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                JsonNode node = objectMapper.readTree(responseEntity.getBody());
                if (node.get("processQueryKey") != null) {
                    return;
                } else {
                    throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
                }
            }
        } catch (JsonProcessingException e) {
            throw new DingTalkApiException("入参格式错误--" + msgParam);
        }
    }

    /**
     * 使用钉钉机器人发送文字消息
     * 
     * @param msg
     * @throws DingTalkApiException
     */
    public void sendTextMsgWithRobot(String msg) throws DingTalkApiException {
        String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());

        Map<String, Object> msgParam = new HashMap<>();
        msgParam.put("content", msg);

        Map<String, Object> request = new HashMap<>();
        try {
            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
            request.put("msgKey", "sampleText");
            request.put("openConversationId", conversationId);
            request.put("robotCode", appKey);

            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);

            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                JsonNode node = objectMapper.readTree(responseEntity.getBody());
                if (node.get("processQueryKey") != null) {
                    return;
                } else {
                    throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
                }
            }
        } catch (JsonProcessingException e) {
            throw new DingTalkApiException("入参格式错误--" + msgParam);
        }
    }

    /**
     * 使用钉钉机器人发送文字消息
     * 
     * @param msg
     * @throws DingTalkApiException
     */
    public void sendMarkdownMsgWithRobot(String msg) throws DingTalkApiException {
        String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());

        Map<String, Object> msgParam = new HashMap<>();
        msgParam.put("text", msg);

        Map<String, Object> request = new HashMap<>();
        try {
            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
            request.put("msgKey", "sampleMarkdown");
            request.put("openConversationId", conversationId);
            request.put("robotCode", appKey);

            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);

            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);

            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                JsonNode node = objectMapper.readTree(responseEntity.getBody());
                if (node.get("processQueryKey") != null) {
                    return;
                } else {
                    throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
                }
            }
        } catch (JsonProcessingException e) {
            throw new DingTalkApiException("入参格式错误--" + msgParam);
        }
    }

    /*
     * 获取api access token
     * 钉钉的access token的有效期是2个小时，并且对获取token的接口有熔断机制
     * 这里使用redis缓存token，缓存有效期设置为90分钟。
     * 优先从redis获取，redis中不存在的情况下从
     */
    public String getApiToken() throws DingTalkApiException {
        Object token = redisTemplate.opsForValue().get(appKey + API_ACCESS_TOKEN_REDIS_KEY);
        if (token == null) {
            // 调用api获取accessToken
            String url = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            Map<String, Object> request = new HashMap<>();
            request.put("appKey", appKey);
            request.put("appSecret", appSecret);
            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<Map<String, Object>>(request, headers);

            // 发送请求
            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
            log.info("请求新的api accessToken--{}", responseEntity.getStatusCode());
            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                try {
                    JsonNode node = objectMapper.readTree(responseEntity.getBody());
                    if (node.get("accessToken") != null) {
                        // 添加到缓存,缓存时间比返回的时间短10秒
                        token = node.get("accessToken").asText();
                        redisTemplate.boundValueOps(appKey + API_ACCESS_TOKEN_REDIS_KEY)
                                .set(token, Duration.ofSeconds(node.get("expireIn").asLong(3600L) - 10L));
                    } else {
                        throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
                    }
                } catch (JsonProcessingException e) {
                    throw new DingTalkApiException(e.getMessage());
                }
            } else {
                throw new DingTalkApiException(
                        "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
            }
        }
        return (String) token;
    }

    /*
     * 获取oapi access token
     */
    public Object getOapiToken() throws DingTalkApiException {
        Object token = redisTemplate.opsForValue().get(appKey + OAPI_ACCESS_TOKEN_REDIS_KEY);

        if (token == null) {
            String url = "https://oapi.dingtalk.com/gettoken?appkey={0}&appsecret={1}";

            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, appKey, appSecret);
            log.info("请求新的oapi accessToken--{}--{}", responseEntity.getStatusCode(), responseEntity.getBody());
            if (responseEntity.getStatusCode().is2xxSuccessful()) {
                try {
                    JsonNode node = objectMapper.readTree(responseEntity.getBody());
                    int code = node.get("errcode").asInt(-1);
                    if (code == 0) {
                        // 添加到缓存
                        token = node.get("access_token").asText();
                        redisTemplate.boundValueOps(appKey + OAPI_ACCESS_TOKEN_REDIS_KEY)
                                .set(token, Duration.ofSeconds(node.get("expires_in").asLong(3600L) - 10L));
                    } else {
                        throw new DingTalkApiException(node.get("errmsg").asText());
                    }
                } catch (JsonProcessingException e) {
                    throw new DingTalkApiException(e.getMessage());
                }
            } else {
                throw new DingTalkApiException(
                        "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
            }
        }
        return (String) token;
    }
}
