package cokr.xit.ens.modules.kkotalk.service.support;

import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;

import javax.validation.*;

import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;

import com.google.gson.*;

import cokr.xit.ens.core.aop.*;
import cokr.xit.ens.core.exception.*;
import cokr.xit.ens.core.exception.code.*;
import cokr.xit.ens.core.utils.*;
import cokr.xit.ens.core.utils.crypto.*;
import cokr.xit.ens.modules.common.biztmplt.*;
import cokr.xit.ens.modules.common.code.*;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.*;
import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.*;
import cokr.xit.ens.modules.common.ctgy.mblpage.domain.*;
import cokr.xit.ens.modules.common.ctgy.mblpage.domain.repository.*;
import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.*;
import cokr.xit.ens.modules.common.ctgy.sys.mng.service.*;
import cokr.xit.ens.modules.common.domain.*;
import cokr.xit.ens.modules.common.domain.repository.*;
import cokr.xit.ens.modules.kkomydoc.model.config.*;
import cokr.xit.ens.modules.kkotalk.code.*;
import cokr.xit.ens.modules.kkotalk.mapper.*;
import cokr.xit.ens.modules.kkotalk.model.*;
import lombok.*;
import lombok.extern.slf4j.*;

@Slf4j
@Component
@RequiredArgsConstructor
public class KkoTalkAcceptor implements EnsPhaseProcSupport<EnsResponseVO<?>, KkotalkDTO.KkoTalkAcceptReqDTO> {

    private final SendMastRepository sendMastRepository;
    private final IKkoTalkMapper kkoTalkMapper;
    private final KeySequenceService keySequenceService;
    private final SendDetailMblPageRepository sendDetailMblPageRepository;
    private final BillRepository billRepository;
    private final OrgMngService orgMngService;
    private final TmpltMngService tmpltMngService;

    private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create();

    @Override
    public EnsResponseVO<?> statReady(List<KkotalkDTO.KkoTalkAcceptReqDTO> reqDTOs) {
        log.info("no process");
        return null;
    }

    @Override
    public EnsResponseVO<?> statBegin(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) {
        final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<KkotalkDTO.KkoTalkAcceptReqDTO>> list = validator.validate(reqDTO);
        if (!list.isEmpty()) {
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT402)
                    .errMsg("유효하지 않은 요청값 입니다.")
                    .resultInfo(list.stream()
                            .map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath()))
                            .collect(Collectors.toList()))
                    .build();
        }
        List<String> expiredDateValidateList = expiredDateVaildate(reqDTO);
        if (!expiredDateValidateList.isEmpty()) {
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT402)
                    .errMsg("유효하지 않은 유효기한 입니다. 자료의 유효기한은 마감일시 이내이어야 합니다.")
                    .resultInfo(expiredDateValidateList)
                    .build();
        }
        EnsResponseVO<OrgMng> orgMng = orgMngService.find(reqDTO.getOrg_cd());
        if (!EnsErrCd.OK.equals(orgMng.getErrCode()))
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT404)
                    .errMsg("일치하는 기관정보가 없습니다.")
                    .resultInfo(orgMng.getErrMsg())
                    .build();
        EnsResponseVO<?> tmpltMng = tmpltMngService.find(reqDTO.getOrg_cd(), reqDTO.getTmplt_cd(), "kkotalk");
        if (!EnsErrCd.OK.equals(tmpltMng.getErrCode()))
            return EnsResponseVO.errRsltBuilder()
                    .errCode(EnsErrCd.ACPT404)
                    .errMsg("일치하는 템플릿정보가 없습니다.")
                    .resultInfo(tmpltMng.getErrMsg())
                    .build();

        return EnsResponseVO.okBuilder().build();
    }

    private List<String> expiredDateVaildate(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) {
        List<String> result = new ArrayList<>();

        AtomicInteger i = new AtomicInteger();
        reqDTO.getDocuments().forEach(document -> {
            if (!CmmnUtil.isEmpty(document)) {
                if (!CmmnUtil.isEmpty(document.getReadExpiresAt())) {
                    String expDate = DateUtil.getTimeOfTimeT(document.getReadExpiresAt(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                        result.add(String.format("마감일시보다 \"최초열람 마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.readExpiresAt ]", i.get()));
                }
                if (!CmmnUtil.isEmpty(document.getReviewExpiresAt())) {
                    String expDate = DateUtil.getTimeOfTimeT(document.getReviewExpiresAt(), "yyyyMMddHHmmss");
                    int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt()));
                    if (sec < 0)
                         result.add(String.format("마감일시보다 \"재열람 만료일시\"가 느립니다. [ document[%d].acpt_data.kko_talk.readExpiresAt ]", i.get()));
                }
            }
            i.getAndIncrement();
        });

        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public EnsResponseVO<?> execute(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) throws EnsException {
        AES256 aes256 = new AES256(Crypto.AES256.getKey());
        EnsResponseVO<?> responseVO = null;
        try {
            SendMast sendMast = SendMast.builder()
                    .vender(VenderCd.valueOfEnum(reqDTO.getVender()))
                    .postSe(PostSeCd.kkoTalk)
                    .orgCd(reqDTO.getOrg_cd())
                    .tmpltCd(reqDTO.getTmplt_cd())
                    .postBundleTitle(reqDTO.getPost_bundle_title())
                    .statCd(StatCd.accept)
                    .sendDt(DateUtil.toLocalDateTime(reqDTO.getSend_dt()))
                    .sendCnt(reqDTO.getDocuments().size())
                    .closeDt(DateUtil.toLocalDateTime(reqDTO.getClose_dt()))
                    .build();
            sendMastRepository.save(sendMast);

            Gson gson = new GsonBuilder().disableHtmlEscaping().create();
            List<SendDetailKkoTalkDTO> sendDetails = new ArrayList<>();
            List<Map<String, Object>> sendDetailMblPages = new ArrayList<>();
            List<Bill> bills = new ArrayList<>();
            final String prefixBillUid = sendMast.getPostSe().getCode() + "-" + IdGenerator.getCurrentTimeSec();
            AtomicInteger i = new AtomicInteger();
            reqDTO.getDocuments().forEach(document -> {
                i.getAndIncrement();
                try {

                    Bill bill = null;
                    if (!CmmnUtil.isEmpty(document.getXit_property())
                            && !CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data())) {
//                        if (document.getXit_property().getBill_acpt_data().getUse_bill_uid()) {
                        if (!CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBillUid())) {
                            bill = billRepository.findByBillUid(document.getXit_property().getBill_acpt_data().getBillUid())
                                    .orElseThrow(() -> new RuntimeException("등록된 청구서가 없습니다."));
                        } else {
                            bill = Bill.builder()
                                    .billId(null)
                                    .billUid(IdGenerator.getShortUUID(prefixBillUid))
                                    .billerUserKey(document.getXit_property().getBill_acpt_data().getBillerUserKey())
                                    .billSeCd(document.getXit_property().getBill_acpt_data().getBillSe())
                                    .orgMng(OrgMng.builder().orgCd(reqDTO.getOrg_cd()).build())
                                    .build();
                        }
                        bills.add(bill);
                    }


                    XitProperty xitProperty = Optional.ofNullable(document.getXit_property()).orElse(XitProperty.builder().build());


                    SendDetailKkoTalkDTO sendDetail = SendDetailKkoTalkDTO.builder()
                        .sendMastId(sendMast.getSendMastId())
                        .title(document.getTitle())
                        .link(document.getContent() != null? document.getContent().getLink(): null)
                        .hash(document.getHash())
                        .guide(document.getGuide())
                        .payload(document.getPayload())
                        .readExpiresAt(document.getReadExpiresAt())
                        .reviewExpiresAt(document.getReviewExpiresAt())
                        .useNonPersonalizedNotification(document.getUseNonPersonalizedNotification())
                        .ci(document.getCi())
                        .phoneNumber(document.getPhoneNumber())
                        .name(document.getName())
                        .birthday(document.getBirthday())
                        .externalId(document.getExternalId())
                        .mkBillUseYn(CmmnUtil.isEmpty(bill) ? "N" : "Y")
                        .billUid(Objects.requireNonNull(bill).getBillUid())
                        .mkJid(aes256.encrypt(xitProperty.getJid()))
                        .mkTmpltMsgJsonData(CmmnUtil.isEmpty(xitProperty.getTmplt_msg_data()) ? null : gson.toJson(xitProperty.getTmplt_msg_data()))
                        .status(KkoTalkStatusCd.NULL)
                        .build();
                    sendDetails.add(sendDetail);

                    if (!(CmmnUtil.isEmpty(document.getXit_property()) || CmmnUtil.isEmpty(document.getXit_property().getMbl_page_data()))) {
                        Map<String, Object> mMblPage = new HashMap<>();
                        mMblPage.put("sendDetail", sendDetail);
                        mMblPage.put("details", gson.toJson(document.getXit_property().getMbl_page_data()));
                        sendDetailMblPages.add(mMblPage);
                    }

                } catch (Exception e) {
                    throw new EnsException(EnsErrCd.ACPT500, String.format("%d번째 데이터 접수처리 중 오류 발생. 실패사유: %s", i.get(), e.getMessage()));
                }
            });
            if (CmmnUtil.isEmpty(sendDetails))
                throw new EnsException(EnsErrCd.ACPT410, "발송상세 자료가 없습니다.");


            billRepository.saveAll(bills.stream()
                    .filter(row -> CmmnUtil.isEmpty(row.getBillId()))
                    .collect(Collectors.toList())
            );

            // FIXME: 발송상세 저장을 위해 추가
            sendMastRepository.flush();
            billRepository.flush();
            // kkotalk 발송상세 저장
            sendDetails.forEach(d -> {
                d.setSendDetailId(keySequenceService.getKeySequence("SendDetailKkoTalk_id"));
                kkoTalkMapper.saveSndDtlKkoTalk(d);
            });

            if (!sendDetailMblPages.isEmpty())

                sendDetailMblPageRepository.saveAll(
                    sendDetailMblPages.stream()
                        .map(data -> {
                            SendDetailKkoTalkDTO sendDetail = (SendDetailKkoTalkDTO) data.get("sendDetail");
                            return SendDetailMblPage.builder()
                                    .postSe(PostSeCd.kkoTalk.getCode())
                                    .sendDetailId(sendDetail.getSendDetailId())
                                    .details((String) data.get("details"))
                                    .build();
                        })
                        .collect(Collectors.toList())
                );


            Map<String, Object> resultInfo = new HashMap<>();
            resultInfo.put("sendMastId", sendMast.getSendMastId());
            resultInfo.put("sendDetails", sendDetails);
            responseVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build();
        } catch (EnsException e) {
            throw e;
        } catch (Exception e) {
            throw new EnsException(EnsErrCd.ACPT500, String.format("오류가 발생하여 접수에 실패 했습니다. 실패사유: %s", e.getMessage()));
        }

        return responseVO;
    }


}
