package com.eactive.eai.adapter.controller;

import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.adapter.service.ApiAdapterService;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.stdmessage.STDMessageManager;
import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
import com.eactive.eai.common.util.InboundErrorLogger;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.inbound.error.InboundErrorInfoVO;

// jwhong
import org.json.simple.JSONObject;
import java.util.HashMap;
import java.util.Map;

@RestController
public class ApiAdapterController implements HttpAdapterServiceKey {
	static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

	@Autowired
	ApiAdapterService service;

	/**
	 * KJBANK 요구사항으로 /mapi/oauth2/token 과 같은 형태로 토큰 발급거래를 수행해야해서 예외처리함
	 * 
	 * @See com.eactive.eai.authserver.config.AuthorizationServerConfig.configure(AuthorizationServerEndpointsConfigurer
	 *      endpoints)
	 */
	@RequestMapping(value = { "/api/*", "/mapi/*", "/api/*/{path:^(?!.*oauth).*$}/**",
			"/mapi/{path:^(?!.*oauth2).*$}/**" })
//    @RequestMapping(value = {"/api/**"})
	public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
			throws Exception {
		// /ONLWeb/api/v1/public/getUserInfo.svc
		String apiUri = servletRequest.getRequestURI();
		// /api/v1/public/getUserInfo.svc
		apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath());
		apiUri = StringUtils.removeEnd(apiUri, "/");

		// ** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다
		String methodAndUri = servletRequest.getMethod() + "|" + apiUri;
		HttpDynamicInAdapterUri adptUri = null;
		String bzwkSvcKeyName = ""; // Adapter Group별 Action Class에 따라 구성이 달라짐. 예)
									// _AGW_IN_RST_SyS:POST/account/{acc_no}
		String adapterGrpName = ""; // Adapter Group명 예) _AGW_IN_RST_SyS : API_PATH(/api/test)
		String apiSvcCode = ""; // eaiSvcCd 예) LONNCHCON00005S2
		String apiFullPathKey = ""; // 예) POST|/api/test/account/list, POST|/api/test/account/{acc_no}
		Map<String, String> pathVariables = null;
		try {
			// PathVariable(ex:/api/test/account/{acc_no}) 대응 및 DAO조회 제거.
			// /api/test/account/1234567 호출시 /api/test/account/{acc_no} API 로 식별.
			STDMessageManager manager = STDMessageManager.getInstance();
			STDMsgInfoAddOnVO stdMsgInfo = manager.getStdMsgInfoAddOn(methodAndUri);
			bzwkSvcKeyName = stdMsgInfo.getBzwksvckeyname();
			apiSvcCode = stdMsgInfo.getEaiSvcCd();
			adapterGrpName = stdMsgInfo.gAdapterGroupName();
			apiFullPathKey = stdMsgInfo.getApiFullPath();
			if (STDMessageManager.isPathVariable(apiFullPathKey)) {
				pathVariables = new AntPathMatcher().extractUriTemplateVariables(apiFullPathKey, methodAndUri);
			}

			adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), adapterGrpName);
		} catch (Exception e) {
			adptUri = null;
		}
		// ***

		if (logger.isDebug()) {
			logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI());
		}

		if (adptUri == null) {
			logError(servletRequest);
			String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
					"can not find Adapter Uri");
			return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
		}

		// Received time , jwhong
		long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함
		String receivedTimeStr = String.valueOf(receivedTimeMillis);

		String adapterGroupName = adptUri.getAdptGrpName();
		String adapterName = adptUri.getAdptName();
		AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
		AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName);
		if (adapterVO == null) {
			String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR,
					"Adapter not found error");
			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
					.body(errorMsg);
		}

		Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName());

		String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType();
		String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8");
		MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode);
		String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);

		String responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC");
		ResponseEntity responseEntity = null;
		Properties transactionProp = new Properties();
		// jwhong, put api received time, eaiSvcCode
		transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
		transactionProp.put(API_SERVICE_CODE, apiSvcCode);
		if (pathVariables != null) {
			transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables);
		}

		try {
			String responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO,
					transactionProp);
			// if (RESPONSE_TYPE_ASYNC.equals(responseType)) { // table의 값이 ASYNC 로 들어가 있는데
			// response_type_async는 ASYN 로 되어 있어 아래처럼 비교함 jwhong 2025
			if ("ASYNC".equals(responseType)) {
				// responseEntity = ResponseEntity.ok("dummy"); // comment by jwhong
				// servletResponse.addHeader("traceId", responseData); // uuid를 response header에
				servletResponse.addHeader("traceId",
						transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에
				int httpStatus = servletResponse.getStatus();
				if (httpStatus == 200) {
					// 업체별 aync response message 가 다르다.
					String asyncMsgStyle = httpProp.getProperty("ASYNC_RTNMSG_TYPE", "");
					String responseBody = "";
					boolean encryptAsyncAckApply = StringUtils
							.equalsIgnoreCase(httpProp.getProperty("ASYNC_ENCRYPT_ACK", "N"), "Y");

					if (asyncMsgStyle == null || asyncMsgStyle.trim().isEmpty()) {
						responseBody = makeResponseBodyMsg();
					} else {
						responseBody = asyncMsgStyle;
					}
					if (encryptAsyncAckApply) {
						responseBody = service.doPostEncryption(responseBody, transactionProp, servletRequest);
					}
					// In 으로 들어온 http header를 ack 줄때 다시 넘겨줌 jwhong, 특히 kakaobank.
					Enumeration<String> headerNames = servletRequest.getHeaderNames();
					while (headerNames.hasMoreElements()) {
					    String headerName = headerNames.nextElement();
					    String headerValue = servletRequest.getHeader(headerName);
					    servletResponse.setHeader(headerName, headerValue);
					}

					responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseBody);
					// jwhong
				} else {
					responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData);
				}
			} else {
//                responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData);
//                버즈빌 포인트 적립 처리하기 위하여 정상응답이더라도 응답코드(apiRsltCd) 값이 200이 아닌 경우 처리를 위하여 수정
//                Filter에서 설정한 response status값을 responseEntity 생성시 적용 modify by lwk 2025.03.24

				servletResponse.addHeader("traceId",
						transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에
				int httpStatus = servletResponse.getStatus();
				if (httpStatus != 200) {
					responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData);
				} else {
					responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData);
				}
			}

		} catch (HttpStatusException e) {
			logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
			String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
					MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat);
			responseEntity = ResponseEntity.status(e.getStatus()).contentType(mediaType).body(errorMsg);
		} catch (JwtAuthException e) {
			logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
			String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
					MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage(), errorResponseFormat);
			responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg);
		} catch (Exception e) {
			logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
			String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
					MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat);
			responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType)
					.body(errorMsg);
		} finally {
			/**
			 * 로깅 인터셉터에 데이터를 전달하기 위한 처리 이미 거래처리가 끝나서 영향도가 없을만한 servletRequest에 Attribute로 전달
			 * 추후 더 좋은방법이 생길경우 개선 요망
			 */
			servletRequest.setAttribute(TransactionContextKeys.TRANSACTION_PROP, transactionProp);

			if (!"ASYNC".equals(responseType)) {
				String uuid = transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
				String url = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI);
				String method = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD);
				int httpStatusCode = servletResponse.getStatus();

				Map<Object, Object> headerMap = new HashMap<>();
				// 응답 헤더 로깅
				for (String headerName : servletResponse.getHeaderNames()) {
					String headerValue = servletResponse.getHeader(headerName);
					logger.debug(
							String.format("httpHeader logging headerKey=%s, headerValue=%s", headerName, headerValue));
					headerMap.put(headerName, headerValue);
				}
				HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName, adapterName, headerMap,
						url, method, httpStatusCode);
			}
		}
		return responseEntity;
	}

	/**
	 * url에서 뒤쪽 /이후를 제거하면서 찾는다.<br>
	 * /api/v1/public/getUserInfo.svc
	 *
	 * @param apiUri
	 * @return
	 */
	private HttpDynamicInAdapterUri findAdptUri(String apiUri, HttpDynamicInAdapterManager manager,
			String adapterGrpName) throws Exception {
		if (StringUtils.isBlank(apiUri)) {
			return null;
		}

		// * manager 출력
		logger.warn("===== adptUriMap 상세 내용 =====");
		for (Map.Entry<String, HttpDynamicInAdapterUri> entry : manager.adptUriMap.entrySet()) {
			HttpDynamicInAdapterUri uriObj = entry.getValue();
			logger.warn("GroupName=" + uriObj.getAdptGrpName() + ", AdapterName=" + uriObj.getAdptName() + ", URI="
					+ uriObj.getUri());
		}
		logger.warn("================================");

		//
		for (int i = 0; i < 10; i++) {
			HttpDynamicInAdapterUri adptUri = manager.getAdptUri(apiUri);
			if (adptUri != null) {
				if (adptUri.getAdptGrpName().equals(adapterGrpName)) {
					return adptUri;
				}
			}

			// /api/v1/public
			apiUri = StringUtils.substringBeforeLast(apiUri, "/");
			if (apiUri.length() < 4) { // /api
				return null;
			}
		}

		return null;
	}

	private void logError(HttpServletRequest request) {
		InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO();

		EAIServerManager eaiServerManager = EAIServerManager.getInstance();
		String serverName = eaiServerManager.getLocalServerName();
		String uuid = null;
		StringBuffer sb = new StringBuffer();

		String instanceid1 = serverName.substring(0, 2);
		String instanceid2 = serverName.substring(serverName.length() - 2, serverName.length());
		String instid = instanceid1 + instanceid2;
		uuid = instid + UUIDGenerator.getUUID();

		String[] msgArgs = new String[1];
		msgArgs[0] = request.getRequestURI();

		String errorCode = "RECEAIIRP010";
		String errorMsg = ExceptionUtil.make(new Exception("cat not find uri"), errorCode, msgArgs);

		errorInfoVO.setEaiSvcSno(uuid); // EAI서비스일련번호
		errorInfoVO.setAdptBwkGrpNm("HTTP_IN_NO_URI"); // 어댑터업무그룹명
		errorInfoVO.setErrCd(errorCode); // 에러코드
		errorInfoVO.setErrTxt(errorMsg); // 에러내용
		errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(new Date().getTime())); // 에러발생시각
		errorInfoVO.setErrDstcd(" ");
		sb.append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ")
				.append(request.getRequestURI());
		errorInfoVO.setBwkDataTxt(sb.toString()); // 업무데이터내용
		errorInfoVO.setEaiSvrInstNm(serverName); // EAI서버인스턴스명

		InboundErrorLogger.error(errorInfoVO);
	}

	private String makeResponseBodyMsg() {

		Map<String, Object> dataMap = new HashMap<>();
		dataMap.put("result", 1);
		Map<String, Object> map = new HashMap<>();

		map.put("code", "200");
		map.put("data", dataMap);
		map.put("message", "정상 처리 되었습니다.");

		JSONObject json = new JSONObject();
		json.putAll(map);

		return json.toJSONString();
	}

	/*
	 * private String makeResponseBodyMsg(String asyncMsgStyle) {
	 * 
	 * Map<String, Object> dataMap = new HashMap<>(); dataMap.put("result", 1);
	 * Map<String, Object> map = new HashMap<>();
	 * 
	 * switch (asyncMsgStyle) { case "TB_SUC": map.put("rspCode", "TB_SUC_000");
	 * map.put("rspMsg", "정상"); map.put("data", dataMap); break; case "ONLYDATA":
	 * map.put("data", dataMap); map.put("success", "true"); break; default:
	 * map.put("code", "200"); map.put("data", dataMap); map.put("message",
	 * "정상 처리 되었습니다."); break; }
	 * 
	 * JSONObject json = new JSONObject(); json.putAll(map);
	 * 
	 * return json.toJSONString(); }
	 */
}