package org.qinan.safetyeval.infrastructure.remote;

import cn.hutool.json.JSONUtil;
import com.alibaba.cola.dto.SingleResponse;
import com.jjb.saas.framework.redis.service.RedisService;
import com.jjb.saas.message.client.message.facede.MessageFacade;
import com.jjb.saas.message.client.message.request.MessageSendCmd;
import com.jjb.saas.message.client.message.request.MessageTargetCmd;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.qinan.safetyeval.client.dto.SendMessageCmd;
import org.qinan.safetyeval.domain.exception.BizException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import static org.qinan.safetyeval.domain.exception.ErrorCode.*;

/**
 * 助通短信平台（融合云 v2）远程调用
 * <p>
 * 接口说明：
 * - 地址：https://api-shss.zthysms.com/v2/sendSms
 * - 方式：POST，Content-Type: application/json
 * - 鉴权：tKey = 当前 Unix 秒级时间戳；password = md5(md5(明文密码) + tKey)
 * - 错误码：200 成功；4023 签名错误；4001 用户名错误；4009 密码错误 等
 *
 * @author safety-eval
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SmsRemote {

    private final SmsProperties smsProperties;

    private RestTemplate restTemplate;

    @DubboReference
    private MessageFacade messageFacade;
    private final RedisService redisService;
    private final RedissonClient redissonClient;

    /**
     * 注册验证码前缀
     */
    public static final String SMS_SIGN_PREFIX = "sms:sign";


    @Value("${sms.sign.sourceCode:}")
    private String sourceCode;

    @PostConstruct
    public void init() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(smsProperties.getConnectTimeout());
        factory.setReadTimeout(smsProperties.getReadTimeout());
        this.restTemplate = new RestTemplate(factory);
    }

    /**
     * 发送注册验证码短信（模板ID 793059）
     *
     * @param mobile    手机号
     * @param validCode 验证码
     * @return 调用结果
     */
    public SingleResponse<String> sendRegisterVerificationCode(String mobile, String validCode) {
        // 模板内容：[秦安安全]您正在注册本系统，验证码:{valid_code}，5分钟内有效，请勿泄露给他人，如非本人操作请忽略本条短信。
        String content = smsProperties.getSignature()
                + "您正在注册本系统，验证码:" + validCode
                + "，5分钟内有效，请勿泄露给他人，如非本人操作请忽略本条短信。";
        return sendSms(mobile, content);
    }

    public SingleResponse<Boolean> sign(String mobile) {
        String validCode = generateVerificationCode();
        String bizId = SMS_SIGN_PREFIX + mobile;
        String content = "【秦安安全】您正在注册本系统，验证码:" + validCode + "，5分钟内有效，请勿泄露给他人，如非本人操作请忽略本条短信。";
        sendSms(mobile, content);
        redisService.set(bizId, validCode, 5 * 60 * 1000);
        return SingleResponse.of(true);
    }

    public boolean signValidCode(String mobile, String validCode) {
        String bizId = SMS_SIGN_PREFIX + mobile;
        // 以 bizId 为 key 加分布式锁，锁 1s，防止刷验证码
        RLock lock = redissonClient.getLock(bizId);
        boolean locked;
        try {
            // 不等待，拿不到锁即视为频繁操作；锁自动 1s 后释放
            locked = lock.tryLock(0, 1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BizException(SMS_CODE_TOO_FREQUENT);
        }
        if (!locked) {
            throw new BizException(SMS_CODE_TOO_FREQUENT);
        }
        try {
            Object code = redisService.get(bizId);
            if (code == null) {
                throw new BizException(SMS_CODE_EXPIRED);
            }
            if (!code.toString().equals(validCode)) {
                throw new BizException(SMS_CODE_NOT_RIGHT);
            }
            return true;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 发送短信（融合云 v2 接口，明文内容）
     *
     * @param mobile  手机号
     * @param content 短信内容（需包含全角【】签名）
     * @return 调用结果
     */
    public SingleResponse<String> sendSms(String mobile, String content) {
        log.info("助通短信发送请求 - mobile={}, content={}", mobile, content);
        SingleResponse<String> response;
        try {
            String jsonBody = buildRequestBody(mobile, content);
            log.info("助通短信发送请求体: {}", jsonBody);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));

            HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);

            ResponseEntity<String> resp =
                    restTemplate.postForEntity(smsProperties.getApiUrl(), requestEntity, String.class);

            String body = resp.getBody();
            log.info("助通短信发送响应: status={}, body={}", resp.getStatusCodeValue(), body);

            // 融合云 HTTP 状态码恒为 200，真正结果在 body 的 code 字段（200 才算成功）
            if (resp.getStatusCodeValue() != 200) {
                log.warn("助通短信发送失败(HTTP) - mobile={}, body={}", mobile, body);
                throw new BizException("SMS_SEND_FAIL", "短信发送失败: " + body);
            }

            // 解析 body 中的 code 字段判断业务结果
            Map<String, Object> bodyMap = parseJson(body);
            int code = bodyMap.get("code") instanceof Number
                    ? ((Number) bodyMap.get("code")).intValue() : -1;
            if (code != 200) {
                String msg = bodyMap.getOrDefault("msg", "未知错误").toString();
                log.error("助通短信发送失败(业务) - mobile={}, code={}, msg={}", mobile, code, msg);
                throw new BizException("SMS_SEND_FAIL",
                        "短信发送失败[" + code + "]: " + msg);
            }
            response = SingleResponse.of(body);
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("助通短信发送异常 - mobile={}", mobile, e);
            throw new BizException("SMS_SEND_ERROR", "短信发送异常: " + e.getMessage());
        }
        return response;
    }

    /**
     * 生成 6 位随机验证码
     *
     * @return 6位验证码字符串
     */
    public String generateVerificationCode() {
        return String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
    }

    // ==================== 内部方法 ====================

    /**
     * 构建融合云 v2 JSON 请求体
     */
    private String buildRequestBody(String mobile, String content) {
        String tKey = String.valueOf(System.currentTimeMillis() / 1000);
        String finalPassword = md5(md5(smsProperties.getPassword()) + tKey);

        return "{"
                + "\"username\":\"" + smsProperties.getUsername() + "\","
                + "\"password\":\"" + finalPassword + "\","
                + "\"tKey\":\"" + tKey + "\","
                + "\"mobile\":\"" + mobile + "\","
                + "\"content\":\"" + escapeJson(content) + "\""
                + "}";
    }

    /**
     * MD5 加密（小写）
     */
    private String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b & 0xff));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new BizException("SMS_MD5_ERROR", "MD5加密失败: " + e.getMessage());
        }
    }

    /**
     * JSON 字符串转义
     */
    private String escapeJson(String input) {
        if (input == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            switch (c) {
                case '"':
                    sb.append("\\\"");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 解析 JSON 字符串为 Map（基于 hutool JSONUtil）
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> parseJson(String json) {
        if (StringUtils.isBlank(json)) {
            return Collections.emptyMap();
        }
        return JSONUtil.parseObj(json);
    }


    @ApiOperation("发送短信")
    @PostMapping("/sendMessage")
    public com.alibaba.cola.dto.SingleResponse<Boolean> sendMessage(@Validated @RequestBody SendMessageCmd cmd) {
        if (StringUtils.isBlank(sourceCode)) {
            throw new BizException(SMS_SOURCE_CODE_NOT_EXIST);
        }

        MessageSendCmd messageSendCmd = new MessageSendCmd();
        messageSendCmd.setBusinessId(UUID.randomUUID().toString().replace("-", ""));

        MessageTargetCmd messageTargetCmd = new MessageTargetCmd();
        messageTargetCmd.setMobile(cmd.getMobile());
        messageSendCmd.setTargetCmd(messageTargetCmd);
        messageSendCmd.setNeedTokenEnum(false);

        messageSendCmd.setSourceCode(sourceCode);
        Map<String, Object> codeMap = new HashMap<>();
        codeMap.put("valid_code", "123456");
        messageSendCmd.setParams(codeMap);

        log.info("发送短信参数: messageSendCmd: {}", JSONUtil.toJsonStr(messageSendCmd));
        com.alibaba.cola.dto.SingleResponse<Boolean> result = messageFacade.send(messageSendCmd);
        log.info("发送短信结果: result: {}", JSONUtil.toJsonStr(result));
        return result;
    }
}
