package gc.mda.signal_batch.domain.vessel.service;

import com.github.benmanes.caffeine.cache.Cache;
import gc.mda.signal_batch.domain.vessel.dto.RecentVesselPositionDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

/**
 * 선박 최신 위치 정보 캐시 관리 서비스
 *
 * 캐시 구조:
 * - Key: mmsi (예: "440123456")
 * - Value: RecentVesselPositionDto
 * - TTL: 60분 (CacheConfig에서 설정)
 *
 * 데이터 흐름:
 * 1. VesselPositionCacheRefreshScheduler가 1분마다 refreshCache() 호출
 * 2. DB에서 최근 1분치 데이터 조회
 * 3. 캐시에 업데이트 (기존 데이터는 TTL로 자동 만료)
 * 4. API 요청 시 getFilteredPositions()로 minutes 파라미터 기준 필터링
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class VesselLatestPositionCache {

    private final CacheManager cacheManager;

    private static final String CACHE_NAME = "vesselLatestPositions";

    /**
     * 캐시 키 생성
     */
    private String createKey(String mmsi) {
        return mmsi;
    }

    /**
     * 단일 선박 위치 캐시 저장
     */
    public void put(RecentVesselPositionDto position) {
        if (position == null || position.getMmsi() == null) {
            log.warn("Invalid position data, skipping cache: {}", position);
            return;
        }

        String key = createKey(position.getMmsi());
        getCache().put(key, position);
    }

    /**
     * 여러 선박 위치 일괄 캐시 저장
     */
    public void putAll(List<RecentVesselPositionDto> positions) {
        if (positions == null || positions.isEmpty()) {
            log.debug("No positions to cache");
            return;
        }

        // 갱신 전 캐시 크기
        int sizeBefore = getCacheSize();

        int count = 0;
        for (RecentVesselPositionDto position : positions) {
            if (position.getMmsi() != null) {
                put(position);
                count++;
            }
        }

        // 갱신 후 캐시 크기
        int sizeAfter = getCacheSize();
        int delta = sizeAfter - sizeBefore;
        String deltaStr = delta >= 0 ? "+" + delta : String.valueOf(delta);

        log.info("Cached {} vessel positions (Total: {} vessels, Delta: {})",
                count, sizeAfter, deltaStr);
    }

    /**
     * 캐시에서 지정된 시간 범위 내의 선박 위치 조회 (API 요청용)
     *
     * @param minutes 현재 시간 기준 조회할 시간 범위 (분 단위)
     * @return 필터링된 선박 위치 목록
     */
    public List<RecentVesselPositionDto> getFilteredPositions(int minutes) {
        LocalDateTime threshold = LocalDateTime.now().minusMinutes(minutes);
        List<RecentVesselPositionDto> result = new ArrayList<>();

        ConcurrentMap<Object, Object> nativeCache = getNativeCache();
        if (nativeCache == null || nativeCache.isEmpty()) {
            log.debug("Cache is empty, returning empty list");
            return result;
        }

        int totalCached = 0;
        int filtered = 0;

        for (Map.Entry<Object, Object> entry : nativeCache.entrySet()) {
            totalCached++;

            if (entry.getValue() instanceof RecentVesselPositionDto position) {
                // lastUpdate가 threshold 이후인 데이터만 포함
                if (position.getLastUpdate() != null &&
                        position.getLastUpdate().isAfter(threshold)) {
                    result.add(position);
                    filtered++;
                }
            }
        }

        log.debug("Cache filter result: total={}, filtered={}, minutes={}",
                totalCached, filtered, minutes);

        return result;
    }

    /**
     * 캐시 전체 삭제
     */
    public void clear() {
        org.springframework.cache.Cache cache = getCache();
        if (cache != null) {
            cache.clear();
            log.info("Cache cleared");
        }
    }

    /**
     * 캐시 통계 정보 조회
     */
    public CacheStats getStats() {
        Cache<Object, Object> caffeineCache = getCaffeineCache();
        if (caffeineCache == null) {
            return new CacheStats(0, 0, 0.0, 0.0, 0L, 0L);
        }

        com.github.benmanes.caffeine.cache.stats.CacheStats stats = caffeineCache.stats();
        ConcurrentMap<Object, Object> nativeCache = getNativeCache();
        int size = nativeCache != null ? nativeCache.size() : 0;

        return new CacheStats(
                size,
                caffeineCache.estimatedSize(),
                stats.hitRate() * 100,
                stats.missRate() * 100,
                stats.hitCount(),
                stats.missCount()
        );
    }

    /**
     * Spring Cache 객체 가져오기
     */
    private org.springframework.cache.Cache getCache() {
        return cacheManager.getCache(CACHE_NAME);
    }

    /**
     * Caffeine Cache 네이티브 객체 가져오기
     */
    private Cache<Object, Object> getCaffeineCache() {
        org.springframework.cache.Cache cache = getCache();
        if (cache instanceof CaffeineCache caffeineCache) {
            return caffeineCache.getNativeCache();
        }
        return null;
    }

    /**
     * ConcurrentMap 가져오기 (필터링용)
     */
    private ConcurrentMap<Object, Object> getNativeCache() {
        Cache<Object, Object> caffeineCache = getCaffeineCache();
        if (caffeineCache != null) {
            return caffeineCache.asMap();
        }
        return null;
    }

    /**
     * 현재 캐시 크기 조회
     */
    private int getCacheSize() {
        ConcurrentMap<Object, Object> nativeCache = getNativeCache();
        return nativeCache != null ? nativeCache.size() : 0;
    }

    /**
     * 캐시 통계 정보 DTO
     */
    public record CacheStats(
            int currentSize,
            long estimatedSize,
            double hitRate,
            double missRate,
            long hitCount,
            long missCount
    ) {
    }
}
