package com.writeoff.module.notification.provider;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.writeoff.module.notification.model.PlatformNotifyGatewayResolvedConfig;
import com.writeoff.module.notification.service.PlatformNotifyGatewayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.UUID;

@Component
public class SmsNotificationProvider implements NotificationChannelProvider {
    private static final Logger log = LoggerFactory.getLogger(SmsNotificationProvider.class);
    private final PlatformNotifyGatewayService gatewayService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public SmsNotificationProvider(PlatformNotifyGatewayService gatewayService) {
        this.gatewayService = gatewayService;
    }

    @Override
    public String channel() {
        return "SMS";
    }

    @Override
    public NotificationSendResult send(String receiverRef, String payloadJson, Map<String, Object> context) {
        if (receiverRef == null || receiverRef.trim().isEmpty()) {
            return new NotificationSendResult(false, null, "INVALID_RECEIVER", "短信接收人不能为空");
        }
        PlatformNotifyGatewayResolvedConfig gatewayConfig = gatewayService.resolveChannelConfig("SMS", true);
        if (gatewayConfig == null) {
            String legacyId = "SMS-" + System.currentTimeMillis();
            log.info("sms gateway config not enabled, fallback to legacy mock accept, receiver={}", receiverRef.trim());
            return new NotificationSendResult(true, legacyId, "LEGACY_ACCEPTED", "短信通道已按兼容模式受理");
        }
        if ("ALIYUN_SMS".equalsIgnoreCase(gatewayConfig.getProviderCode())) {
            return sendByAliyun(receiverRef.trim(), payloadJson, context, gatewayConfig);
        }
        boolean mockEnabled = boolValue(gatewayConfig.getConfig().get("mockEnabled"), "MOCK".equalsIgnoreCase(gatewayConfig.getProviderCode()));
        String providerCode = gatewayConfig.getProviderCode() == null ? "SMS" : gatewayConfig.getProviderCode().trim().toUpperCase();
        String id = providerCode + "-" + System.currentTimeMillis();
        log.info("sms sending accepted, provider={}, receiver={}, mockEnabled={}", providerCode, receiverRef.trim(), mockEnabled);
        return new NotificationSendResult(true, id, mockEnabled ? "MOCK_ACCEPTED" : "ACCEPTED", mockEnabled ? "短信网关已模拟受理" : "短信网关已受理");
    }

    private NotificationSendResult sendByAliyun(String receiverRef,
                                                String payloadJson,
                                                Map<String, Object> context,
                                                PlatformNotifyGatewayResolvedConfig gatewayConfig) {
        Map<String, Object> config = gatewayConfig.getConfig();
        boolean mockEnabled = boolValue(config.get("mockEnabled"), false);
        if (mockEnabled) {
            String mockId = "ALIYUN_SMS-" + System.currentTimeMillis();
            return new NotificationSendResult(true, mockId, "MOCK_ACCEPTED", "阿里云短信已按模拟模式受理");
        }
        String endpoint = text(config.get("endpoint"));
        if (endpoint.isEmpty()) {
            endpoint = "https://dysmsapi.aliyuncs.com/";
        } else if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
            endpoint = "https://" + endpoint;
        }
        String accessKeyId = text(config.get("accessKeyId"));
        String accessKeySecret = text(config.get("accessKeySecret"));
        String signName = text(config.get("signName"));
        String templateCode = text(config.get("templateCode"));
        String regionId = textOr(config.get("regionId"), "cn-hangzhou");
        if (accessKeyId.isEmpty() || accessKeySecret.isEmpty()) {
            return new NotificationSendResult(false, null, "CONFIG_ERROR", "阿里云短信 AccessKey 配置不完整");
        }
        if (signName.isEmpty() || templateCode.isEmpty()) {
            return new NotificationSendResult(false, null, "CONFIG_ERROR", "阿里云短信签名或模板编码未配置");
        }
        try {
            Map<String, Object> payload = parsePayload(payloadJson);
            Map<String, Object> templateParams = buildAliyunTemplateParams(payload, context);
            String outId = resolveOutId(context);
            Map<String, String> params = new LinkedHashMap<String, String>();
            params.put("AccessKeyId", accessKeyId);
            params.put("Action", "SendSms");
            params.put("Format", "JSON");
            params.put("PhoneNumbers", receiverRef);
            params.put("RegionId", regionId);
            params.put("SignName", signName);
            params.put("SignatureMethod", "HMAC-SHA1");
            params.put("SignatureNonce", UUID.randomUUID().toString());
            params.put("SignatureVersion", "1.0");
            params.put("TemplateCode", templateCode);
            params.put("TemplateParam", objectMapper.writeValueAsString(templateParams));
            params.put("Timestamp", utcTimestamp());
            params.put("Version", "2017-05-25");
            if (!outId.isEmpty()) {
                params.put("OutId", outId);
            }
            String signature = signAliyunRpc(params, accessKeySecret);
            params.put("Signature", signature);
            String responseText = doGet(endpoint, params);
            Map<String, Object> responseMap = parsePayload(responseText);
            String code = text(responseMap.get("Code"));
            String message = text(responseMap.get("Message"));
            String bizId = text(responseMap.get("BizId"));
            if (!"OK".equalsIgnoreCase(code)) {
                return new NotificationSendResult(false, bizId.isEmpty() ? null : bizId, code.isEmpty() ? "SEND_FAILED" : code, message.isEmpty() ? "阿里云短信发送失败" : message);
            }
            return new NotificationSendResult(true, bizId.isEmpty() ? "ALIYUN_SMS-" + System.currentTimeMillis() : bizId, code, message.isEmpty() ? "阿里云短信发送成功" : message);
        } catch (Exception ex) {
            log.error("aliyun sms sending failed, receiver={}, err={}", receiverRef, ex.getMessage(), ex);
            return new NotificationSendResult(false, null, "SEND_FAILED", ex.getMessage());
        }
    }

    private Map<String, Object> parsePayload(String payloadJson) throws Exception {
        if (payloadJson == null || payloadJson.trim().isEmpty() || !payloadJson.trim().startsWith("{")) {
            return new LinkedHashMap<String, Object>();
        }
        Map<String, Object> payload = objectMapper.readValue(payloadJson, new TypeReference<Map<String, Object>>() {});
        return payload == null ? new LinkedHashMap<String, Object>() : new LinkedHashMap<String, Object>(payload);
    }

    private Map<String, Object> buildAliyunTemplateParams(Map<String, Object> payload, Map<String, Object> context) {
        Map<String, Object> params = new LinkedHashMap<String, Object>();
        if (payload != null) {
            params.putAll(payload);
        }
        if (context != null) {
            Object taskId = context.get("taskId");
            if (taskId != null) {
                params.put("taskId", String.valueOf(taskId));
            }
        }
        if (!params.containsKey("content") && params.containsKey("message")) {
            params.put("content", params.get("message"));
        }
        if (!params.containsKey("message") && params.containsKey("content")) {
            params.put("message", params.get("content"));
        }
        return params;
    }

    private String resolveOutId(Map<String, Object> context) {
        if (context == null) {
            return "";
        }
        Object outId = context.get("outId");
        if (outId != null && String.valueOf(outId).trim().length() > 0) {
            return String.valueOf(outId).trim();
        }
        Object taskId = context.get("taskId");
        if (taskId != null && String.valueOf(taskId).trim().length() > 0) {
            return "task-" + String.valueOf(taskId).trim();
        }
        return "";
    }

    private String utcTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "UTC"));
        return sdf.format(new Date());
    }

    private String signAliyunRpc(Map<String, String> params, String accessKeySecret) throws Exception {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        StringBuilder canonicalized = new StringBuilder();
        for (String key : keys) {
            if (canonicalized.length() > 0) {
                canonicalized.append("&");
            }
            canonicalized.append(percentEncode(key)).append("=").append(percentEncode(params.get(key)));
        }
        String stringToSign = "GET&%2F&" + percentEncode(canonicalized.toString());
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec((accessKeySecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
        return Base64.getEncoder().encodeToString(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
    }

    private String doGet(String endpoint, Map<String, String> params) throws Exception {
        StringBuilder query = new StringBuilder();
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        for (String key : keys) {
            if (query.length() > 0) {
                query.append("&");
            }
            query.append(URLEncoder.encode(key, "UTF-8")).append("=").append(URLEncoder.encode(text(params.get(key)), "UTF-8"));
        }
        String url = endpoint + (endpoint.contains("?") ? "&" : "?") + query;
        HttpURLConnection connection = (HttpURLConnection) new java.net.URL(url).openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(5000);
        connection.setReadTimeout(10000);
        connection.setDoInput(true);
        int code = connection.getResponseCode();
        InputStream stream = code >= 200 && code < 300 ? connection.getInputStream() : connection.getErrorStream();
        String response = readStream(stream);
        if (code < 200 || code >= 300) {
            throw new IllegalStateException("HTTP " + code + ": " + response);
        }
        return response;
    }

    private String readStream(InputStream stream) throws Exception {
        if (stream == null) {
            return "";
        }
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    }

    private String percentEncode(String value) throws Exception {
        return URLEncoder.encode(value == null ? "" : value, "UTF-8")
                .replace("+", "%20")
                .replace("*", "%2A")
                .replace("%7E", "~");
    }

    private boolean boolValue(Object value, boolean fallback) {
        if (value == null) {
            return fallback;
        }
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        String text = String.valueOf(value).trim();
        if ("true".equalsIgnoreCase(text) || "1".equals(text)) {
            return true;
        }
        if ("false".equalsIgnoreCase(text) || "0".equals(text)) {
            return false;
        }
        return fallback;
    }

    private String text(Object value) {
        return value == null ? "" : String.valueOf(value).trim();
    }

    private String textOr(Object value, String fallback) {
        String text = text(value);
        return text.isEmpty() ? fallback : text;
    }
}
