package com.njcn.user.mapper;

import com.alibaba.nacos.shaded.com.google.gson.Gson;
import com.alibaba.nacos.shaded.com.google.gson.JsonObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.redis.utils.RedisUtil;
import com.njcn.user.pojo.dto.CredentialReqDTO;
import com.njcn.user.pojo.dto.SendResult;
import com.njcn.user.pojo.po.app.SmsSendRecord;
import com.njcn.user.pojo.vo.app.MessageRecordReqVO;
import com.njcn.user.service.ISmsSendService;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author xy
 */
@Slf4j
@Service
@AllArgsConstructor
@RequiredArgsConstructor
public class SmsSendServiceImpl extends ServiceImpl<CsSmsSendRecordMapper, SmsSendRecord> implements ISmsSendService {

    @Value("${msg.credential_url:http://192.168.2.126:48083/admin-api/push/credential/generate}")
    private String CREDENTIAL_URL;
    @Value("${msg.sms_send_url:http://192.168.2.126:48083/admin-api/push/message/send/sms}")
    private String SMS_SEND_URL;
    @Value("${msg.connect_timeout:5000}")
    private Integer CONNECT_TIMEOUT;
    @Value("${msg.read_timeout:30000}")
    private Integer READ_TIMEOUT;
    @Value("${msg.system_name:NPQS-9500}")
    private String SYSTEM_NAME;
    @Value("${msg.secret_key:123456}")
    private String SECRET_KEY;

    private static final int[] RETRY_DELAYS = {1, 2, 3};
    private static final int MAX_RETRY = 3;
    private static final String CREDENTIAL_CACHE_KEY = "SMS_CREDENTIAL_TOKEN";
    private static final Gson GSON = new Gson();

    @Resource
    private RedisUtil redisUtil;

    @Override
    public void sendSmsWithRetry(String receiver, String content, String messageType) {
        MessageRecordReqVO vo = new MessageRecordReqVO();
        vo.setReceiver(receiver);
        vo.setContent(content);
        vo.setMessageType(messageType);
        sendSmsWithRetry(vo);
    }

    @Override
    public void sendSmsWithRetry(MessageRecordReqVO messageRecordReqVO) {
        SmsSendRecord record = initRecord(messageRecordReqVO);

        this.save(record);

        try {
            String credentialToken = getOrRefreshCredentialWithRetry(record);

            if (credentialToken == null) {
                record.setSendStatus(0);
                record.setFailReason("获取凭证失败，已重试3次");
                log.error("获取凭证失败，短信未发送，接收者: {}", messageRecordReqVO.getReceiver());
                this.updateById(record);
                throw new BusinessException("获取凭证失败，已重试3次");
            }

            record.setCredentialToken(credentialToken);
            record.setSendTime(LocalDateTime.now());
            this.updateById(record);

            boolean success = attemptSendWithRetry(messageRecordReqVO, credentialToken, record);

            if (success) {
                record.setSendStatus(1);
                record.setFailReason(null);
                log.info("短信发送成功，接收者: {}", messageRecordReqVO.getReceiver());
            } else {
                record.setSendStatus(0);
                if (record.getFailReason() == null) {
                    record.setFailReason("超过最大重试次数，发送失败");
                }
                log.error("短信发送失败，接收者: {}，已重试{}次，原因: {}",
                        messageRecordReqVO.getReceiver(), record.getRetryCount(), record.getFailReason());
                throw new BusinessException("短信发送失败: " + record.getFailReason());
            }
        } catch (BusinessException e) {
            record.setSendStatus(0);
            record.setFailReason(e.getMessage());
            log.error("短信发送业务异常，接收者: {}", messageRecordReqVO.getReceiver(), e);
            this.updateById(record);
            throw e;
        } catch (Exception e) {
            record.setSendStatus(0);
            record.setFailReason("发送异常: " + e.getMessage());
            log.error("短信发送异常，接收者: {}", messageRecordReqVO.getReceiver(), e);
            this.updateById(record);
            throw new BusinessException("短信发送异常: " + e.getMessage());
        } finally {
            this.updateById(record);
        }
    }

    private SmsSendRecord initRecord(MessageRecordReqVO vo) {
        SmsSendRecord record = new SmsSendRecord();
        record.setReceiver(vo.getReceiver());
        record.setContent(vo.getContent());
        record.setMessageType(vo.getMessageType());
        record.setSendStatus(-1);
        record.setRetryCount(0);
        record.setMaxRetry(MAX_RETRY);
        record.setCreateTime(LocalDateTime.now());
        return record;
    }

    private String getOrRefreshCredentialWithRetry(SmsSendRecord record) {
        Object cachedToken = redisUtil.getObjectByKey(CREDENTIAL_CACHE_KEY);
        if (cachedToken != null) {
            log.info("使用缓存的凭证令牌");
            return cachedToken.toString();
        }

        log.info("缓存中无凭证，开始获取新凭证（最多重试3次）");

        for (int i = 1; i <= 3; i++) {
            try {
                String token = fetchNewCredential();
                log.info("第{}次尝试获取凭证成功", i);
                return token;
            } catch (Exception e) {
                log.warn("第{}次获取凭证失败: {}", i, e.getMessage());

                record.setFailReason("获取凭证第" + i + "次失败: " + e.getMessage());
                this.updateById(record);

                try {
                    int waitSeconds = i * 10;
                    log.info("等待{}秒后重试...", waitSeconds);
                    TimeUnit.SECONDS.sleep(waitSeconds);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    log.error("凭证获取重试被中断");
                    record.setFailReason("获取凭证实例被中断");
                    this.updateById(record);
                    return null;
                }
            }
        }

        log.error("获取凭证失败，已重试3次");
        return null;
    }

    private String fetchNewCredential() {
        CredentialReqDTO reqDTO = new CredentialReqDTO();
        reqDTO.setSystemName(SYSTEM_NAME);
        reqDTO.setSecretKey(SECRET_KEY);

        HttpURLConnection connection = null;
        try {
            URL url = new URL(CREDENTIAL_URL);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);
            connection.setDoOutput(true);

            OutputStream outputStream = connection.getOutputStream();
            outputStream.write(GSON.toJson(reqDTO).getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();

            int responseCode = connection.getResponseCode();
            if (responseCode != 200) {
                throw new BusinessException("获取凭证失败，HTTP响应码: " + responseCode);
            }

            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                response.append(inputLine);
            }
            reader.close();

            JsonObject jsonResponse = GSON.fromJson(response.toString(), JsonObject.class);
            int code = jsonResponse.get("code").getAsInt();

            if (code != 0) {
                String msg = jsonResponse.has("msg") ? jsonResponse.get("msg").getAsString() : "未知错误";
                throw new BusinessException("获取凭证失败，错误码: " + code + "，错误信息: " + msg);
            }

            JsonObject data = jsonResponse.getAsJsonObject("data");
            String token = data.get("credentialToken").getAsString();
            long expiresTimestamp = data.get("expiresTime").getAsLong();

            LocalDateTime expiresTime = LocalDateTime.ofInstant(
                    Instant.ofEpochMilli(expiresTimestamp),
                    ZoneId.systemDefault()
            );

            long expireSeconds = calculateExpireSeconds(expiresTime);
            redisUtil.saveByKeyWithExpire(CREDENTIAL_CACHE_KEY, token, expireSeconds);

            log.info("获取新凭证成功，过期时间: {}，缓存有效期: {}秒", expiresTime, expireSeconds);
            return token;

        } catch (SocketTimeoutException e) {
            throw new BusinessException("获取凭证超时(30秒)，请检查网络连接");
        } catch (ConnectException e) {
            throw new BusinessException("无法连接到凭证服务，请检查服务是否启动和网络是否正常");
        } catch (IOException e) {
            throw new BusinessException("获取凭证IO异常: " + e.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private long calculateExpireSeconds(LocalDateTime expiresTime) {
        long expireSeconds = java.time.Duration.between(
                LocalDateTime.now(),
                expiresTime
        ).getSeconds();

        expireSeconds = expireSeconds - 60;

        return Math.max(expireSeconds, 60);
    }

    private boolean attemptSendWithRetry(MessageRecordReqVO vo, String token, SmsSendRecord record) {
        for (int attempt = 0; attempt <= MAX_RETRY; attempt++) {
            if (attempt > 0) {
                int delayMinutes = RETRY_DELAYS[attempt - 1];
                log.info("第{}次重试，等待{}分钟后发送...", attempt, delayMinutes);

                try {
                    TimeUnit.MINUTES.sleep(delayMinutes);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("重试等待被中断", e);
                    record.setFailReason("重试等待被中断");
                    return false;
                }
                record.setRetryCount(attempt);
            }

            SendResult result = executeSendSms(vo, token, record);

            if (result.isSuccess()) {
                record.setFailReason(null);
                log.info("第{}次尝试发送成功，消息ID: {}", attempt, result.getMessageId());
                return true;
            }

            record.setFailReason(result.getFailReason());

            if (result.isUnauthorized()) {
                log.warn("凭证失效(401)，重新获取凭证后重试...");
                String newToken = getOrRefreshCredentialWithRetry(record);
                if (newToken == null) {
                    record.setFailReason("凭证刷新失败，无法重新获取凭证");
                    return false;
                }
                token = newToken;
                record.setCredentialToken(newToken);
                this.updateById(record);
                continue;
            }

            if (!result.isTimeOut()) {
                log.warn("发送失败且非超时，不再重试，原因: {}，响应时间: {}ms",
                        result.getFailReason(), record.getResponseTime());
                return false;
            }

            log.warn("第{}次发送超时，将重试，响应时间: {}ms",
                    attempt, record.getResponseTime());
        }

        record.setFailReason("超过最大重试次数，发送超时");
        return false;
    }

    private SendResult executeSendSms(MessageRecordReqVO vo, String token, SmsSendRecord record) {
        HttpURLConnection connection = null;
        long startTime = System.currentTimeMillis();

        try {
            URL url = new URL(SMS_SEND_URL);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("X-Credential-Token", token);
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);
            connection.setDoOutput(true);

            OutputStream outputStream = connection.getOutputStream();
            outputStream.write(GSON.toJson(Collections.singletonList(vo)).getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();

            int responseCode = connection.getResponseCode();
            long responseTime = System.currentTimeMillis() - startTime;
            record.setResponseTime(responseTime);

            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(
                            responseCode == 200 ? connection.getInputStream() : connection.getErrorStream(),
                            StandardCharsets.UTF_8
                    )
            );
            StringBuilder response = new StringBuilder();
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                response.append(inputLine);
            }
            reader.close();

            if (responseCode != 200) {
                String failReason = "HTTP响应码异常: " + responseCode;
                if (response.length() > 0) {
                    failReason += "，响应: " + response.toString();
                }
                record.setFailReason(failReason);
                return new SendResult(false, null, failReason, false, false);
            }

            JsonObject jsonResponse = GSON.fromJson(response.toString(), JsonObject.class);
            int code = jsonResponse.get("code").getAsInt();

            if (code == 401) {
                String failReason = "凭证失效(HTTP 401)";
                record.setFailReason(failReason);
                redisUtil.delete(CREDENTIAL_CACHE_KEY);
                return new SendResult(false, null, failReason, false, true);
            } else {
                if (code != 0) {
                    String msg = jsonResponse.has("msg") ? jsonResponse.get("msg").getAsString() : "未知错误";
                    String failReason = "业务错误码: " + code + "，错误信息: " + msg;
                    record.setFailReason(failReason);
                    return new SendResult(false, null, failReason, false, false);
                }
            }

            JsonObject firstResult = jsonResponse.getAsJsonArray("data").get(0).getAsJsonObject();
            boolean result = firstResult.get("result").getAsBoolean();
            String messageId = firstResult.has("messageId") ? firstResult.get("messageId").getAsString() : null;
            String detail = firstResult.has("detail") ? firstResult.get("detail").getAsString() : null;

            if (result) {
                log.info("短信发送成功，接收者: {}，消息ID: {}，详情: {}，耗时: {}ms",
                        vo.getReceiver(), messageId, detail, responseTime);
                return new SendResult(true, messageId, null, false, false);
            } else {
                String failReason = "发送失败: " + detail;
                record.setFailReason(failReason);
                return new SendResult(false, messageId, failReason, false, false);
            }

        } catch (SocketTimeoutException e) {
            long responseTime = System.currentTimeMillis() - startTime;
            record.setResponseTime(responseTime);
            String failReason = "请求超时(30秒)";
            record.setFailReason(failReason);
            log.warn("短信发送超时，接收者: {}，耗时: {}ms", vo.getReceiver(), responseTime);
            return new SendResult(false, null, failReason, true, false);
        } catch (ConnectException e) {
            long responseTime = System.currentTimeMillis() - startTime;
            record.setResponseTime(responseTime);
            String failReason = "无法连接到短信服务";
            record.setFailReason(failReason);
            log.error("短信服务连接失败，接收者: {}", vo.getReceiver(), e);
            return new SendResult(false, null, failReason, false, false);
        } catch (IOException e) {
            long responseTime = System.currentTimeMillis() - startTime;
            record.setResponseTime(responseTime);
            String failReason = "IO异常: " + e.getMessage();
            record.setFailReason(failReason);
            log.error("短信发送IO异常，接收者: {}，耗时: {}ms", vo.getReceiver(), responseTime, e);
            return new SendResult(false, null, failReason, false, false);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}
