package com.ga.external.message;

import com.fasterxml.jackson.databind.JsonNode;
import com.ga.common.adapter.message.MessageAdapter;
import com.ga.common.adapter.message.MessageChannel;
import com.ga.common.adapter.message.MessageRequest;
import com.ga.common.adapter.message.MessageResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

/**
 * NHN Cloud(Toast) SMS / 카카오 알림톡 어댑터 — 실구현.
 * <p>
 * 활성 조건: <code>app.adapter.message=toast</code> (기본은 mock). 키는 {@link ToastProperties} 로 외부화.
 * <ul>
 *   <li>{@link MessageChannel#SMS}   → SMS v3.0 단문 발송 API</li>
 *   <li>{@link MessageChannel#KAKAO} → 알림톡 v2.3 발송 API (templateCode + 파라미터)</li>
 * </ul>
 * 키가 비어 있으면 fail-fast 한다(잘못된 운영 적용 즉시 감지). 외부호출 감사로그는 ga-api 의 AOP가 남긴다.
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "app.adapter", name = "message", havingValue = "toast")
@EnableConfigurationProperties(ToastProperties.class)
public class ToastMessageAdapter implements MessageAdapter {

    private final ToastProperties props;
    private final RestClient smsClient;
    private final RestClient alimtalkClient;

    public ToastMessageAdapter(ToastProperties props) {
        this.props = props;
        this.smsClient = RestClient.builder().baseUrl(props.getSmsBaseUrl()).build();
        this.alimtalkClient = RestClient.builder().baseUrl(props.getAlimtalkBaseUrl()).build();
    }

    @Override
    public MessageResponse send(MessageRequest request) {
        MessageChannel channel = request.getChannel() == null ? MessageChannel.SMS : request.getChannel();
        return channel == MessageChannel.KAKAO ? sendAlimtalk(request) : sendSms(request);
    }

    // ── SMS ───────────────────────────────────────────────────────────
    private MessageResponse sendSms(MessageRequest req) {
        require(props.getSmsAppKey(), "app.adapter.toast.sms-app-key");
        require(props.getSmsSecretKey(), "app.adapter.toast.sms-secret-key");
        require(props.getSenderNo(), "app.adapter.toast.sender-no");

        Map<String, Object> body = Map.of(
                "body", req.getContent(),
                "sendNo", props.getSenderNo(),
                "recipientList", List.of(Map.of("recipientNo", normalize(req.getPhoneNumber()))));

        try {
            JsonNode res = smsClient.post()
                    .uri("/sms/v3.0/appKeys/{appKey}/sender/sms", props.getSmsAppKey())
                    .header("X-Secret-Key", props.getSmsSecretKey())
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(body)
                    .retrieve()
                    .body(JsonNode.class);
            return toResponse(res, "body");
        } catch (RestClientException e) {
            log.warn("[TOAST SMS] 전송 실패 phone={} err={}", mask(req.getPhoneNumber()), e.getMessage());
            return fail(e.getMessage());
        }
    }

    // ── 카카오 알림톡 ───────────────────────────────────────────────────
    private MessageResponse sendAlimtalk(MessageRequest req) {
        require(props.getAlimtalkAppKey(), "app.adapter.toast.alimtalk-app-key");
        require(props.getAlimtalkSecretKey(), "app.adapter.toast.alimtalk-secret-key");
        require(props.getSenderKey(), "app.adapter.toast.sender-key");
        if (!StringUtils.hasText(req.getTemplateCode())) {
            throw new IllegalArgumentException("알림톡 발송에는 templateCode 가 필요합니다.");
        }

        Map<String, Object> recipient = Map.of(
                "recipientNo", normalize(req.getPhoneNumber()),
                "templateParameter", Map.of(
                        "title", req.getTitle() == null ? "" : req.getTitle(),
                        "content", req.getContent() == null ? "" : req.getContent()));
        Map<String, Object> body = Map.of(
                "senderKey", props.getSenderKey(),
                "templateCode", req.getTemplateCode(),
                "recipientList", List.of(recipient));

        try {
            JsonNode res = alimtalkClient.post()
                    .uri("/alimtalk/v2.3/appkeys/{appkey}/messages", props.getAlimtalkAppKey())
                    .header("X-Secret-Key", props.getAlimtalkSecretKey())
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(body)
                    .retrieve()
                    .body(JsonNode.class);
            return toResponse(res, "message");
        } catch (RestClientException e) {
            log.warn("[TOAST ALIMTALK] 전송 실패 phone={} template={} err={}",
                    mask(req.getPhoneNumber()), req.getTemplateCode(), e.getMessage());
            return fail(e.getMessage());
        }
    }

    /**
     * Toast 공통 응답(header.isSuccessful/resultCode/resultMessage) 파싱.
     * SMS 는 body.data.requestId, 알림톡은 message.requestId 로 발급 id 위치가 다르다.
     */
    private MessageResponse toResponse(JsonNode res, String dataRoot) {
        boolean ok = res != null && res.path("header").path("isSuccessful").asBoolean(false);
        int code = res == null ? -1 : res.path("header").path("resultCode").asInt(-1);
        String msg = res == null ? "no response" : res.path("header").path("resultMessage").asText("");
        String messageId = "";
        if (res != null) {
            JsonNode root = res.path(dataRoot);
            messageId = root.path("data").path("requestId").asText(
                    root.path("requestId").asText(""));
        }
        return MessageResponse.builder()
                .messageId(messageId)
                .sent(ok)
                .resultCode(String.valueOf(code))
                .resultMessage(msg)
                .sentAt(LocalDateTime.now())
                .build();
    }

    private MessageResponse fail(String message) {
        return MessageResponse.builder()
                .sent(false).resultCode("ERROR").resultMessage(message)
                .sentAt(LocalDateTime.now()).build();
    }

    private void require(String value, String key) {
        if (!StringUtils.hasText(value)) {
            throw new IllegalStateException("Toast 설정 누락: " + key + " 가 필요합니다.");
        }
    }

    /** 010-1234-5678 → 01012345678 (Toast 는 하이픈 없는 번호 권장). */
    private String normalize(String phone) {
        return phone == null ? null : phone.replaceAll("[^0-9]", "");
    }

    private String mask(String phone) {
        String d = normalize(phone);
        if (d == null || d.length() <= 4) return "****";
        return d.substring(0, d.length() - 4) + "****";
    }
}
