package com.eactive.eai.rms.data.entity.onl.kjb.statistics;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.eactive.eai.data.jpa.AbstractDataService;
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;

@Service
@Transactional
public class ApiStatsDayService
		extends AbstractDataService<ApiStatsDay, ApiStatsDayId, ApiStatsDayRepository> {

	private static final int MAX_DAYS = 31;

	public Page<ApiStatsDay> selectList(ApiStatsSearch search, Pageable pageable) {
		QApiStatsDay q = QApiStatsDay.apiStatsDay;
		BooleanBuilder builder = buildSearchConditions(q, search);
		return repository.findAll(builder, pageable);
	}

	public List<ApiStatsDay> selectListForExcel(ApiStatsSearch search) {
		QApiStatsDay q = QApiStatsDay.apiStatsDay;
		BooleanBuilder builder = buildSearchConditions(q, search);

		return getJPAQueryFactory()
				.selectFrom(q)
				.where(builder)
				.orderBy(q.statTime.desc())
				.fetch();
	}

	public List<ApiStatsChartUI> selectChartData(ApiStatsSearch search) {
		QApiStatsDay q = QApiStatsDay.apiStatsDay;
		BooleanBuilder builder = new BooleanBuilder();

		calculateDateRange(search);
		if (search.getParsedStartDate() != null) {
			builder.and(q.statTime.goe(search.getParsedStartDate()));
			builder.and(q.statTime.loe(search.getParsedEndDate()));
		}

		if (StringUtils.isNotBlank(search.getSearchApiName())) {
			builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
		}

		if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
			builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
		}

		if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
			builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
		}

		if (StringUtils.isNotBlank(search.getSearchClientId())) {
			builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
		}

		return getJPAQueryFactory()
				.select(Projections.constructor(ApiStatsChartUI.class,
						q.statTime,
						q.totalCnt.sum(),
						q.successCnt.sum(),
						q.timeoutCnt.sum(),
						q.systemErrCnt.sum(),
						q.bizErrCnt.sum(),
						q.seq900TimeoutCnt.sum(),
						q.seq900SystemErrCnt.sum(),
						q.seq900BizErrCnt.sum(),
						q.avgRespTime.avg(),
						q.p50RespTime.avg(),
						q.p95RespTime.avg()))
				.from(q)
				.where(builder)
				.groupBy(q.statTime)
				.orderBy(q.statTime.asc())
				.fetch();
	}

	/**
	 * API명별 요약 통계 조회 (GROUP BY apiName)
	 */
	public Page<ApiStatsUI> selectSummaryList(ApiStatsSearch search, Pageable pageable) {
		QApiStatsDay q = QApiStatsDay.apiStatsDay;
		BooleanBuilder builder = buildSearchConditions(q, search);

		// 전체 건수 조회
		Long total = getJPAQueryFactory()
				.select(q.apiName.countDistinct())
				.from(q)
				.where(builder)
				.fetchOne();

		// 페이징 적용하여 조회
		List<ApiStatsUI> results = getJPAQueryFactory()
				.select(Projections.fields(ApiStatsUI.class,
						q.apiName,
						q.totalCnt.sum().as("totalCnt"),
						q.successCnt.sum().as("successCnt"),
						q.timeoutCnt.sum().as("timeoutCnt"),
						q.systemErrCnt.sum().as("systemErrCnt"),
						q.bizErrCnt.sum().as("bizErrCnt"),
						q.seq900TimeoutCnt.sum().as("seq900TimeoutCnt"),
						q.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
						q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
						q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
						q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
						// 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt)
						Expressions.cases()
								.when(q.totalCnt.sum().gt(0))
								.then(q.avgRespTime.multiply(q.totalCnt).sum()
										.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
								.otherwise((BigDecimal) null)
								.as("avgRespTime")))
				.from(q)
				.where(builder)
				.groupBy(q.apiName)
				.orderBy(q.apiName.asc())
				.offset(pageable.getOffset())
				.limit(pageable.getPageSize())
				.fetch();

		// 소수점 3자리로 반올림
		results.forEach(ui -> {
			if (ui.getAvgRespTime() != null) {
				ui.setAvgRespTime(ui.getAvgRespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (ui.getMinRespTime() != null) {
				ui.setMinRespTime(ui.getMinRespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (ui.getMaxRespTime() != null) {
				ui.setMaxRespTime(ui.getMaxRespTime().setScale(3, RoundingMode.HALF_UP));
			}
		});

		return new PageImpl<>(results, pageable, total != null ? total.longValue() : 0);
	}

	/**
	 * API명별 요약 통계 전체 조회 (Excel용)
	 */
	public List<ApiStatsUI> selectSummaryListForExcel(ApiStatsSearch search) {
		QApiStatsDay q = QApiStatsDay.apiStatsDay;
		BooleanBuilder builder = buildSearchConditions(q, search);

		List<ApiStatsUI> results = getJPAQueryFactory()
				.select(Projections.fields(ApiStatsUI.class,
						q.apiName,
						q.totalCnt.sum().as("totalCnt"),
						q.successCnt.sum().as("successCnt"),
						q.timeoutCnt.sum().as("timeoutCnt"),
						q.systemErrCnt.sum().as("systemErrCnt"),
						q.bizErrCnt.sum().as("bizErrCnt"),
						q.seq900TimeoutCnt.sum().as("seq900TimeoutCnt"),
						q.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
						q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
						q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
						q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
						Expressions.cases()
								.when(q.successCnt.sum().gt(0))
								.then(q.avgRespTime.multiply(q.successCnt).sum()
										.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
								.otherwise((BigDecimal) null)
								.as("avgRespTime")))
				.from(q)
				.where(builder)
				.groupBy(q.apiName)
				.orderBy(q.apiName.asc())
				.fetch();

		// 소수점 3자리로 반올림
		results.forEach(ui -> {
			if (ui.getAvgRespTime() != null) {
				ui.setAvgRespTime(ui.getAvgRespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (ui.getMinRespTime() != null) {
				ui.setMinRespTime(ui.getMinRespTime().setScale(3, RoundingMode.HALF_UP));
			}
			if (ui.getMaxRespTime() != null) {
				ui.setMaxRespTime(ui.getMaxRespTime().setScale(3, RoundingMode.HALF_UP));
			}
		});

		return results;
	}

	private BooleanBuilder buildSearchConditions(QApiStatsDay q, ApiStatsSearch search) {
		BooleanBuilder builder = new BooleanBuilder();

		calculateDateRange(search);
		if (search.getParsedStartDate() != null) {
			builder.and(q.statTime.goe(search.getParsedStartDate()));
			builder.and(q.statTime.loe(search.getParsedEndDate()));
		}

		if (StringUtils.isNotBlank(search.getSearchApiName())) {
			builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
		}

		if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
			builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
		}

		if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
			builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
		}

		if (StringUtils.isNotBlank(search.getSearchClientId())) {
			builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
		}

		if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
			builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
		}

		if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
			builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
		}

		return builder;
	}

	/**
	 * 조회 기간 계산 (최대 31일 제한)
	 */
	private void calculateDateRange(ApiStatsSearch search) {
		if (StringUtils.isBlank(search.getSearchStartDateTime())) {
			return;
		}

		LocalDate startDate = LocalDate.parse(search.getSearchStartDateTime(),
				DateTimeFormatter.ofPattern("yyyyMMdd"));
		LocalDate endDate;

		if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
			endDate = LocalDate.parse(search.getSearchEndDateTime(),
					DateTimeFormatter.ofPattern("yyyyMMdd"));
			// 31일 초과 시 시작날짜 기준 31일 후로 강제 설정
			if (java.time.Period.between(startDate, endDate).getDays() > MAX_DAYS) {
				endDate = startDate.plusDays(MAX_DAYS);
			}
		} else {
			endDate = startDate.plusDays(MAX_DAYS);
		}

		search.setParsedStartDate(startDate);
		search.setParsedEndDate(endDate);
	}
}
