package com.eactive.eai.rms.ext.djb.job;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.util.CommonUtil;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsHour;

/**
 * API 로그 테이블을 시간별 통계로 집계하는 배치 작업
 * TSEAILGXX → API_STATS_HOUR
 * 실행 주기: 매시 00:10, 직전 1시간(예: 14:10 실행 시 13:00~13:59:59) 데이터만 집계
 * (0시대 실행 시 전날 23:00~23:59:59 집계)
 */
@Component
public class ApiStatsHourlyAggregationJob implements Job {
	private static final Logger log = LoggerFactory.getLogger(ApiStatsHourlyAggregationJob.class);

	@PersistenceContext
	private EntityManager entityManager;

	private ApiStatsHourService apiStatsHourService;

	@Autowired
	public void setApiStatsHourService(ApiStatsHourService apiStatsHourService) {
		this.apiStatsHourService = apiStatsHourService;
	}

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		ApplicationContext appContext = null;
		final ApiStatsHourlyAggregationJob selfJob;
		try {
			appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
			selfJob = appContext.getBean(ApiStatsHourlyAggregationJob.class);
		} catch (SchedulerException e) {
			log.error("applicationContext get module error", e);
			return;
		}

		DataSourceType dataType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
		DataSourceContextHolder.setDataSourceType(dataType);
		try {
			// 직전 1시간(예: 14:10 실행 -> 13:00~13:59:59). 0시대 실행 시 자동으로 전날 23시로 롤오버됨
			LocalDateTime targetHour = LocalDateTime.now().minusHours(1).truncatedTo(ChronoUnit.HOURS);
			selfJob.executeManual(targetHour);
		} catch (Exception e) {
			throw new JobExecutionException(e);
		} finally {
			DataSourceContextHolder.clearDataSourceType();
		}
	}

	/**
	 * 수동 실행 메서드 (날짜 지정, 하루 전체 재집계 - 관리자 화면 백필용)
	 */
	@Transactional
	public int executeManual(LocalDate targetDate) {
		LocalDateTime rangeStart = targetDate.atStartOfDay();
		return aggregate(rangeStart, rangeStart.plusDays(1));
	}

	/**
	 * 수동/스케줄 실행 메서드 (시각 지정, 해당 시각이 속한 1시간만 집계)
	 */
	@Transactional
	public int executeManual(LocalDateTime targetHour) {
		LocalDateTime rangeStart = targetHour.truncatedTo(ChronoUnit.HOURS);
		return aggregate(rangeStart, rangeStart.plusHours(1));
	}

	private int aggregate(LocalDateTime rangeStart, LocalDateTime rangeEnd) {
		long startTime = System.currentTimeMillis();
		log.info("=== 시간별 통계 집계 작업 시작 === 대상: {} ~ {}", rangeStart, rangeEnd);

		DateTimeFormatter tsFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
		String logTableName = CommonUtil.getLogTable(rangeStart.format(DateTimeFormatter.ofPattern("yyyyMMdd")), true);
		String rangeStartStr = rangeStart.format(tsFormat) + "000";
		String rangeEndStr = rangeEnd.format(tsFormat) + "000";

		// 1. 대상 구간(rangeStart ~ rangeEnd)의 기존 데이터만 삭제 (다른 시간대 데이터는 보존)
		QApiStatsHour q = QApiStatsHour.apiStatsHour;
		long deletedCount = apiStatsHourService.getJPAQueryFactory()
				.delete(q)
				.where(q.statTime.goe(rangeStart)
					.and(q.statTime.lt(rangeEnd)))
				.execute();
		log.info("기존 데이터 삭제: {} 건", deletedCount);

		// 2. 집계 및 데이터 입력
		/**
		 * EAISVCSERNO로 그룹핑하여 1행으로 집계를 한 다음 (한거래 안에서 adapter가 여러개 있을 경우 seq=100의 adapter를 기준)  
		 * 총건수 : EAISVCSERNO로 그룹핑한 총 건수 
		 * 성공건수 : seq = 400 and EAIERRCD = null 인 건수
		 * Timeout : EAIERRCD in (공통코드 CODEGROUP = 'ERRCODE_TIMEOUT') 인 건수
		 * 시스템오류 : (seq400 = null or EAIERRCD is not null) and 공통코드(ERRCODE_TIMEOUT)에 정의되지 않은 errorcode 인 건수
		 */
		String sql =
				"INSERT INTO API_STATS_HOUR" +
				" (STAT_TIME, API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, CLIENT_ID" +
				" , INBOUND_ADAPTER, OUTBOUND_ADAPTER" +
				", TOTAL_CNT, SUCCESS_CNT, TIMEOUT_CNT, SYSTEM_ERR_CNT, BIZ_ERR_CNT" +
				", SEQ900_TIMEOUT_CNT, SEQ900_SYSTEM_ERR_CNT, SEQ900_BIZ_ERR_CNT" +
				", AVG_RESP_TIME, MIN_RESP_TIME, MAX_RESP_TIME, P50_RESP_TIME, P95_RESP_TIME)" +
				" SELECT TO_TIMESTAMP(SUBSTR(DT,1,10) || '0000', 'YYYYMMDDHH24MISS')" +
				"      , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" +
				"      , INBOUND_ADAPTER, OUTBOUND_ADAPTER" +
				"      , COUNT(EAISVCSERNO)" +
				"      , SUM(CASE WHEN A.ERROR_CODE IS NULL AND E400 IS NOT NULL THEN 1 ELSE 0 END)" +
				"      , SUM(CASE WHEN B.CODE IS NOT NULL THEN 1 ELSE 0 END)" +
				"      , SUM(CASE WHEN (A.E400 IS NULL OR A.ERROR_CODE IS NOT NULL) AND B.CODE IS NULL THEN 1 ELSE 0 END)" +
				"      , 0, 0, 0, 0" +
				"      , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" +
				"   FROM (" +
				"         SELECT EAISVCSERNO" +
				"             , MAX(EAISVCNAME) AS API_NAME, MAX(EAISEVRINSTNCNAME) AS GW_INSTANCE_ID" +
				"             , MAX(EAIBZWKDSTCD) AS BIZ_DIV_CODE, MAX(CLIENTID) AS CLIENT_ID" +
				"             , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN GSTATSYSADPTRBZWKGROUPNAME ELSE '' END) AS INBOUND_ADAPTER" +
				"             , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN PSVSYSADPTRBZWKGROUPNAME ELSE '' END) AS OUTBOUND_ADAPTER" +
				"             , MIN(MSGDPSTYMS) AS DT" +
				"             , ( (TO_DATE(SUBSTR(MAX(MSGPRCSSYMS),1,14),'YYYYMMDDHH24MISS')" +
				"                  - TO_DATE(SUBSTR(MIN(MSGPRCSSYMS),1,14),'YYYYMMDDHH24MISS')) * 86400000" +
				"                + TO_NUMBER(SUBSTR(MAX(MSGPRCSSYMS),15,3))" +
				"                - TO_NUMBER(SUBSTR(MIN(MSGPRCSSYMS),15,3))" +
				"               ) AS RESP_TIME" +
				"             , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN LOGPRCSSSERNO ELSE '' END) AS E400" +
				"             , MAX(EAIERRCD) AS ERROR_CODE" +
				"         FROM " + logTableName +
				"         WHERE MSGDPSTYMS >= :rangeStart AND MSGDPSTYMS < :rangeEnd" +
				"         GROUP BY EAISVCSERNO" +
				"       ) A LEFT OUTER JOIN TSEAICM20 B ON A.ERROR_CODE = B.CODE AND B.CODEGROUP = 'ERRCODE_TIMEOUT' AND B.USEYN = 'Y' " +
				"  GROUP BY SUBSTR(DT,1,10), API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" +
				"         , INBOUND_ADAPTER, OUTBOUND_ADAPTER";

		int savedCount = entityManager.createNativeQuery(sql)
				.setParameter("rangeStart", rangeStartStr)
				.setParameter("rangeEnd", rangeEndStr)
				.executeUpdate();

		long elapsedTime = System.currentTimeMillis() - startTime;
		log.info("=== 집계 완료 === (처리 건수: {}, 소요 시간: {}ms)", savedCount, elapsedTime);
		return savedCount;
	}
}
