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

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.atomic.AtomicLong;

import com.eactive.eai.custom.statistics.dto.MinuteStatsKey;
import com.eactive.eai.custom.statistics.dto.TransactionEvent;
import com.eactive.eai.data.entity.custom.statistics.ApiStatsMinute;

import lombok.Getter;

/**
 * 분단위 통계 집계기
 * - Bucket 방식으로 응답시간 백분위 계산 (메모리 최적화)
 * - Thread-safe한 집계 처리
 */
public class MinuteStatsAggregator {

	@Getter
	private final MinuteStatsKey key;

	// 건수 메트릭
	private final AtomicLong totalCnt = new AtomicLong(0);
	private final AtomicLong successCnt = new AtomicLong(0);
	private final AtomicLong timeoutCnt = new AtomicLong(0);
	private final AtomicLong systemErrCnt = new AtomicLong(0);
	private final AtomicLong bizErrCnt = new AtomicLong(0);

	// 로그 시퀀스 900 구간 에러 카운트
	private final AtomicLong seq900TimeoutCnt = new AtomicLong(0);
	private final AtomicLong seq900SystemErrCnt = new AtomicLong(0);
	private final AtomicLong seq900BizErrCnt = new AtomicLong(0);

	// 응답시간 Bucket (10개 구간으로 분류)
	private final AtomicLong[] respTimeBuckets = new AtomicLong[10];

	// Bucket 경계값 (ms)
	private static final long[] BUCKET_BOUNDARIES = {
		20, 50, 70, 100,          // 빠른 구간 (0-100ms)
		200, 300, 500,            // 일반 구간 (100-500ms) - P95 주로 여기
		1000, 5000, 60000         // 느린 구간 (500ms-60s)
	};

	// 응답시간 통계용
	private final AtomicLong totalRespTime = new AtomicLong(0); // 평균 계산용
	private final AtomicLong minRespTime = new AtomicLong(Long.MAX_VALUE);
	private final AtomicLong maxRespTime = new AtomicLong(Long.MIN_VALUE);

	public MinuteStatsAggregator(MinuteStatsKey key) {
		this.key = key;
		// Bucket 초기화
		for (int i = 0; i < respTimeBuckets.length; i++) {
			respTimeBuckets[i] = new AtomicLong(0);
		}
	}

	/**
	 * 거래 이벤트 기록
	 */
	public void recordEvent(TransactionEvent event) {
		int logSeq = event.getLogSequence();

		// 로그 시퀀스 100: 총 건수만 증가 (첫 호출)
		if (logSeq == 100) {
//			totalCnt.incrementAndGet();
		}
		// 로그 시퀀스 400: 응답시간 및 에러 타입 기록
		else if (logSeq == 400) {
			totalCnt.incrementAndGet();
			// 성공 여부 카운트
			if (event.isSuccess()) {
				successCnt.incrementAndGet();
			} else {
				// 에러 유형별 카운트
				switch (event.getErrorType()) {
					case TIMEOUT:
						timeoutCnt.incrementAndGet();
						break;
					case SYSTEM_ERROR:
						systemErrCnt.incrementAndGet();
						break;
					case BUSINESS_ERROR:
						bizErrCnt.incrementAndGet();
						break;
				}
			}

			// 응답시간 기록 (400 구간만)
			long respTime = event.getResponseTimeMs();
			totalRespTime.addAndGet(respTime);

			// Bucket에 기록
			int bucketIndex = findBucketIndex(respTime);
			respTimeBuckets[bucketIndex].incrementAndGet();

			// Min/Max 업데이트
			updateMinMax(respTime);
		}
		// 로그 시퀀스 900: 900 전용 에러 카운트만 집계
		else if (logSeq == 900 && !event.isSuccess()) {
			switch (event.getErrorType()) {
				case TIMEOUT:
					seq900TimeoutCnt.incrementAndGet();
					break;
				case SYSTEM_ERROR:
					seq900SystemErrCnt.incrementAndGet();
					break;
				case BUSINESS_ERROR:
					seq900BizErrCnt.incrementAndGet();
					break;
			}
		}
		// 그 외 로그 시퀀스는 무시
	}

	/**
	 * Min/Max 응답시간 업데이트 (thread-safe, lock-free)
	 */
	private void updateMinMax(long respTime) {
		// Min 업데이트 (compare-and-swap)
		long currentMin;
		while ((currentMin = minRespTime.get()) > respTime) {
			if (minRespTime.compareAndSet(currentMin, respTime)) {
				break;
			}
		}

		// Max 업데이트 (compare-and-swap)
		long currentMax;
		while ((currentMax = maxRespTime.get()) < respTime) {
			if (maxRespTime.compareAndSet(currentMax, respTime)) {
				break;
			}
		}
	}

	/**
	 * 응답시간에 해당하는 Bucket 인덱스 찾기
	 */
	private int findBucketIndex(long respTime) {
		for (int i = 0; i < BUCKET_BOUNDARIES.length; i++) {
			if (respTime <= BUCKET_BOUNDARIES[i]) {
				return i;
			}
		}
		return BUCKET_BOUNDARIES.length - 1; // 마지막 경계값 초과 시 마지막 bucket
	}

	/**
	 * Bucket 기반 백분위 계산
	 * @param percentile 백분위 (50.0 for P50, 95.0 for P95)
	 * @return 백분위 응답시간 (ms)
	 */
	private long calculatePercentile(double percentile) {
		long avgCount = getAvgCount();
		if (avgCount == 0) {
			return 0;
		}

		long targetCount = (long) Math.ceil(avgCount * percentile / 100.0);
		long accumulated = 0;

		for (int i = 0; i < respTimeBuckets.length; i++) {
			accumulated += respTimeBuckets[i].get();
			if (accumulated >= targetCount) {
				// 해당 bucket의 상한값 반환
				return BUCKET_BOUNDARIES[i];
			}
		}

		// 모든 bucket을 넘어간 경우
		return BUCKET_BOUNDARIES[BUCKET_BOUNDARIES.length - 1];
	}

	/**
	 * 현재 집계 결과를 Entity로 변환
	 */
	public ApiStatsMinute snapshot() {
		ApiStatsMinute stats = new ApiStatsMinute();

		// 키 정보
		stats.setStatTime(key.getStatTime());
		stats.setApiName(key.getApiName());
		stats.setGwInstanceId(key.getGwInstanceId());
		stats.setBizDivCode(key.getBizDivCode());
		stats.setClientId(key.getClientId());
		stats.setInboundAdapter(key.getInboundAdapter());
		stats.setOutboundAdapter(key.getOutboundAdapter());

		// 건수 메트릭
		long total = totalCnt.get();
		long success = successCnt.get();
		long timeout = timeoutCnt.get();
		long systemErr = systemErrCnt.get();
		long bizErr = bizErrCnt.get();
		long avgCount = success + timeout + systemErr + bizErr;

		stats.setTotalCnt(total);
		stats.setSuccessCnt(success);
		stats.setTimeoutCnt(timeout);
		stats.setSystemErrCnt(systemErr);
		stats.setBizErrCnt(bizErr);

		// 로그 시퀀스 900 구간 에러 카운트
		stats.setSeq900TimeoutCnt(seq900TimeoutCnt.get());
		stats.setSeq900SystemErrCnt(seq900SystemErrCnt.get());
		stats.setSeq900BizErrCnt(seq900BizErrCnt.get());

		// 응답시간 메트릭
		if (avgCount > 0 && totalRespTime.get() > 0) {
			// 응답시간이 실제로 기록된 경우만 처리
			double avg = (double) totalRespTime.get() / avgCount;
			stats.setAvgRespTime(BigDecimal.valueOf(avg).setScale(3, RoundingMode.HALF_UP));

			// Min/Max (totalRespTime > 0이면 반드시 갱신됨)
			stats.setMinRespTime(BigDecimal.valueOf(minRespTime.get()).setScale(3, RoundingMode.HALF_UP));
			stats.setMaxRespTime(BigDecimal.valueOf(maxRespTime.get()).setScale(3, RoundingMode.HALF_UP));

			// 백분위 (Bucket 기반 계산)
			long p50 = calculatePercentile(50.0);
			long p95 = calculatePercentile(95.0);
			stats.setP50RespTime(BigDecimal.valueOf(p50).setScale(3, RoundingMode.HALF_UP));
			stats.setP95RespTime(BigDecimal.valueOf(p95).setScale(3, RoundingMode.HALF_UP));
		} else {
			// 응답시간이 기록되지 않음 (logSeq 400 없음)
			stats.setAvgRespTime(BigDecimal.ZERO);
			stats.setMinRespTime(BigDecimal.ZERO);
			stats.setMaxRespTime(BigDecimal.ZERO);
			stats.setP50RespTime(BigDecimal.ZERO);
			stats.setP95RespTime(BigDecimal.ZERO);
		}

		return stats;
	}

	/**
	 * 집계된 총 건수 반환 (빈 집계기 체크용)
	 */
	public long getTotalCount() {
		return totalCnt.get();
	}
	
	private long getAvgCount() {
		long success = successCnt.get();
		long timeout = timeoutCnt.get();
		long systemErr = systemErrCnt.get();
		long bizErr = bizErrCnt.get();
		return success + timeout + systemErr + bizErr;
	}
}
