package com.eactive.ext.kjb.statistics.job;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

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.data.entity.onl.kjb.statistics.ApiStatsDay;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsDay;
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsHour;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;

/**
 * 시간별 통계를 일별 통계로 집계하는 배치 작업
 * API_STATS_HOUR → API_STATS_DAY
 * 실행 주기: 매일 00:10 (전일 데이터 집계)
 */
@Component
public class HourlyToDailyAggregationJob implements Job {
	private static final Logger log = LoggerFactory.getLogger(HourlyToDailyAggregationJob.class);

	private ApiStatsHourService apiStatsHourService;
	private ApiStatsDayService apiStatsDayService;

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

	@Autowired
	public void setApiStatsDayService(ApiStatsDayService apiStatsDayService) {
		this.apiStatsDayService = apiStatsDayService;
	}

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
   		ApplicationContext appContext = null;
   		final HourlyToDailyAggregationJob selfJob;
   		try {
			appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
			selfJob = appContext.getBean(HourlyToDailyAggregationJob.class);
   		} catch (SchedulerException e) {
   			log.error("applicationContext get module error",e);
   			return ;
   		}
		
		DataSourceType dataType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
  		DataSourceContextHolder.setDataSourceType(dataType);
		try {
			LocalDate targetDate = LocalDate.now().minusDays(1);
			selfJob.executeManual(targetDate);
		} catch (Exception e) {
			throw new JobExecutionException(e);
		} finally {
			DataSourceContextHolder.clearDataSourceType();
		}
	}

	/**
	 * 수동 실행 메서드 (날짜 지정)
	 */
	@Transactional
	public int executeManual(LocalDate targetDate) {
		long startTime = System.currentTimeMillis();
		log.info("=== 시간별→일별 통계 집계 작업 시작 ===");

		LocalDateTime startDateTime = targetDate.atStartOfDay();
		LocalDateTime endDateTime = targetDate.plusDays(1).atStartOfDay().minusSeconds(1);

		log.info("집계 대상 기간: {} ~ {}", startDateTime, endDateTime);

		List<ApiStatsDay> aggregatedData = aggregateHourlyToDaily(startDateTime, endDateTime, targetDate);

		// 1. 기존 데이터 삭제 (QueryDSL 사용)
		JPAQueryFactory queryFactory = apiStatsDayService.getJPAQueryFactory();
		QApiStatsDay qd = QApiStatsDay.apiStatsDay;
		long deletedCount = queryFactory
			.delete(qd)
			.where(qd.statTime.eq(targetDate))
			.execute();
		log.info("기존 데이터 삭제: {} 건", deletedCount);

		// 2. 배치 INSERT
		apiStatsDayService.saveAll(aggregatedData);
		int savedCount = aggregatedData.size();

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

		return savedCount;
	}

	/**
	 * 시간별 데이터를 일별로 집계
	 */
	private List<ApiStatsDay> aggregateHourlyToDaily(LocalDateTime startDateTime, LocalDateTime endDateTime, LocalDate targetDate) {
		QApiStatsHour qh = QApiStatsHour.apiStatsHour;
		JPAQueryFactory queryFactory = apiStatsHourService.getJPAQueryFactory();

		List<ApiStatsDay> aggregatedData = queryFactory
				.select(Projections.bean(ApiStatsDay.class,
						qh.apiName,
						qh.gwInstanceId,
						qh.bizDivCode,
						qh.clientId,
						qh.inboundAdapter,
						qh.outboundAdapter,
						qh.totalCnt.sum().as("totalCnt"),
						qh.successCnt.sum().as("successCnt"),
						qh.timeoutCnt.sum().as("timeoutCnt"),
						qh.systemErrCnt.sum().as("systemErrCnt"),
						qh.bizErrCnt.sum().as("bizErrCnt"),
						qh.seq900TimeoutCnt.sum().as("seq900TimeoutCnt"),
						qh.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
						qh.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
						Expressions.cases()
				.when(qh.totalCnt.sum().gt(0))
				.then(qh.avgRespTime.multiply(qh.totalCnt).sum()
						.divide(qh.totalCnt.sum()).castToNum(BigDecimal.class))
				.otherwise((BigDecimal) null)
				.as("avgRespTime"),
						qh.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
						qh.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
						Expressions.cases()
				.when(qh.successCnt.sum().gt(0))
				.then(qh.p50RespTime.multiply(qh.successCnt).sum()
						.divide(qh.successCnt.sum()).castToNum(BigDecimal.class))
				.otherwise((BigDecimal) null)
				.as("p50RespTime"),
						Expressions.cases()
				.when(qh.successCnt.sum().gt(0))
				.then(qh.p95RespTime.multiply(qh.successCnt).sum()
						.divide(qh.successCnt.sum()).castToNum(BigDecimal.class))
				.otherwise((BigDecimal) null)
				.as("p95RespTime")))
				.from(qh)
				.where(qh.statTime.goe(startDateTime)
						.and(qh.statTime.loe(endDateTime)))
				.groupBy(qh.apiName, qh.gwInstanceId, qh.bizDivCode, qh.clientId, qh.inboundAdapter, qh.outboundAdapter)
				.fetch();

		for (ApiStatsDay statsDay : aggregatedData) {
			statsDay.setStatTime(targetDate);
			if (statsDay.getAvgRespTime() != null) {
				statsDay.setAvgRespTime(statsDay.getAvgRespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (statsDay.getP50RespTime() != null) {
				statsDay.setP50RespTime(statsDay.getP50RespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (statsDay.getP95RespTime() != null) {
				statsDay.setP95RespTime(statsDay.getP95RespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (statsDay.getMinRespTime() == null) {
				statsDay.setMinRespTime(BigDecimal.ZERO);
			}
			if (statsDay.getMaxRespTime() == null) {
				statsDay.setMaxRespTime(BigDecimal.ZERO);
			}
		}

		return aggregatedData;
	}
}
