package com.eactive.ext.kjb.ums;

import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.entity.MessageRequest;
import com.eactive.ext.kjb.common.KjbProperty;
import com.eactive.ext.kjb.common.KjbPropertyHolder;
import com.eactive.ext.kjb.common.KjbUmsException;
import com.eactive.ext.kjb.ums.gson.*;
import com.eactive.ext.kjb.utils.JsonUtil;
import com.eactive.ext.kjb.utils.ValidationUtil;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.Timeout;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.concurrent.*;

@Slf4j
public class KjbUmsService {
    public final static int[] ALLOW_CHANNELS = { MessageCode.Channels.KAKAO_ALIMTALK.getValue() };

    private final static String SEND_URL = "/ums/openapi/send";
    private final static String CONTENT_TYPE = "application/json; charset=utf-8";
    private final static String AUTHORIZATION_FMT = "Bearer %s";

    private static volatile KjbUmsService instance;

    private KjbUmsService() {
        // 싱글톤
    }

    public static KjbUmsService getInstance() {
        if (instance == null) {
            synchronized (KjbUmsService.class) {
                if (instance == null) {
                    instance = new KjbUmsService();
                }
            }
        }
        return instance;
    }

    private KjbProperty getProperty() {
        return KjbPropertyHolder.get();
    }

    private boolean isRealMode() {
        String url = getProperty().getUmsHostUrl();
        return url != null && StringUtils.isNotEmpty(url.trim());
    }

    private CloseableHttpClient createHttpClient() {
        return HttpClients.createDefault();
    }

    private RequestConfig createRequestConfig() {
        KjbProperty prop = getProperty();
        return RequestConfig.custom()
                .setConnectTimeout(Timeout.ofSeconds(prop.getUmsConnectionTimeout()))
                .setResponseTimeout(Timeout.ofSeconds(prop.getUmsResponseTimeout()))
                .build();
    }


    public static boolean isAllowChannel(MessageRequest message) {
        for (int channel : ALLOW_CHANNELS) {
            if ((message.getMessageCode().getChannels() & channel) == channel) {
                return true;
            }
        }
        return false;
    }


    public boolean send(MessageRequest messageRequest) throws KjbUmsException {
        if (messageRequest == null) {
            throw new IllegalArgumentException("messageRequest is null");
        }

        if (MessageCode.Channels.KAKAO_ALIMTALK.getValue() != (messageRequest.getMessageCode().getChannels() & MessageCode.Channels.KAKAO_ALIMTALK.getValue())) {
            log.error("지원하지 않는 발송 채널 - {}", messageRequest.getMessageCode().toString());
            throw new IllegalArgumentException("지원하지 않는 발송 채널 - " + messageRequest.getMessageCode().toString());
        }

        String umsMessageId = this.sendKakaoAlimTalk(messageRequest.getId(),
                UmsBizWorkCode.VERIFY_PHONE,
                messageRequest.getPhone(),
                GsonUtil.toJsonObject(messageRequest.getMessage())
                );

        log.info("MessageRequest sent - {}", umsMessageId);

        return true;
    }

    public String sendObpMonitoring(String cpno, String message) throws KjbUmsException {
        log.info("OBP 모니터링 용 메세지 발송 - message: {}", message);

        return this.sendKakaoAlimTalk(null, UmsBizWorkCode.MONITORING, cpno, GsonUtil.toJsonObject(ObpMonitoringUmsTemplate.builder().message(message).build()));
    }

    public String sendKakaoAlimTalk(String guid, UmsBizWorkCode bizWorkCode, String cpno, JsonObject content) throws KjbUmsException {
        if (content == null) {
            throw  new IllegalArgumentException("content is null");
        }

        if (StringUtils.isEmpty(guid)) {
            guid = String.format("APM-%s", DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
        }

        UmsRequestPayload payload = UmsRequestPayload.builder()
                .messageIdentityNo(guid)
                .msgBizWorkCode(bizWorkCode.getCode())
                .content(content)
                .cellphone(cpno)
                .msgSndChnlCd(UmsRequestPayload.ChannelCode.KM)
                .build();

        try {
            ValidationUtil.validateOrThrow(payload);
        } catch (Exception ex) {
            log.debug("UMS Request payload validation failed - {}", JsonUtil.toPrettyString(payload));
            throw ex;
        }

        UmsCallResult result = call(SEND_URL, payload);
        if (!result.isSuccess()) {
            throw new KjbUmsException(result.getThrowable().getMessage());
        }

        return result.getUmsMessageId();
    }

    private UmsCallResult call(String url, UmsRequestPayload requestPayload) throws KjbUmsException {
        KjbProperty prop = getProperty();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Gson gson = new Gson();
        String json = gson.toJson(requestPayload);

        HttpPost httpPost = new HttpPost(prop.getUmsHostUrl() + url);
        httpPost.setHeader("Content-Type", CONTENT_TYPE);
        httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, prop.getUmsApiKey()));
        httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build());

        if (log.isDebugEnabled()) {
            try {
                log.debug("Header - {}", httpPost.getHeader("Content-Type"));
                log.debug("Header - {}", httpPost.getHeader("Authorization"));
                log.debug("Request Body : {}", StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset()));
            } catch (ProtocolException | IOException e) {
                throw new RuntimeException(e);
            }
        }

        httpPost.setConfig(createRequestConfig());

        if (isRealMode()) {
            try (CloseableHttpClient httpClient = createHttpClient()) {
                Future<UmsCallResult> future = executor.submit(() -> {
                    return httpClient.execute(httpPost, res -> {
                        try {
                            UmsCallResult result;
                            String body = EntityUtils.toString(res.getEntity());
                            if (log.isDebugEnabled()) {
                                log.debug("Response - {}", body);
                            }
                            result = gson.fromJson(body, UmsCallResult.class);
                            result.setUmsMessageId(requestPayload.getMessageIdentityNo());
                            result.setSuccess(true);
                            return result;
                        } catch (JsonSyntaxException e) {
                            log.error("UMS Call error(JSON Syntax)", e);
                            return UmsCallResult.builder()
                                    .umsMessageId(requestPayload.getMessageIdentityNo())
                                    .isSuccess(false)
                                    .throwable(e)
                                    .build();
                        } catch (Exception e) {
                            log.error("UMS Call error", e);
                            return UmsCallResult.builder()
                                    .umsMessageId(requestPayload.getMessageIdentityNo())
                                    .isSuccess(false)
                                    .throwable(e)
                                    .build();
                        } finally {
                            log.debug("Response status - {}", res.getCode());

                            if (res.getCode() != 200) {
                                Arrays.asList(res.getHeaders()).forEach(o -> {
                                    log.warn("[Header] {}: {}", o.getName(), o.getValue());
                                });
                            }
                        }
                    });
                });

                return future.get(prop.getUmsTimeout(), TimeUnit.SECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException | IOException e) {
                log.error("UMS call exception", e);
                throw new KjbUmsException("UMS call failed", e);
            } finally {
                executor.shutdown();
            }
        } else {
            log.warn("UMS URL 미지정으로 실제 호출을 진행하지 않음.");
            return UmsCallResult.builder().isSuccess(true).build();
        }
    }
}
