package com.ktds.act.admin.hndlrmngt.service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import javax.transaction.Transactional;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestBody;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ktds.act.admin.common.config.hndlr.HndlrBlackList;
import com.ktds.act.admin.common.handler.AdminRuntimeException;
import com.ktds.act.admin.common.handler.model.TestDto;
import com.ktds.act.admin.common.response.Pagination;
import com.ktds.act.admin.common.response.Response;
import com.ktds.act.admin.common.response.StatusEnum;
import com.ktds.act.admin.common.util.UserAuthUtil;
import com.ktds.act.admin.domain.dto.AdmHndlrDto;
import com.ktds.act.admin.domain.dto.AdmTmpltDto;
import com.ktds.act.admin.domain.dto.AdmUtilDto;
import com.ktds.act.admin.domain.entity.AdmApiDply;
import com.ktds.act.admin.domain.entity.AdmHndlr;
import com.ktds.act.admin.domain.enums.DplyType;
import com.ktds.act.admin.domain.repository.AdmApiDplyTrRepository;
import com.ktds.act.admin.domain.repository.AdmHndlrDplyTrRepository;
import com.ktds.act.admin.domain.repository.AdmHndlrRepository;
import com.ktds.act.admin.domain.repository.AdmTmpltRepository;
import com.ktds.act.admin.domain.repository.AdmUtilRepository;
import com.ktds.act.admin.hndlrmngt.db.model.ApiIdAndSysIdDto;
import com.ktds.act.admin.hndlrmngt.db.model.ApiListDto;
import com.ktds.act.admin.hndlrmngt.db.model.DuplicatedCheckDto;
import com.ktds.act.admin.hndlrmngt.db.model.HndlrMngtQueryParameter;
import com.ktds.act.admin.hndlrmngt.db.model.Transaction;
import com.ktds.act.admin.hndlrmngt.db.model.UrlDto;
import com.ktds.act.apigw.common.header.Header;
import com.ktds.act.apigw.common.util.CompilerUtils;
import com.ktds.act.apigw.filter.handler.RouteHandler;
import com.ktds.act.apigw.filter.model.ComnonDto;
import com.ktds.act.apigw.filter.model.RequestDto;
import com.ktds.act.apigw.filter.model.ResponseDto;
import com.ktds.act.apigw.filter.model.StoreDto;
import com.ktds.act.apigw.filter.model.TransactionDto;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class HndlrMngtService {

    @Value("${nexus.url}")
    private String domainUrl;

    @Value("${nexus.basic}")
    private String basicKey;

    private final AdmApiDplyTrRepository admApiDplyTrRepository;
    private final AdmHndlrRepository admHndlrRepository;
    private final AdmHndlrDplyTrRepository admHndlrDplyRepository;
    private final AdmTmpltRepository admTmpltRepository;
    private final AdmUtilRepository admUtilRepository;

    private final ApplicationContext applicationContext;
    private final UserAuthUtil userAuthUtil;

    // 핸들러 임시저장 및 등록
    public Response<Object> createHndlr(AdmHndlrDto admHndlrDto, HttpServletRequest request) {

        String cstmClassAdr = "";

        if (!admHndlrDto.getCstmClassSrc().isEmpty() || admHndlrDto.getCstmClassSrc() != "") {
            String[] reg = admHndlrDto.getCstmClassSrc().split(";");
            String[] adr = reg[0].split("package ");
            cstmClassAdr = adr[1] + "." + admHndlrDto.getCstmClassNm();
        }
        admHndlrDto.setCstmClassAdr(cstmClassAdr);
        admHndlrDto.setTbReqDply("N");
        admHndlrDto.setProdReqDply("N");

        AdmHndlr entity = admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId()).orElseGet(AdmHndlr::new);

        if (entity.getId() == null) {
            if (admHndlrRepository.existsByHndlrId(entity.getHndlrId())) {
                throw new AdminRuntimeException(StatusEnum.DUPLICATED_HNDLR_ID);
            }
            entity = AdmHndlr.to(admHndlrDto);
        } else {
            entity = admHndlrRepository.findById(entity.getId()).get();
        }

        // db에 값이 있을 때만 update
        if (entity.getId() != null) {
            BeanUtils.copyProperties(admHndlrDto, entity, "id", "hndlrId", "cretId", "cretDt");
        }

        entity.setProdDplyUrl("");
        entity.setTbDplyUrl("");

        var result = admHndlrRepository.save(entity);
        return new Response<>().responseOk(result);
    }

    // 핸들러 전체조회 및 검색
    public Response<List<AdmHndlrDto>> getHndlrList(Pageable pageable,
            HndlrMngtQueryParameter hndlrMngtQueryParameter, HttpServletRequest request) {
    
        if (hndlrMngtQueryParameter.getHndlrId() != null || hndlrMngtQueryParameter.getTrtSect() != null
                || hndlrMngtQueryParameter.getTbReqDply() != null || hndlrMngtQueryParameter.getProdReqDply() != null) {

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(),
                                pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTrtSectIgnoreCaseContainingOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getTrtSect(),
                                pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTbReqDplyOrderByUpdDtDesc(hndlrMngtQueryParameter.getTbReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByProdReqDplyOrderByUpdDtDesc(hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTrtSectOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(),
                                hndlrMngtQueryParameter.getTrtSect(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTbReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(),
                                hndlrMngtQueryParameter.getTbReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTrtSectIgnoreCaseContainingAndTbReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getTrtSect(),
                                hndlrMngtQueryParameter.getTbReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTrtSectIgnoreCaseContainingAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getTrtSect(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTbReqDplyIgnoreCaseContainingAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getTbReqDply(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            //3개조건
            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() == null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTrtSectAndTbReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(), hndlrMngtQueryParameter.getTrtSect(),
                                hndlrMngtQueryParameter.getTbReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }
            
            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() == null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTrtSectAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(), hndlrMngtQueryParameter.getTrtSect(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }                

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() == null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTbReqDplyAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(), hndlrMngtQueryParameter.getTbReqDply(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            } 

            if (hndlrMngtQueryParameter.getHndlrId() == null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByTrtSectIgnoreCaseContainingAndTbReqDplyAndProdReqDplyOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getTrtSect(), hndlrMngtQueryParameter.getTbReqDply(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            } 

            if (hndlrMngtQueryParameter.getHndlrId() != null && hndlrMngtQueryParameter.getTrtSect() != null
                    && hndlrMngtQueryParameter.getTbReqDply() != null && hndlrMngtQueryParameter.getProdReqDply() != null) {
                var pageAdmHndlr = (Page) admHndlrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTbReqDplyAndProdReqDplyAndTrtSectOrderByUpdDtDesc(
                                hndlrMngtQueryParameter.getHndlrId(), hndlrMngtQueryParameter.getTrtSect(), hndlrMngtQueryParameter.getTbReqDply(),
                                hndlrMngtQueryParameter.getProdReqDply(), pageable);
                return new Response<List<AdmHndlrDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            } 

        }

        var result = (Page) admHndlrRepository.findByOrderByUpdDtDescHndlrIdAsc(pageable);
        return new Response<List<AdmHndlrDto>>().responseOk(result.getContent(),
                new Pagination().complete(result, pageable));
    }

    

    // 핸들러 상세조회
    public Response<AdmHndlrDto> getHndlrById(String hndlrId, HttpServletRequest request) {

        return admHndlrRepository.findByHndlrId(hndlrId).map(entity -> {

            var responseDto = AdmHndlrDto.of(entity);
            return new Response<AdmHndlrDto>().responseOk(responseDto);
        }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
    
    }

    // 핸들러 수정
    @Transactional
    public Response<Object> updateHndlr(AdmHndlrDto admHndlrDto, HttpServletRequest request) {

        return admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId()).map(entity -> {

            String hndlrId = entity.getHndlrId();
            String trtSect = entity.getTrtSect();

            String cstmClassAdr = "";

            if (!admHndlrDto.getCstmClassSrc().isEmpty() || admHndlrDto.getCstmClassSrc() != "") {
                String[] reg = admHndlrDto.getCstmClassSrc().split(";");
                String[] adr = reg[0].split("package ");
                cstmClassAdr = adr[1] + "." + admHndlrDto.getCstmClassNm();
            }

            admHndlrDto.setCstmClassAdr(cstmClassAdr);
            admHndlrDto.setTbReqDply("N");
            admHndlrDto.setProdReqDply("N");
            BeanUtils.copyProperties(admHndlrDto, entity, "id", "hndlrId", "cretId", "cretDt", "url", "encodedUrl");

            admHndlrRepository.save(entity);

            return new Response<>().responseOk(StatusEnum.SUCCESS);
        }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
        
    }

    // 핸들러 삭제
    @Transactional
    public Response<Object> deleteHndlr(String hndlrId, HttpServletRequest request) {

        return admHndlrRepository.findByHndlrId(hndlrId).map(entity -> {

            String objectId = entity.getId();
            String trtSect = entity.getTrtSect();

            Boolean apiBool = false;

            if (trtSect.equals("REQ")) {
                apiBool = admApiDplyTrRepository.existsByReqHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            } else if (trtSect.equals("RES")) {
                apiBool = admApiDplyTrRepository.existsByResHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            } else if (trtSect.equals("ERR")) {
                apiBool = admApiDplyTrRepository.existsByErrHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            }

            admHndlrRepository.deleteById(objectId);

            return new Response<>().responseOk(StatusEnum.SUCCESS);
        }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
    
    }

    // 핸들러 ID 중복체크
    public Response<DuplicatedCheckDto> hndlrCheckById(String hndlrId) {
        boolean isExisted = false;
        isExisted = admHndlrRepository.existsByHndlrId(hndlrId);
        var responseDto = new DuplicatedCheckDto().setIsPkDuplicated(isExisted);
        return new Response<DuplicatedCheckDto>().responseOk(responseDto);
    }

    // 핸들러 리스트 조회
    // public Response<Map<String, List<AdmHndlrListDto>>> getHndlrIdList(String trtSect, HttpServletRequest request) {

    //     var autId = userAuthUtil.getAutId(request);

    //     if (userAuthUtil.isAdmr(request)) {
    //         var hndlrList = this.admHndlrRepository.findByTrtSectOrderByOrderAsc(trtSect).stream()
    //                 .filter(s -> s.getSttus().equals("CMPLT")).map(entity -> {
    //                     var hndlrDto = AdmHndlrDto.of(entity);
    //                     var admHndlrListDto = AdmHndlrListDto.builder()
    //                             .admHndlrDto(hndlrDto)
    //                             .type(hndlrDto.getType())
    //                             .build();
    //                     return admHndlrListDto;
    //                 }).collect(Collectors.groupingBy(AdmHndlrListDto::getType,
    //                         Collectors.mapping(AdmHndlrListDto::getAdmHndlrDto, Collectors.toList())));
    //         return new Response().responseOk(hndlrList);
    //     } else { // 관리자 아닐때
    //         var hndlrList = this.admHndlrRepository.findByAutIdAndTrtSectOrderByOrderAsc(autId, trtSect).stream()
    //                 .filter(s -> s.getSttus().equals("CMPLT")).map(entity -> {
    //                     var hndlrDto = AdmHndlrDto.of(entity);
    //                     var admHndlrListDto = AdmHndlrListDto.builder()
    //                             .admHndlrDto(hndlrDto)
    //                             .type(hndlrDto.getType())
    //                             .build();
    //                     return admHndlrListDto;
    //                 }).collect(Collectors.groupingBy(AdmHndlrListDto::getType,
    //                         Collectors.mapping(AdmHndlrListDto::getAdmHndlrDto, Collectors.toList())));
    //         return new Response().responseOk(hndlrList);
    //     }
    // }

    // 템플릿 소스 오류체크 컴파일
    public Response<Object> errorCheck(AdmHndlrDto admHndlrDto) {

        try {
            registerTemplateCode(admHndlrDto);
        } catch (AdminRuntimeException e) {
            log.error("invalid source");
            return new Response<>().serverError(StatusEnum.INVALID_SOURCE);
        } catch (Exception e) {
            DuplicatedCheckBean(admHndlrDto);
            log.error("errorCheck error : {}", e.getMessage());
            return new Response<>().serverError(9000, e.getMessage());
        }
        DuplicatedCheckBean(admHndlrDto);
        return new Response<>().responseOk();
    }

    private void registerTemplateCode(AdmHndlrDto admHndlrDto) throws ClassNotFoundException, IOException {

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        String compileSource = admHndlrDto.getCstmClassSrc();

        HndlrBlackList.getSourceBlackList().stream().forEach(e -> {
            if (admHndlrDto.getCstmClassSrc().contains(e)) {
                throw new AdminRuntimeException(StatusEnum.INVALID_SOURCE);
            }
        });

        String cstmClassAdr = "";

        if (!admHndlrDto.getCstmClassSrc().isEmpty() || admHndlrDto.getCstmClassSrc() != "") {
            String[] reg = admHndlrDto.getCstmClassSrc().split(";");
            String[] adr = reg[0].split("package ");
            cstmClassAdr = adr[1] + "." + admHndlrDto.getCstmClassNm();
        }
        admHndlrDto.setCstmClassAdr(cstmClassAdr);
        System.out.println(cstmClassAdr);

        // compileSource = compileSource.replaceFirst("com.ktds.act.apigw.filter.handler.custom;",
        //         "com.ktds.act.admin.common.handler;");
        Class<?> cls = CompilerUtils.compileSource(cstmClassAdr, compileSource);

        defaultListableBeanFactory.registerBeanDefinition(admHndlrDto.getHndlrId(),
                BeanDefinitionBuilder.genericBeanDefinition(cls).getRawBeanDefinition());
    }

    private void registerTemplateCode(TestDto testDto) throws ClassNotFoundException, IOException {

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        String compileSource = testDto.getCstmClassSrc();
        HndlrBlackList.getSourceBlackList().stream().forEach(e -> {
            if (testDto.getCstmClassSrc().contains(e)) {
                throw new AdminRuntimeException(StatusEnum.INVALID_SOURCE);
            }
        });

        compileSource = compileSource.replaceFirst("com.ktds.act.apigw.filter.handler.custom;",
                "com.ktds.act.admin.common.handler;");
        Class<?> cls = CompilerUtils.compileSource("com.ktds.act.admin.common.handler." + testDto.getCstmClassNm(),
                compileSource);

        defaultListableBeanFactory.registerBeanDefinition(testDto.getHndlrId(),
                BeanDefinitionBuilder.genericBeanDefinition(cls).getRawBeanDefinition());
    }

    // 템플릿 소스 테스트
    public Response<Object> templateTest(TestDto testDto, HttpServletRequest request)
            throws UnsupportedEncodingException {

        TransactionDto transactionDto = TransactionDto.builder()
                .comn(ComnonDto.builder()
                        .build())
                .req(RequestDto.builder()
                        .header(new HttpHeaders())
                        .build())
                .res(ResponseDto.builder()
                        .header(new HttpHeaders())
                        .build())
                .store(StoreDto.builder()
                        .build())
                .build();

        try {
            registerTemplateCode(testDto);
        } catch (AdminRuntimeException e) {
            log.error("invalid source");
            return new Response<>().serverError(StatusEnum.INVALID_SOURCE);
        } catch (Exception e) {
            DuplicatedCheckBean(testDto);
            log.error("templateTest register bean error : {}", e.getMessage());
            return new Response<>().serverError(StatusEnum.CODE_COMPILE_ERROR);
        }

        // 헤더 설정
        setRequestHeader(testDto, transactionDto);

        // req, res, err 핸들러 바디 설정
        setTransaction(testDto, request, transactionDto);

        boolean timeOut = false;
        Transaction transaction = new Transaction();

        try {
            RouteHandler routeHandler = (RouteHandler) applicationContext.getBean(testDto.getHndlrId());

            Loop loop = new Loop(transactionDto, routeHandler);

            long start = System.currentTimeMillis();
            long end;
            loop.start();

            while(true){
                end = System.currentTimeMillis();
                if(end-start>=10000){
                    timeOut = true;
                    break;
                }

                if(loop.isAlive() == false){
                    break;
                }

            }

            if(timeOut){
                loop.stop();
                return new Response<>().serverError(StatusEnum.TIME_OUT);
            }

            boolean testResult = loop.getTestResult();

            if (!testResult) {
                return new Response<>().serverError(StatusEnum.CODE_TEST_FAIL);
            }
            setTestResult(transaction, testDto, transactionDto);

        } catch (Exception e) {
            DuplicatedCheckBean(testDto);
            log.error("templateTest error : {}", e.getMessage());
            return new Response<>().serverError(9001, e.getMessage());
        }
        DuplicatedCheckBean(testDto);
        return new Response<>().responseOk(transaction);
    }

    // 중복된 빈 제거
    private void DuplicatedCheckBean(TestDto testDto) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        if (defaultListableBeanFactory.containsBeanDefinition(testDto.getHndlrId())) {
            defaultListableBeanFactory.removeBeanDefinition(testDto.getHndlrId());
        }
    }

    // 중복된 빈 제거
    private void DuplicatedCheckBean(AdmHndlrDto admHndlrDto) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        if (defaultListableBeanFactory.containsBeanDefinition(admHndlrDto.getHndlrId())) {
            defaultListableBeanFactory.removeBeanDefinition(admHndlrDto.getHndlrId());
        }
    }

    private MultiValueMap<String, String> ListToMultiValueMap(TestDto testDto) {
        MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();

        if (testDto.getQueryParam() != null && !testDto.getQueryParam().isEmpty()) {
            for (Map<String, String> data : testDto.getQueryParam()) {
                for (String key : data.keySet()) {
                    multiValueMap.set(key, data.get(key));
                }
            }
        }

        return multiValueMap;
    }

    private Map<String, String> MultiValueMapToList(TestDto testDto, TransactionDto transactionDto) {
        Map<String, String> header = new HashMap<>();

        if (testDto.getTrtSect().equals("REQ")) {
            header = transactionDto.getReq().getHeader().toSingleValueMap();
        } else {
            header = transactionDto.getRes().getHeader().toSingleValueMap();
        }

        return header;
    }

    private void setRequestHeader(TestDto testDto, TransactionDto transactionDto) {
        HttpHeaders header = new HttpHeaders();

        if (testDto.getHeader() != null && !testDto.getHeader().isEmpty()) {
            for (Map<String, String> data : testDto.getHeader()) {
                for (String key : data.keySet()) {
                    header.add(key, (String) data.get(key));
                    if (testDto.getTrtSect().equals("REQ")) {
                        transactionDto.setReqHeader(header);
                    } else {
                        transactionDto.setResHeader(header);
                    }
                }
            }
        }
    }

    // 템플릿 리스트 조회
    public Response<List<AdmTmpltDto>> getTmpltList(String trtSect) {

        var responseDto = (List) this.admTmpltRepository.findAllByTrtSect(trtSect).stream().map(entity -> {
            return AdmTmpltDto.of(entity);
        }).collect(Collectors.toList());
        return new Response().responseOk(responseDto);
    }

    // 템플릿 클래스 중복 조회
    public Response<DuplicatedCheckDto> checkClassNm(String classNm) {
        var isDuplicated = admHndlrRepository.existsByCstmClassNm(classNm);
        var responseDto = new DuplicatedCheckDto().setIsPkDuplicated(isDuplicated);
        return new Response<DuplicatedCheckDto>().responseOk(responseDto);
    }

    // 유틸 리스트 조회
    public Response<List<AdmUtilDto>> getUtilList() {

        var responseDto = (List) this.admUtilRepository.findAll().stream().map(entity -> {
            return AdmUtilDto.of(entity);
        }).collect(Collectors.toList());
        return new Response().responseOk(responseDto);
    }

    private String getHeaderF(HttpHeaders header, String key) {
        if (header.containsKey(key)) {
            return header.getFirst(key);
        }
        return "";
    }

    private void setTransaction(TestDto testDto, HttpServletRequest request, TransactionDto transactionDto)
            throws UnsupportedEncodingException {
        // testDto -> transactionDto
        String txId = getHeaderF(transactionDto.getReq().getHeader(), Header.TX_ID);
        String clientIp = getHeaderF(transactionDto.getReq().getHeader(), Header.CLIENT_IP);
        String reqMthd = request.getMethod();
        String uriIn = request.getRequestURI();
        byte[] reqBodyByte = testDto.getBody().getBytes("euc-kr");
        var multiValueMap = ListToMultiValueMap(testDto);
        MultiValueMapToList(testDto, transactionDto);

        if (testDto.getTrtSect().equals("REQ")) {

            transactionDto.setComn(ComnonDto.builder().txId(txId).build());

            transactionDto.setReq(RequestDto.builder()
                    .stTm(System.currentTimeMillis())
                    .svcIp(clientIp)
                    .meth(reqMthd)
                    .in(uriIn)
                    .out("/out")
                    .header(transactionDto.getReq().getHeader())
                    .body(reqBodyByte)
                    .queryParams(multiValueMap)
                    .svcId("backendSvcId")
                    .build());

            transactionDto.setStore(StoreDto.builder().build());

        } else {

            transactionDto.setComn(ComnonDto.builder()
                    .txId(txId)
                    .build());

            transactionDto.setRes(ResponseDto.builder()
                    .stTm(System.currentTimeMillis())
                    .header(transactionDto.getRes().getHeader())
                    .body(reqBodyByte)
                    .build());

            transactionDto.setStore(StoreDto.builder()
                    .build());
        }
    }

    private void setTestResult(Transaction transaction, TestDto testDto, TransactionDto transactionDto) {
        if (testDto.getTrtSect().equals("REQ")) {
            transaction.setBody(new String(transactionDto.getReq().getBody()));
        } else {
            transaction.setBody(new String(transactionDto.getRes().getBody()));
        }
        var header = MultiValueMapToList(testDto, transactionDto);
        transaction.setHeader(header);
        transaction.setTransactionDto(transactionDto);
    }

    // 핸들러를 사용하는 API 조회
    public Response<ApiListDto> getApiListByHndlrId(String trtSect, String hndlrId) {
        ApiListDto resList = new ApiListDto();

        var isExistedHndlr = admHndlrRepository.existsByHndlrId(hndlrId);
        if (!isExistedHndlr) {
            throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
        }

        List<ApiIdAndSysIdDto> apiList = new ArrayList<>();
        List<AdmApiDply> admApiList = new ArrayList<>();

        if ("REQ".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByReqHndlr(hndlrId).get();
        } else if ("RES".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByResHndlr(hndlrId).get();
        } else if ("ERR".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByErrHndlr(hndlrId).get();
        }

        admApiList.stream().forEach(e -> {
            ApiIdAndSysIdDto apiIdAndSysIdDto = new ApiIdAndSysIdDto();
            apiIdAndSysIdDto.setApiId(e.getApiId())
                    .setSysId(e.getSysId());

            apiList.add(apiIdAndSysIdDto);
        });

        resList.setApiList(apiList);
        return new Response<ApiListDto>().responseOk(resList);
    }

    //핸들러 관리 업로드 API
    public Response<UrlDto> reqHndlrDply(AdmHndlrDto admHndlrDto, HttpServletRequest request) throws MalformedURLException, IOException, NoSuchAlgorithmException{

        ObjectMapper mapper = new ObjectMapper();

        if(domainUrl.substring(0,5).equals("https")){

            System.out.println("상용 환경입니다");
            
            //TB 또는 상용 환경
            if(admHndlrDto.getTbProd().equals("tb")){

                admHndlrDto.setTbReqDply("Y");
                admHndlrDto.setProdReqDply("N");

                String json = mapper.writeValueAsString(admHndlrDto);
                String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));;
                
                String upUrl = domainUrl + admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";

                // System.out.println("upUrl:" + upUrl);
                // System.out.println();

                //인코딩 작업
                String rawUrl =  admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";
                String encodedUrl = Base64.getEncoder().encodeToString(rawUrl.getBytes());
                // System.out.println("rawUrl:" + rawUrl);
                // System.out.println(encodedUrl);

                //조회후 업데이트 
                AdmHndlr entity = new AdmHndlr();
                Optional<AdmHndlr> optEntity = admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId());
                if(optEntity.isPresent()){
                    entity = optEntity.get();
                    entity.setTbReqDply("Y"); //TB만 Y해줌
                    entity.setProdReqDply(optEntity.get().getProdReqDply()); //Prod는 있는 그대로 해줌
                    entity.setTbDplyUrl(encodedUrl);
                    entity.setProdDplyUrl(optEntity.get().getProdDplyUrl());
                    admHndlrRepository.save(entity);
                }else{
                    throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
                }

                
                HttpsURLConnection con = null;
                con = (HttpsURLConnection) new URL(upUrl).openConnection();
                
                con.setConnectTimeout(3000);
                con.setRequestMethod("PUT");
                con.setDoInput(true);
                con.setDoOutput(true);
                con.addRequestProperty("Authorization", "Basic "+basicKey);
                
                OutputStream out_stream = con.getOutputStream();
                
                out_stream.write(json.getBytes("UTF-8"));
                out_stream.flush();
                out_stream.close();

                UrlDto urlDto = new UrlDto();
                urlDto.setTbDplyUrl(encodedUrl).setProdDplyUrl(optEntity.get().getProdDplyUrl());
                System.out.println(con.getResponseCode());
                return new Response<UrlDto>().responseOk(urlDto);

            }else if(admHndlrDto.getTbProd().equals("prd")){

                System.out.println("TB환경입니다");

                admHndlrDto.setTbReqDply("N");
                admHndlrDto.setProdReqDply("Y");

                String json = mapper.writeValueAsString(admHndlrDto);
                String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));;
                
                String upUrl = domainUrl  + admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";

                // System.out.println("upUrl:" + upUrl);
                // System.out.println();

                //인코딩 작업
                String rawUrl =  admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";
                String encodedUrl = Base64.getEncoder().encodeToString(rawUrl.getBytes());

                // System.out.println("rawUrl:" + rawUrl);
                // System.out.println(encodedUrl);

                //조회후 업데이트 
                AdmHndlr entity = new AdmHndlr();
                Optional<AdmHndlr> optEntity = admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId());
                if(optEntity.isPresent()){
                    entity = optEntity.get();
                    entity.setTbReqDply(optEntity.get().getTbReqDply()); //TB만 Y해줌
                    entity.setProdReqDply("Y"); //Prod는 있는 그대로 해줌
                    entity.setTbDplyUrl(optEntity.get().getTbDplyUrl());
                    entity.setProdDplyUrl(encodedUrl);
                    admHndlrRepository.save(entity);
                }else{
                    throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
                }

                HttpsURLConnection con = null;
                con = (HttpsURLConnection) new URL(upUrl).openConnection();
                
                con.setConnectTimeout(3000);
                con.setRequestMethod("PUT");
                con.setDoInput(true);
                con.setDoOutput(true);
                con.addRequestProperty("Authorization", "Basic "+basicKey);
                
                OutputStream out_stream = con.getOutputStream();
                
                out_stream.write(json.getBytes("UTF-8"));
                out_stream.flush();
                out_stream.close();

                UrlDto urlDto = new UrlDto();
                urlDto.setTbDplyUrl(optEntity.get().getTbDplyUrl()).setProdDplyUrl(encodedUrl);
                System.out.println(con.getResponseCode());
                return new Response<UrlDto>().responseOk(urlDto);

            }else{
                throw new AdminRuntimeException(StatusEnum.BASEURL_BAD_REQUEST);
            }

        }else{
            //dev 환경
            //TB 또는 상용 환경
            
            if(admHndlrDto.getTbProd().equals("tb")){
                System.out.println("Dev - TB 환경입니다");

                admHndlrDto.setTbReqDply("Y");
                admHndlrDto.setProdReqDply("N");

                String json = mapper.writeValueAsString(admHndlrDto);
                String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));;
                
                String upUrl = domainUrl + admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) + ".json";

                // System.out.println("upUrl:" + upUrl);
                // System.out.println();

                //인코딩 작업
                String rawUrl =  admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";
                String encodedUrl = Base64.getEncoder().encodeToString(rawUrl.getBytes());

                // System.out.println("rawUrl:" + rawUrl);
                // System.out.println(encodedUrl);

                //조회후 업데이트 
                AdmHndlr entity = new AdmHndlr();
                Optional<AdmHndlr> optEntity = admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId());
                if(optEntity.isPresent()){
                    entity = optEntity.get();
                    entity.setTbReqDply("Y"); //TB만 Y해줌
                    entity.setProdReqDply(optEntity.get().getProdReqDply()); //Prod는 있는 그대로 해줌
                    entity.setTbDplyUrl(encodedUrl);
                    entity.setProdDplyUrl(optEntity.get().getProdDplyUrl());
                    admHndlrRepository.save(entity);
                }else{
                    throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
                }
                
                HttpURLConnection con = null;
                con = (HttpURLConnection) new URL(upUrl).openConnection();
                
                con.setConnectTimeout(3000);
                con.setRequestMethod("PUT");
                con.setDoInput(true);
                con.setDoOutput(true);
                con.addRequestProperty("Authorization", "Basic "+basicKey);
                
                OutputStream out_stream = con.getOutputStream();
                
                out_stream.write(json.getBytes("UTF-8"));
                out_stream.flush();
                out_stream.close();

                UrlDto urlDto = new UrlDto();
                urlDto.setTbDplyUrl(encodedUrl).setProdDplyUrl(optEntity.get().getProdDplyUrl());

                System.out.println(con.getResponseCode());
                return new Response<UrlDto>().responseOk(urlDto);

        }else if(admHndlrDto.getTbProd().equals("prd")){

            System.out.println("Dev - PROD 환경입니다");
            admHndlrDto.setTbReqDply("N");
            admHndlrDto.setProdReqDply("Y");

            String json = mapper.writeValueAsString(admHndlrDto);
            String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
            
            
            String upUrl = domainUrl + admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";

            // System.out.println("upUrl:" + upUrl);
            // System.out.println();

            //인코딩 작업
            String rawUrl =  admHndlrDto.getTbProd() + "/" + admHndlrDto.getHndlrId() + "/" +  admHndlrDto.getHndlrId() + "_" + now + "_" +checkSum(json) +".json";
            String encodedUrl = Base64.getEncoder().encodeToString(rawUrl.getBytes());

            // System.out.println("rawUrl:" + rawUrl);
            // System.out.println(encodedUrl);

            //조회후 업데이트 
            AdmHndlr entity = new AdmHndlr();
            Optional<AdmHndlr> optEntity = admHndlrRepository.findByHndlrId(admHndlrDto.getHndlrId());
            if(optEntity.isPresent()){
                entity = optEntity.get();
                entity.setTbReqDply(optEntity.get().getTbReqDply()); //TB만 Y해줌
                entity.setProdReqDply("Y"); //Prod는 있는 그대로 해줌
                entity.setTbDplyUrl(optEntity.get().getTbDplyUrl());
                entity.setProdDplyUrl(encodedUrl);
                admHndlrRepository.save(entity);
            }else{
                throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
            }

            
            HttpURLConnection con = null;
            con = (HttpURLConnection) new URL(upUrl).openConnection();
            
            con.setConnectTimeout(3000);
            con.setRequestMethod("PUT");
            con.setDoInput(true);
            con.setDoOutput(true);
            con.addRequestProperty("Authorization", "Basic "+basicKey);
            
            OutputStream out_stream = con.getOutputStream();
            
            out_stream.write(json.getBytes("UTF-8"));
            out_stream.flush();
            out_stream.close();

            UrlDto urlDto = new UrlDto();
            urlDto.setTbDplyUrl(optEntity.get().getTbDplyUrl()).setProdDplyUrl(encodedUrl);

            System.out.println(con.getResponseCode());

            return new Response<UrlDto>().responseOk(urlDto);

        }else{
            throw new AdminRuntimeException(StatusEnum.BASEURL_BAD_REQUEST);
        }
        }
    }

    public String checkSum(String data) throws NoSuchAlgorithmException, IOException{
        
        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));;
		
		MessageDigest md5 = MessageDigest.getInstance("MD5");
        DigestInputStream dis = new DigestInputStream(new ByteArrayInputStream(data.getBytes("UTF-8")), md5);
        while(dis.read() != -1) ;
        dis.close();
        String resultData;
        Formatter formatter = new Formatter();
        for(byte b : md5.digest()) {
            formatter.format("%02X", b);
        }
        resultData = formatter.toString();
        formatter.close();

        return resultData;
    }
}

class Loop extends Thread {

    private TransactionDto transactionDto;
    private RouteHandler routeHandler;
    private boolean testResult;

    public Loop(TransactionDto transactionDto, RouteHandler routeHandler){
        this.transactionDto = transactionDto;
        this.routeHandler = routeHandler;
    }

    @Override
    public void run() {
        testResult = routeHandler.process(transactionDto);

    }

    public void stopThread() {
        super.interrupt();
    }

    public boolean getTestResult(){
        return this.testResult;
    }

}

핸들러 배포

package com.ktds.act.admin.hndlrdplymngt.service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.Formatter;

import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import javax.transaction.Transactional;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ktds.act.admin.common.config.hndlr.HndlrBlackList;
import com.ktds.act.admin.common.handler.AdminRuntimeException;
import com.ktds.act.admin.common.response.Pagination;
import com.ktds.act.admin.common.response.Response;
import com.ktds.act.admin.common.response.StatusEnum;
import com.ktds.act.admin.common.util.UserAuthUtil;
import com.ktds.act.admin.domain.dto.AdmHndlrDplyDto;
import com.ktds.act.admin.domain.entity.AdmApiDply;
import com.ktds.act.admin.domain.entity.AdmHndlrDply;
import com.ktds.act.admin.domain.enums.DplyType;
import com.ktds.act.admin.domain.repository.AdmApiDplyTrRepository;
import com.ktds.act.admin.domain.repository.AdmHndlrDplyTrRepository;
import com.ktds.act.admin.hndlrdplymngt.db.HndlrDplyQueryParameter;
import com.ktds.act.admin.hndlrdplymngt.db.SrcDto;
import com.ktds.act.admin.hndlrmngt.db.model.ApiIdAndSysIdDto;
import com.ktds.act.admin.hndlrmngt.db.model.ApiListDto;
import com.ktds.act.admin.hndlrmngt.db.model.DuplicatedCheckDto;
import com.ktds.act.apigw.common.util.CompilerUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class HndlrDplyMngtService {

    @Value("${nexus.url}")
    private String domainUrl;

    @Value("${nexus.basic}")
    private String basicKey;

    private final AdmHndlrDplyTrRepository admHndlrDplyTrRepository;
    private final AdmApiDplyTrRepository admApiDplyTrRepository;
    private final ApplicationContext applicationContext;
    private final UserAuthUtil userAuthUtil;

    // 핸들러 임시저장 및 등록
    public Response<Object> hndlrMngt(AdmHndlrDplyDto admHndlrDplyDto, HttpServletRequest request) {

        AdmHndlrDply result = new AdmHndlrDply();

        if (admHndlrDplyTrRepository.existsByHndlrId(admHndlrDplyDto.getHndlrId()) == true) {
            //수정하는 부분
            return admHndlrDplyTrRepository.findByHndlrId(admHndlrDplyDto.getHndlrId()).map(entity -> {

                String hndlrId = entity.getHndlrId();
                String trtSect = entity.getTrtSect();
    
                String cstmClassAdr = "";
    
                if (!admHndlrDplyDto.getCstmClassSrc().isEmpty() || admHndlrDplyDto.getCstmClassSrc() != "") {
                    String[] reg = admHndlrDplyDto.getCstmClassSrc().split(";");
                    String[] adr = reg[0].split("package ");
                    cstmClassAdr = adr[1] + "." + admHndlrDplyDto.getCstmClassNm();
                }
    
                admHndlrDplyDto.setCstmClassAdr(cstmClassAdr);
                admHndlrDplyDto.setDplyType(DplyType.DPLY);

                BeanUtils.copyProperties(admHndlrDplyDto, entity, "id", "hndlrId", "cretId", "cretDt");
    
                admHndlrDplyTrRepository.save(entity);

                return new Response<>().responseOk(StatusEnum.SUCCESS);

            }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
        
        }else{
            //등록하는 부분
            String cstmClassAdr = "";

            if (!admHndlrDplyDto.getCstmClassSrc().isEmpty() || admHndlrDplyDto.getCstmClassSrc() != "") {
                String[] reg = admHndlrDplyDto.getCstmClassSrc().split(";");
                String[] adr = reg[0].split("package ");
                cstmClassAdr = adr[1] + "." + admHndlrDplyDto.getCstmClassNm();
            }
            admHndlrDplyDto.setCstmClassAdr(cstmClassAdr);
            admHndlrDplyDto.setDplyType(DplyType.DPLY);
    
            AdmHndlrDply entity = admHndlrDplyTrRepository.findByHndlrId(admHndlrDplyDto.getHndlrId()).orElseGet(AdmHndlrDply::new);
    
            if (entity.getId() == null) {
                if (admHndlrDplyTrRepository.existsByHndlrId(entity.getHndlrId())) {
                    throw new AdminRuntimeException(StatusEnum.DUPLICATED_HNDLR_ID);
                }
                entity = AdmHndlrDply.to(admHndlrDplyDto);
            } else {
                entity = admHndlrDplyTrRepository.findById(entity.getId()).get();
            }
    
            // db에 값이 있을 때만 update
            if (entity.getId() != null) {
                BeanUtils.copyProperties(admHndlrDplyDto, entity, "id", "hndlrId", "cretId", "cretDt");
            }

            result = admHndlrDplyTrRepository.save(entity);

        }
        return new Response<>().responseOk(result);
    }

    // 핸들러 전체조회 및 검색
    public Response<List<AdmHndlrDplyDto>> getHndlrList(Pageable pageable,
            HndlrDplyQueryParameter hndlrDplyQueryParameter, HttpServletRequest request) {

        if (hndlrDplyQueryParameter.getHndlrId() != null || hndlrDplyQueryParameter.getTrtSect() != null){
            if(hndlrDplyQueryParameter.getHndlrId() != null && hndlrDplyQueryParameter.getTrtSect() != null){

                var pageAdmHndlr = (Page) admHndlrDplyTrRepository
                        .findByHndlrIdIgnoreCaseContainingAndTrtSectIgnoreCaseContainingAndDplyType(hndlrDplyQueryParameter.getHndlrId(), hndlrDplyQueryParameter.getTrtSect(),"DPLY",pageable);
                return new Response<List<AdmHndlrDplyDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if(hndlrDplyQueryParameter.getHndlrId() != null){

                var pageAdmHndlr = (Page) admHndlrDplyTrRepository
                        .findByHndlrIdIgnoreCaseContainingAndDplyType(hndlrDplyQueryParameter.getHndlrId(), "DPLY",pageable);
                return new Response<List<AdmHndlrDplyDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }

            if(hndlrDplyQueryParameter.getTrtSect() != null){

                var pageAdmHndlr = (Page) admHndlrDplyTrRepository
                        .findByTrtSectIgnoreCaseContainingAndDplyType(hndlrDplyQueryParameter.getTrtSect(), "DPLY",pageable);
                return new Response<List<AdmHndlrDplyDto>>().responseOk(pageAdmHndlr.getContent(),
                        new Pagination().complete(pageAdmHndlr, pageable));
            }
        }
        var result = (Page) admHndlrDplyTrRepository.findByDplyTypeOrderByDplyDtDescHndlrIdAsc("DPLY", pageable);
        return new Response<List<AdmHndlrDplyDto>>().responseOk(result.getContent(),
                new Pagination().complete(result, pageable));  
    }

    

    // 핸들러 상세조회
    public Response<AdmHndlrDplyDto> getHndlrById(String hndlrId, HttpServletRequest request) {

        return admHndlrDplyTrRepository.findByHndlrIdAndDplyType(hndlrId, "DPLY").map(entity -> {

            var responseDto = AdmHndlrDplyDto.of(entity);
            return new Response<AdmHndlrDplyDto>().responseOk(responseDto);
        }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
        
    }

    // 핸들러 ID 중복체크
    public Response<DuplicatedCheckDto> hndlrCheckById(String hndlrId, HttpServletRequest request) {
        boolean isExisted = false;
        isExisted = admHndlrDplyTrRepository.existsByHndlrId(hndlrId);
        var responseDto = new DuplicatedCheckDto().setIsPkDuplicated(isExisted);
        return new Response<DuplicatedCheckDto>().responseOk(responseDto);
    }

    // 템플릿 소스 오류체크 컴파일
    public Response<Object> errorCheck(AdmHndlrDplyDto admHndlrDplyDto) {

        try {
            registerTemplateCode(admHndlrDplyDto);
        } catch (AdminRuntimeException e) {
            log.error("invalid source");
            return new Response<>().serverError(StatusEnum.INVALID_SOURCE);
        } catch (Exception e) {
            DuplicatedCheckBean(admHndlrDplyDto);
            log.error("errorCheck error : {}", e.getMessage());
            return new Response<>().serverError(9000, e.getMessage());
        }
        DuplicatedCheckBean(admHndlrDplyDto);
        return new Response<>().responseOk();
    }

    private void registerTemplateCode(AdmHndlrDplyDto admHndlrDplyDto) throws ClassNotFoundException, IOException {

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        String compileSource = admHndlrDplyDto.getCstmClassSrc();

        HndlrBlackList.getSourceBlackList().stream().forEach(e -> {
            if (admHndlrDplyDto.getCstmClassSrc().contains(e)) {
                throw new AdminRuntimeException(StatusEnum.INVALID_SOURCE);
            }
        });

        String cstmClassAdr = "";

        if (!admHndlrDplyDto.getCstmClassSrc().isEmpty() || admHndlrDplyDto.getCstmClassSrc() != "") {
            String[] reg = admHndlrDplyDto.getCstmClassSrc().split(";");
            String[] adr = reg[0].split("package ");
            cstmClassAdr = adr[1] + "." + admHndlrDplyDto.getCstmClassNm();
        }
        admHndlrDplyDto.setCstmClassAdr(cstmClassAdr);
        System.out.println(cstmClassAdr);

        // compileSource = compileSource.replaceFirst("com.ktds.act.apigw.filter.handler.custom;",
        //         "com.ktds.act.admin.common.handler;");
        Class<?> cls = CompilerUtils.compileSource(cstmClassAdr, compileSource);

        defaultListableBeanFactory.registerBeanDefinition(admHndlrDplyDto.getHndlrId(),
                BeanDefinitionBuilder.genericBeanDefinition(cls).getRawBeanDefinition());
    }

    // 중복된 빈 제거
    private void DuplicatedCheckBean(AdmHndlrDplyDto admHndlrDplyDto) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext
                .getBeanFactory();

        if (defaultListableBeanFactory.containsBeanDefinition(admHndlrDplyDto.getHndlrId())) {
            defaultListableBeanFactory.removeBeanDefinition(admHndlrDplyDto.getHndlrId());
        }
    }

    // 템플릿 클래스 중복 조회
    public Response<DuplicatedCheckDto> checkClassNm(String classNm) {
        var isDuplicated = admHndlrDplyTrRepository.existsByCstmClassNm(classNm);
        var responseDto = new DuplicatedCheckDto().setIsPkDuplicated(isDuplicated);
        return new Response<DuplicatedCheckDto>().responseOk(responseDto);
    }

    // 핸들러를 사용하는 API 조회
    public Response<ApiListDto> getApiListByHndlrId(String trtSect, String hndlrId) {
        ApiListDto resList = new ApiListDto();

        var isExistedHndlr = admHndlrDplyTrRepository.existsByHndlrId(hndlrId);
        if (!isExistedHndlr) {
            throw new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND);
        }

        List<ApiIdAndSysIdDto> apiList = new ArrayList<>();
        List<AdmApiDply> admApiList = new ArrayList<>();

        if ("REQ".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByReqHndlr(hndlrId).get();
        } else if ("RES".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByResHndlr(hndlrId).get();
        } else if ("ERR".equals(trtSect)) {
            admApiList = admApiDplyTrRepository.findByErrHndlr(hndlrId).get();
        }

        admApiList.stream().forEach(e -> {
            ApiIdAndSysIdDto apiIdAndSysIdDto = new ApiIdAndSysIdDto();
            apiIdAndSysIdDto.setApiId(e.getApiId())
                    .setSysId(e.getSysId());

            apiList.add(apiIdAndSysIdDto);
        });

        resList.setApiList(apiList);
        return new Response<ApiListDto>().responseOk(resList);
    }

    // 핸들러 삭제 
    @Transactional
    public Response<Object> hndlrDplyDel(AdmHndlrDplyDto admHndlrDplyDto, HttpServletRequest request) {

        var hndlrId = admHndlrDplyDto.getHndlrId();

        return admHndlrDplyTrRepository.findByHndlrId(hndlrId).map(entity -> {

            String objectId = entity.getId();
            String trtSect = entity.getTrtSect();

            Boolean apiBool = false;

            if (trtSect.equals("REQ")) {
                apiBool = admApiDplyTrRepository.existsByReqHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            } else if (trtSect.equals("RES")) {
                apiBool = admApiDplyTrRepository.existsByResHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            } else if (trtSect.equals("ERR")) {
                apiBool = admApiDplyTrRepository.existsByErrHndlrAndDplyTypeNotIn(hndlrId, DplyType.DEL);

                if (apiBool == true) {
                    throw new AdminRuntimeException(StatusEnum.USED_HNDLR);
                }
            }

            // hndlrDply 상태 변경
            admHndlrDplyTrRepository.findByHndlrId(hndlrId).stream().forEach(hndlrDplyEntity ->{
                if(DplyType.DPLY.equals(hndlrDplyEntity.getDplyType())){
                    hndlrDplyEntity.setDplyType(DplyType.DEL);
                }
                admHndlrDplyTrRepository.save(hndlrDplyEntity);
            });

            return new Response<>().responseOk(StatusEnum.SUCCESS);
        }).orElseThrow(() -> new AdminRuntimeException(StatusEnum.DATA_NOT_FOUND));
    }

    //핸들러 nexus url 조회
    public Response<AdmHndlrDplyDto> getUrlHndlr(String encodedUrl, HttpServletRequest request) throws MalformedURLException, IOException, ParseException, NoSuchAlgorithmException{

        if(domainUrl.substring(0,5).equals("https")){
            //상용 TB 환경

            JSONParser jsonParser = new JSONParser();
            
            AdmHndlrDplyDto admHndlrDplyDto = new AdmHndlrDplyDto();

            //encodedUrl 해석부분(복호화)
            String decodedUrl = decodingUrl(encodedUrl);
            String url = decodedUrl;
            
            HttpsURLConnection con = null;
            con = (HttpsURLConnection) new URL(url).openConnection();
            
            con.setConnectTimeout(3000);
            con.setRequestMethod("GET");
            con.setDoInput(true);
            con.setDoOutput(true);
            con.addRequestProperty("Authorization", "Basic "+basicKey);
            
            InputStream input_stream = con.getInputStream();

            byte[] contentsByte = input_stream.readAllBytes();
            String jsonString = new String(contentsByte);
            System.out.println(jsonString);
            

            //checkSum 확인
            if(!checkOfCheckSum(jsonString, decodedUrl)){
                throw new AdminRuntimeException(StatusEnum.BAD_CHECKSUM);
            }

            Object obj = jsonParser.parse(jsonString);
            JSONObject jsonObj = (JSONObject) obj;

            admHndlrDplyDto.setCstmClassSrc(jsonObj.get("cstmClassSrc").toString())
                            .setCstmClassAdr(jsonObj.get("cstmClassAdr").toString())
                            .setCstmClassNm(jsonObj.get("cstmClassNm").toString())
                            .setBodyRfrn(true)
                            .setHndlrId(jsonObj.get("hndlrId").toString())
                            .setType(jsonObj.get("type").toString())
                            .setTrtSect(jsonObj.get("trtSect").toString())
                            .setDesc(jsonObj.get("desc").toString());;
            
            if(decodedUrl.contains("/tb/")){
                admHndlrDplyDto.setTbDplyUrl(encodedUrl);
                admHndlrDplyDto.setProdDplyUrl("");
            }else{
                admHndlrDplyDto.setTbDplyUrl("");
                admHndlrDplyDto.setProdDplyUrl(encodedUrl);
            }
                            

            input_stream.close();

            //errorCheck 로직 
            errorCheck(admHndlrDplyDto);

            System.out.println(con.getResponseCode());
            return new Response<AdmHndlrDplyDto>().responseOk(admHndlrDplyDto);

        }else{
            //Dev 환경
            JSONParser jsonParser = new JSONParser();
            
            AdmHndlrDplyDto admHndlrDplyDto = new AdmHndlrDplyDto();

            //encodedUrl 해석부분(복호화)
            String decodedUrl = decodingUrl(encodedUrl);
            String url = decodedUrl;
            
            HttpURLConnection con = null;
            con = (HttpURLConnection) new URL(url).openConnection();
            
            con.setConnectTimeout(3000);
            con.setRequestMethod("GET");
            con.setDoInput(true);
            con.setDoOutput(true);
            con.addRequestProperty("Authorization", "Basic "+basicKey);
            
            InputStream input_stream = con.getInputStream();

            byte[] contentsByte = input_stream.readAllBytes();
            String jsonString = new String(contentsByte);
            System.out.println(jsonString);

            //checkSum 확인
            if(!checkOfCheckSum(jsonString, decodedUrl)){
                throw new AdminRuntimeException(StatusEnum.BAD_CHECKSUM);
            }

            Object obj = jsonParser.parse(jsonString);
            JSONObject jsonObj = (JSONObject) obj;

            admHndlrDplyDto.setCstmClassSrc(jsonObj.get("cstmClassSrc").toString())
                            .setCstmClassAdr(jsonObj.get("cstmClassAdr").toString())
                            .setCstmClassNm(jsonObj.get("cstmClassNm").toString())
                            .setBodyRfrn(true)
                            .setHndlrId(jsonObj.get("hndlrId").toString())
                            .setType(jsonObj.get("type").toString())
                            .setTrtSect(jsonObj.get("trtSect").toString())
                            .setDesc(jsonObj.get("desc").toString());
            

            if(decodedUrl.contains("/tb/")){
                admHndlrDplyDto.setTbDplyUrl(encodedUrl);
                admHndlrDplyDto.setProdDplyUrl("");
            }else{
                admHndlrDplyDto.setTbDplyUrl("");
                admHndlrDplyDto.setProdDplyUrl(encodedUrl);
            }                

            input_stream.close();

            //errorCheck 로직 
            errorCheck(admHndlrDplyDto);
            
            System.out.println(con.getResponseCode());
            System.out.println(con.getResponseMessage());

            return new Response<AdmHndlrDplyDto>().responseOk(admHndlrDplyDto);

        }

    }

    //get 오래된 소스 가져오기 
    public Response<SrcDto> getCstmClassSrcOld(String hndlrId, HttpServletRequest request){

        SrcDto srcDto = new SrcDto();

        boolean dplyYn = true;

        Optional<AdmHndlrDply> optEntity = admHndlrDplyTrRepository.findByHndlrIdAndDplyType(hndlrId, "DPLY");

        if(optEntity.isPresent()){
            dplyYn = true;
            srcDto.setDplyYn(dplyYn);
            srcDto.setCstmClassSrcOld(optEntity.get().getCstmClassSrc());
        }else{
            dplyYn = false;
            srcDto.setDplyYn(dplyYn);
            srcDto.setCstmClassSrcOld("");
        }

        return new Response<SrcDto>().responseOk(srcDto);
    }
    

    //Encoding된거 해독하기 
    public String decodingUrl(String encodedUrl){

        //byte[] decodedUrl = Base64.getDecoder().decode(encodedUrl.getBytes());

        byte[] decodedUrlBytes =  Base64.getDecoder().decode(encodedUrl);

        String decodedUrl = new String(decodedUrlBytes);

        String resutUrl = domainUrl  + decodedUrl;

        return resutUrl;

    }

    public String checkSum(String data) throws NoSuchAlgorithmException, IOException{
        
        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));;
		
		MessageDigest md5 = MessageDigest.getInstance("MD5");
        DigestInputStream dis = new DigestInputStream(new ByteArrayInputStream(data.getBytes("UTF-8")), md5);
        while(dis.read() != -1) ;
        dis.close();
        String resultData;
        Formatter formatter = new Formatter();
        for(byte b : md5.digest()) {
            formatter.format("%02X", b);
        }
        resultData = formatter.toString();
        formatter.close();

        return resultData;
    }

    public boolean checkOfCheckSum(String data, String decodedUrl) throws NoSuchAlgorithmException, IOException{

        String recieveCheck = checkSum(data);

        boolean result = false;

        if(decodedUrl.contains(recieveCheck)){
            result = true;
        }

        return result;
    }

}