package cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.api;

import cokr.xit.ens.core.utils.CmmnUtil;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.code.NiceCiApiCd;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.model.NiceCiRespDTO;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.model.NiceCiRespVO;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.model.conf.DataBodyCiResp;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.model.conf.DataBodySymkeyResp;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.model.conf.EncData;
import cokr.xit.ens.modules.common.ctgy.nicedici.support.rest.utils.SymmetricKey;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Base64Util;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@SuperBuilder
public class CiRequest extends NiceCiApiAbstract {

    protected final String HOST;
    protected final String API_URL;


    public NiceCiRespVO<DataBodyCiResp> execute(String clientId, String clientSecret, String jumin, String clientIp) throws Exception {
        if (CmmnUtil.isEmpty(clientId))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(clientSecret))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트비밀번호는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getKey()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getSymkeyStatInfo().getCurSymkeyVersion()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키 버전은 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getSiteCode()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("사이트코드는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getRequestNo()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("신청번호는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getIv()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("IV는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(SymmetricKey.getInstance().getData(clientId).getHmacKey()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("HmacKey는 필수 입력값 입니다.").build();
        if (CmmnUtil.isEmpty(jumin))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("주민번호는 필수 입력값 입니다.").build();


        NiceCiRespDTO<DataBodyCiResp> responseDTO = this.getCi(clientId, clientSecret, SymmetricKey.getInstance().getData(clientId), jumin, clientIp);


        if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder()
                    .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + responseDTO.getDataHeader().getGW_RSLT_CD()))
                    .errMsg(String.format("[%s]: %s", responseDTO.getDataHeader().getGW_RSLT_CD(), responseDTO.getDataHeader().getGW_RSLT_MSG()))
                    .build();
        if (!"P000".equals(responseDTO.getDataBody().getRspCd()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder()
                    .errCode(NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd()))
                    .errMsg(String.format("[%s]: %s. %s", responseDTO.getDataBody().getRspCd(), NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd()).getCodeNm(), responseDTO.getDataBody().getResMsg()))
                    .build();
        if (!"0000".equals(responseDTO.getDataBody().getResultCd()))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder()
                    .errCode(NiceCiApiCd.valueOfEnum("CI_" + responseDTO.getDataBody().getResultCd()))
                    .errMsg(String.format("[%s]: %s", responseDTO.getDataBody().getResultCd(), NiceCiApiCd.valueOfEnum("CI_" + responseDTO.getDataBody().getResultCd()).getCodeNm()))
                    .build();


        if (!responseDTO.getDataBody().getIntegrityValue().equals(createIntegrityValue(responseDTO.getDataBody().getEncData(), SymmetricKey.getInstance().getData(clientId).getHmacKey())))
            return NiceCiRespVO.<DataBodyCiResp>errBuilder()
                    .errCode(NiceCiApiCd.FAIL)
                    .errMsg("응답데이터 무결성 체크 오류")
                    .build();
        String decEncDataStr = this.decEncData(responseDTO.getDataBody().getEncData(), SymmetricKey.getInstance().getData(clientId).getKey(), SymmetricKey.getInstance().getData(clientId).getIv());
        responseDTO.getDataBody().setDecEncData(mapper.readValue(decEncDataStr, EncData.class));


        return NiceCiRespVO.<DataBodyCiResp>okBuilder().resultInfo(responseDTO.getDataBody()).build();

    }


    protected NiceCiRespDTO<DataBodyCiResp> getCi(String clientId, String clientSecret, DataBodySymkeyResp symkeyResp, String jumin, String clientIp) throws Exception {
        try {


//            String authorizationToken = Base64.getEncoder().encodeToString(String.format("%s:%s", clientId, clientSecret).getBytes(StandardCharsets.UTF_8));
            String authorizationToken = Base64Util.encode(String.format("%s:%s", clientId, clientSecret));
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8")));
            headers.set("Authorization", String.format("Basic %s", authorizationToken));
            headers.set("productID", PRODUCT_ID);


            StringBuilder url = new StringBuilder();
            url.append(this.HOST)
                    .append(this.API_URL);
            final String strEncData = this.createEncData(symkeyResp.getSiteCode(), jumin, symkeyResp.getRequestNo(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), clientIp);
            final String encData = this.encEncData(strEncData, symkeyResp.getKey(), symkeyResp.getIv());
            final String integrityValue = this.createIntegrityValue(encData, symkeyResp.getHmacKey());
            final String jsonStr = this.createMessage(symkeyResp.getSymkeyStatInfo().getCurSymkeyVersion(), encData, integrityValue);


            ResponseEntity<String> resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers);
            log.info("==================================================================================");
            log.info("==== 아이핀 CI 요청 Info... ====");
            log.info("[Body]: " + jsonStr);
            log.info("[encData]: " + strEncData);
            log.info("==== 아이핀 CI 요청 Result Info... ====");
            log.info("[Headers]: " + resp.getHeaders().toString());
            log.info("[Body]: " + resp.getBody());
            log.info("==================================================================================");



            String strRespBody = resp.getBody().replace("\"\"", "null");
            NiceCiRespDTO<DataBodyCiResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodyCiResp>>(){});


            return respDTO;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("아이핀 CI 요청 실패." + e.getMessage());
        }


    }


    /**
     * JSON 포맷의 요청메시지 생성
     *
     * @param symkeyVersion
     * @param encData
     * @param integrityValue
     * @return
     * @throws JsonProcessingException
     */
    protected String createMessage(String symkeyVersion, String encData, String integrityValue) throws JsonProcessingException {
        Map<String, String> dataHeader = new HashMap<>();
        dataHeader.put("CNTY_CD", "ko"); 
        dataHeader.put("TRAN_ID", null); 

        Map<String, String> dataBody = new HashMap<>();
        dataBody.put("symkey_version", symkeyVersion);  
        dataBody.put("enc_data", encData);              
        dataBody.put("integrity_value", integrityValue);

        Map<String, Object> m = new HashMap<>();
        m.put("dataHeader", dataHeader);
        m.put("dataBody", dataBody);

        return mapper.writeValueAsString(m);
    }

    private String createEncData(String siteCode, String jid, String reqNo, String reqDtim, String clientIp) throws JsonProcessingException {
        Map<String, String> m = new HashMap<>();
        m.put("site_code", siteCode);                                   
        m.put("info_req_type", "1");                                    
        m.put("jumin_id", jid.replaceAll("[^0-9]", ""));
        m.put("req_no", reqNo);                                         
        m.put("req_dtim", reqDtim);                                     
        m.put("client_ip", clientIp);                                   

        return mapper.writeValueAsString(m);


    }

    private String encEncData(String encData, String symkey, String iv) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {

       
        SecretKey secureKey = new SecretKeySpec(symkey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes()));
        byte[] encrypted = cipher.doFinal(encData.trim().getBytes());

       
        String reqDataEnc = Base64Utils.encodeToString(encrypted);

        return reqDataEnc;
    }


    private String decEncData(String encData, String symkey, String iv) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {

       
        byte[] respDataEnc = Base64Utils.decode(encData.getBytes());

       
        SecretKey secureKey = new SecretKeySpec(symkey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes()));
        byte[] decrypted = cipher.doFinal(respDataEnc);
        String strDecrypted = new String(decrypted);

        return strDecrypted;
    }


    /**
     * Hmac 무결성체크값(integrity_value) 생성
     *
     * @param encData
     * @param hmacKey
     * @return
     */
    private String createIntegrityValue(String encData, String hmacKey) {
        byte[] hmacSha256 = hmac256(hmacKey.getBytes(), encData.getBytes());
        String integrityValue = Base64.getEncoder().encodeToString(hmacSha256);

        return integrityValue;
    }

    private byte[] hmac256(byte[] secretKey, byte[] message) {
        byte[] hmac256 = null;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec sks = new SecretKeySpec(secretKey, "HmacSHA256");
            mac.init(sks);
            hmac256 = mac.doFinal(message);
            return hmac256;
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate HMACSHA256 encrypt");
        }
    }


}

