package com.eactive.eai.custom.statistics.collector;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Service;

import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.statistics.config.ApiStatsConfig;
import com.eactive.eai.custom.statistics.dto.HourStatsKey;
import com.eactive.eai.custom.statistics.dto.MinuteStatsKey;
import com.eactive.eai.custom.statistics.dto.TransactionEvent;
import com.eactive.eai.data.entity.custom.statistics.ApiStatsHour;
import com.eactive.eai.data.entity.custom.statistics.ApiStatsMinute;

import lombok.RequiredArgsConstructor;

/**
 * API 통계 수집기
 * - 거래 이벤트를 분/시간 단위로 메모리에 집계
 * - Thread-safe한 ConcurrentHashMap 사용
 */
@Service
@RequiredArgsConstructor
public class ApiStatsCollector {
	
	private static Logger log = Logger.getLogger(Logger.LOGGER_DEFAULT);

	/** 최대 Aggregator 수 제한 (메모리 보호) */
	private static final int MAX_MINUTE_AGGREGATORS = 10000;
	private static final int MAX_HOUR_AGGREGATORS = 5000;

	/**
	 * 분단위 집계 맵
	 * Key: MinuteStatsKey (시간 + 차원들)
	 * Value: MinuteStatsAggregator (집계 데이터)
	 */
	private final ConcurrentHashMap<MinuteStatsKey, MinuteStatsAggregator> minuteAggregators = new ConcurrentHashMap<>();

	/**
	 * 시간별 집계 맵
	 * Key: HourStatsKey (시간 + 차원들)
	 * Value: HourStatsAggregator (집계 데이터)
	 */
	private final ConcurrentHashMap<HourStatsKey, HourStatsAggregator> hourAggregators = new ConcurrentHashMap<>();

	private final ApiStatsConfig apiStatsConfig;
	
	/**
	 * 거래 이벤트 기록
	 *
	 * @param event 거래 이벤트
	 */
	public void recordEvent(TransactionEvent event) {
		recordEvent(event, LocalDateTime.now());
	}

	/**
	 * 거래 이벤트 기록 (시간 지정)
	 *
	 * @param event 거래 이벤트
	 * @param eventTime 이벤트 발생 시간
	 */
	public void recordEvent(TransactionEvent event, LocalDateTime eventTime) {
		boolean minuteEnabled = apiStatsConfig.isMinuteEnabled();
		boolean hourEnabled = apiStatsConfig.isHourEnabled();

		if (!minuteEnabled && !hourEnabled) {
			log.debug("API stats collection is disabled (both minute and hour)");
			return;
		}

		if (minuteEnabled) {
			recordMinuteEvent(event, eventTime);
		}

		if (hourEnabled) {
			recordHourEvent(event, eventTime);
		}
	}

	private void recordMinuteEvent(TransactionEvent event, LocalDateTime eventTime) {
		try {
			if (minuteAggregators.size() >= MAX_MINUTE_AGGREGATORS) {
				log.warn("Minute aggregator limit reached ({}), forcing flush of completed minutes", MAX_MINUTE_AGGREGATORS);
				flushCompletedMinutes();

				if (minuteAggregators.size() >= MAX_MINUTE_AGGREGATORS) {
					log.error("Minute aggregator count still exceeds limit after flush: {}", minuteAggregators.size());
				}
			}

			MinuteStatsKey minuteKey = MinuteStatsKey.from(event, eventTime);
			MinuteStatsAggregator minuteAggregator = minuteAggregators.computeIfAbsent(
				minuteKey,
				MinuteStatsAggregator::new
			);
			minuteAggregator.recordEvent(event);

			log.debug("Recorded minute event: key={}, total={}", minuteKey, minuteAggregator.getTotalCount());
		} catch (Exception e) {
			log.error("Failed to record minute event: {}", event, e);
		}
	}

	private void recordHourEvent(TransactionEvent event, LocalDateTime eventTime) {
		try {
			if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) {
				log.warn("Hour aggregator limit reached ({}), forcing flush of completed hours", MAX_HOUR_AGGREGATORS);
				flushCompletedHours();

				if (hourAggregators.size() >= MAX_HOUR_AGGREGATORS) {
					log.error("Hour aggregator count still exceeds limit after flush: {}", hourAggregators.size());
				}
			}

			HourStatsKey hourKey = HourStatsKey.from(event, eventTime);
			HourStatsAggregator hourAggregator = hourAggregators.computeIfAbsent(
				hourKey,
				HourStatsAggregator::new
			);
			hourAggregator.recordEvent(event);

			log.debug("Recorded hour event: key={}, total={}", hourKey, hourAggregator.getTotalCount());
		} catch (Exception e) {
			log.error("Failed to record hour event: {}", event, e);
		}
	}

	/**
	 * 지정된 시간 이전의 분단위 집계 데이터를 flush하여 반환
	 * - 반환된 데이터는 메모리에서 제거됨
	 *
	 * @param beforeTime 이 시간 이전의 데이터를 flush (exclusive)
	 * @return 집계된 통계 리스트
	 */
	public List<ApiStatsMinute> flushMinuteBefore(LocalDateTime beforeTime) {
		LocalDateTime cutoffTime = beforeTime.truncatedTo(ChronoUnit.MINUTES);
		List<ApiStatsMinute> result = new ArrayList<>();

		// 대상 키 수집 및 스냅샷 생성
		List<MinuteStatsKey> keysToRemove = new ArrayList<>();

		minuteAggregators.forEach((key, aggregator) -> {
			if (key.getStatTime().isBefore(cutoffTime)) {
				// 스냅샷 생성
				ApiStatsMinute snapshot = aggregator.snapshot();
				result.add(snapshot);
				keysToRemove.add(key);
			}
		});

		// 메모리에서 제거
		keysToRemove.forEach(minuteAggregators::remove);

		log.info("Flushed {} minute stats records before {}, remaining: {}",
			result.size(), cutoffTime, minuteAggregators.size());

		return result;
	}

	/**
	 * 지정된 시간 이전의 시간별 집계 데이터를 flush하여 반환
	 * - 반환된 데이터는 메모리에서 제거됨
	 *
	 * @param beforeTime 이 시간 이전의 데이터를 flush (exclusive)
	 * @return 집계된 통계 리스트
	 */
	public List<ApiStatsHour> flushHourBefore(LocalDateTime beforeTime) {
		LocalDateTime cutoffTime = beforeTime.truncatedTo(ChronoUnit.HOURS);
		List<ApiStatsHour> result = new ArrayList<>();

		// 대상 키 수집 및 스냅샷 생성
		List<HourStatsKey> keysToRemove = new ArrayList<>();

		hourAggregators.forEach((key, aggregator) -> {
			if (key.getStatTime().isBefore(cutoffTime)) {
				// 스냅샷 생성
				ApiStatsHour snapshot = aggregator.snapshot();
				result.add(snapshot);
				keysToRemove.add(key);
			}
		});

		// 메모리에서 제거
		keysToRemove.forEach(hourAggregators::remove);

		log.info("Flushed {} hour stats records before {}, remaining: {}",
			result.size(), cutoffTime, hourAggregators.size());

		return result;
	}

	/**
	 * 완료된 분단위 집계 데이터를 flush
	 * - 현재 분 이전의 모든 데이터를 반환하고 제거
	 */
	public List<ApiStatsMinute> flushCompletedMinutes() {
		// 현재 분은 아직 진행 중이므로 제외
		LocalDateTime now = LocalDateTime.now();
		LocalDateTime currentMinute = now.truncatedTo(ChronoUnit.MINUTES);

		return flushMinuteBefore(currentMinute);
	}

	/**
	 * 완료된 시간별 집계 데이터를 flush
	 * - 현재 시간 이전의 모든 데이터를 반환하고 제거
	 */
	public List<ApiStatsHour> flushCompletedHours() {
		// 현재 시간은 아직 진행 중이므로 제외
		LocalDateTime now = LocalDateTime.now();
		LocalDateTime currentHour = now.truncatedTo(ChronoUnit.HOURS);

		return flushHourBefore(currentHour);
	}

	/**
	 * 모든 분단위 집계 데이터를 강제로 flush (종료 시 사용)
	 */
	public List<ApiStatsMinute> flushAllMinutes() {
		List<ApiStatsMinute> result = new ArrayList<>();

		minuteAggregators.forEach((key, aggregator) -> {
			result.add(aggregator.snapshot());
		});

		minuteAggregators.clear();

		log.info("Flushed all {} minute stats records", result.size());

		return result;
	}

	/**
	 * 모든 시간별 집계 데이터를 강제로 flush (종료 시 사용)
	 */
	public List<ApiStatsHour> flushAllHours() {
		List<ApiStatsHour> result = new ArrayList<>();

		hourAggregators.forEach((key, aggregator) -> {
			result.add(aggregator.snapshot());
		});

		hourAggregators.clear();

		log.info("Flushed all {} hour stats records", result.size());

		return result;
	}

	/**
	 * 현재 집계 중인 분단위 키 개수 반환 (모니터링용)
	 */
	public int getActiveMinuteAggregatorCount() {
		return minuteAggregators.size();
	}

	/**
	 * 현재 집계 중인 시간별 키 개수 반환 (모니터링용)
	 */
	public int getActiveHourAggregatorCount() {
		return hourAggregators.size();
	}
}
