package gc.mda.signal_batch.batch.job;

import gc.mda.signal_batch.domain.vessel.dto.RecentVesselPositionDto;
import gc.mda.signal_batch.domain.vessel.service.VesselLatestPositionCache;
import gc.mda.signal_batch.global.util.ShipKindCodeConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 선박 최신 위치 캐시 갱신 스케줄러
 *
 * 실행 주기: 1분마다 (매분 0초)
 * 데이터 소스: Collect DB (sig_test 테이블)
 * 처리 방식: 읽기 전용 (DB에 쓰기 없음, 캐시만 업데이트)
 *
 * 동작 흐름:
 * 1. 매분 0초에 실행
 * 2. 최근 2분치 데이터를 DB에서 조회 (수집 지연 고려)
 * 3. DISTINCT ON으로 선박별 최신 위치만 추출
 * 4. 캐시에 업데이트
 *
 * 기존 배치와의 관계:
 * - 기존 5분 배치는 그대로 유지 (DB 저장)
 * - 이 스케줄러는 캐시만 관리 (읽기 전용)
 * - 충돌 없음
 */
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(name = "vessel.batch.cache.latest-position.enabled", havingValue = "true", matchIfMissing = false)
public class VesselPositionCacheRefreshScheduler {

    @Qualifier("collectJdbcTemplate")
    private final JdbcTemplate collectJdbcTemplate;

    private final VesselLatestPositionCache cache;

    @Value("${vessel.batch.cache.latest-position.refresh-interval-minutes:2}")
    private int refreshIntervalMinutes;

    private volatile boolean isRunning = false;

    /**
     * 1분마다 캐시 갱신
     * 매분 0초에 실행 (예: 10:00:00, 10:01:00, 10:02:00...)
     */
    @Scheduled(cron = "0 * * * * *")
    public void refreshCache() {
        // 동시 실행 방지
        if (isRunning) {
            log.warn("Previous cache refresh is still running, skipping this execution");
            return;
        }

        isRunning = true;
        long startTime = System.currentTimeMillis();

        try {
            // 최근 N분치 데이터 조회 (수집 지연 고려)
            List<RecentVesselPositionDto> positions = fetchLatestPositions();

            if (positions.isEmpty()) {
                log.warn("No vessel positions found in last {} minutes", refreshIntervalMinutes);
                return;
            }

            // 캐시 업데이트
            cache.putAll(positions);

            long duration = System.currentTimeMillis() - startTime;
            log.info("Cache refresh completed in {}ms (fetched {} positions from DB)",
                    duration, positions.size());

            // 캐시 통계 로깅 (5분마다만)
            if (LocalDateTime.now().getMinute() % 5 == 0) {
                logCacheStats();
            }

        } catch (Exception e) {
            log.error("Failed to refresh cache", e);
        } finally {
            isRunning = false;
        }
    }

    /**
     * DB에서 최신 위치 데이터 조회
     */
    private List<RecentVesselPositionDto> fetchLatestPositions() {
        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = endTime.minusMinutes(refreshIntervalMinutes);

        String sql = """
            SELECT DISTINCT ON (sig_src_cd, target_id)
                sig_src_cd,
                target_id,
                lon,
                lat,
                sog,
                cog,
                ship_nm,
                ship_ty,
                message_time as last_update
            FROM signal.sig_test
            WHERE message_time >= ? AND message_time < ?
                AND sig_src_cd NOT IN ('000004', '000005')
                AND length(target_id) > 5
                AND lat BETWEEN -90 AND 90
                AND lon BETWEEN -180 AND 180
            ORDER BY sig_src_cd, target_id, message_time DESC
            """;

        try {
            return collectJdbcTemplate.query(sql,
                    new Object[]{Timestamp.valueOf(startTime), Timestamp.valueOf(endTime)},
                    new VesselPositionRowMapper());

        } catch (Exception e) {
            log.error("Failed to fetch positions from DB", e);
            return List.of();
        }
    }

    /**
     * 캐시 통계 로깅
     */
    private void logCacheStats() {
        try {
            VesselLatestPositionCache.CacheStats stats = cache.getStats();
            log.info("Cache Stats - Size: {}, HitRate: {:.2f}%, MissRate: {:.2f}%, Hits: {}, Misses: {}",
                    stats.currentSize(),
                    stats.hitRate(),
                    stats.missRate(),
                    stats.hitCount(),
                    stats.missCount());
        } catch (Exception e) {
            log.warn("Failed to get cache stats", e);
        }
    }

    /**
     * RowMapper 구현
     */
    private static class VesselPositionRowMapper implements RowMapper<RecentVesselPositionDto> {
        @Override
        public RecentVesselPositionDto mapRow(ResultSet rs, int rowNum) throws SQLException {
            String sigSrcCd = rs.getString("sig_src_cd");
            String targetId = rs.getString("target_id");
            String shipTy = rs.getString("ship_ty");

            // shipKindCode 계산
            String shipKindCode = ShipKindCodeConverter.getShipKindCode(sigSrcCd, shipTy);

            // nationalCode 계산
            String nationalCode;
            if ("000001".equals(sigSrcCd) && targetId != null && targetId.length() >= 3) {
                nationalCode = targetId.substring(0, 3);
            } else {
                nationalCode = "440"; // 기본값
            }

            return RecentVesselPositionDto.builder()
                    .sigSrcCd(sigSrcCd)
                    .targetId(targetId)
                    .lon(rs.getDouble("lon"))
                    .lat(rs.getDouble("lat"))
                    .sog(rs.getBigDecimal("sog"))
                    .cog(rs.getBigDecimal("cog"))
                    .shipNm(rs.getString("ship_nm"))
                    .shipTy(shipTy)
                    .shipKindCode(shipKindCode)
                    .nationalCode(nationalCode)
                    .lastUpdate(rs.getTimestamp("last_update") != null ?
                            rs.getTimestamp("last_update").toLocalDateTime() : null)
                    .build();
        }
    }
}
