package gc.mda.signal_batch.batch.reader;

import gc.mda.signal_batch.domain.vessel.model.AisTargetEntity;
import gc.mda.signal_batch.global.util.SignalKindCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;

/**
 * 기동 시 ChnPrmShip 캐시 워밍업
 *
 * t_ais_position 테이블에서 대상 MMSI의 데이터를 조회하여 캐시를 채운다.
 * 이후 매 분 배치 수집에서 실시간 데이터가 캐시를 갱신한다.
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ChnPrmShipCacheWarmer implements ApplicationRunner {

    private static final int DB_QUERY_CHUNK_SIZE = 500;

    private final ChnPrmShipProperties properties;
    private final ChnPrmShipCacheManager cacheManager;
    @Qualifier("queryJdbcTemplate")
    private final JdbcTemplate queryJdbcTemplate;

    @Override
    public void run(ApplicationArguments args) {
        if (!properties.isWarmupEnabled()) {
            log.info("ChnPrmShip 캐시 워밍업 비활성화");
            return;
        }

        if (properties.getMmsiSet().isEmpty()) {
            log.warn("ChnPrmShip 대상 MMSI가 없어 워밍업을 건너뜁니다");
            return;
        }

        OffsetDateTime since = OffsetDateTime.now(ZoneOffset.UTC)
                .minusDays(properties.getWarmupDays());

        log.info("ChnPrmShip 캐시 워밍업 시작 - 대상: {}건, 조회 범위: 최근 {}일",
                properties.getMmsiSet().size(), properties.getWarmupDays());
        long startTime = System.currentTimeMillis();

        List<String> mmsiList = new ArrayList<>(properties.getMmsiSet());
        int totalLoaded = 0;

        for (int i = 0; i < mmsiList.size(); i += DB_QUERY_CHUNK_SIZE) {
            List<String> chunk = mmsiList.subList(i,
                    Math.min(i + DB_QUERY_CHUNK_SIZE, mmsiList.size()));

            try {
                List<AisTargetEntity> fromDb = queryLatestByMmsiSince(chunk, since);

                fromDb.forEach(entity -> {
                    if (entity.getSignalKindCode() == null) {
                        SignalKindCode kindCode = SignalKindCode.resolve(
                                entity.getVesselType(), entity.getExtraInfo());
                        entity.setSignalKindCode(kindCode.getCode());
                    }
                });

                cacheManager.putAll(fromDb);
                totalLoaded += fromDb.size();
            } catch (Exception e) {
                log.warn("ChnPrmShip 워밍업 DB 조회 실패 (chunk {}/{}): {}",
                        i / DB_QUERY_CHUNK_SIZE + 1,
                        (mmsiList.size() + DB_QUERY_CHUNK_SIZE - 1) / DB_QUERY_CHUNK_SIZE,
                        e.getMessage());
            }
        }

        long elapsed = System.currentTimeMillis() - startTime;
        log.info("ChnPrmShip 캐시 워밍업 완료 - 대상: {}, 로딩: {}건, 소요: {}ms",
                properties.getMmsiSet().size(), totalLoaded, elapsed);
    }

    private List<AisTargetEntity> queryLatestByMmsiSince(List<String> mmsiList, OffsetDateTime since) {
        String placeholders = String.join(",", mmsiList.stream().map(m -> "?").toList());
        String sql = "SELECT mmsi, imo, name, callsign, vessel_type, extra_info, " +
                "lat, lon, heading, sog, cog, rot, length, width, draught, " +
                "destination, eta, status, message_timestamp, signal_kind_code, class_type " +
                "FROM signal.t_ais_position " +
                "WHERE mmsi IN (" + placeholders + ") " +
                "AND message_timestamp >= ?";

        Object[] params = new Object[mmsiList.size() + 1];
        for (int j = 0; j < mmsiList.size(); j++) {
            params[j] = mmsiList.get(j);
        }
        params[mmsiList.size()] = since;

        return queryJdbcTemplate.query(sql, params, (rs, rowNum) -> mapRow(rs));
    }

    private AisTargetEntity mapRow(ResultSet rs) throws SQLException {
        return AisTargetEntity.builder()
                .mmsi(rs.getString("mmsi"))
                .imo(rs.getObject("imo") != null ? rs.getLong("imo") : null)
                .name(rs.getString("name"))
                .callsign(rs.getString("callsign"))
                .vesselType(rs.getString("vessel_type"))
                .extraInfo(rs.getString("extra_info"))
                .lat(rs.getObject("lat") != null ? rs.getDouble("lat") : null)
                .lon(rs.getObject("lon") != null ? rs.getDouble("lon") : null)
                .heading(rs.getObject("heading") != null ? rs.getDouble("heading") : null)
                .sog(rs.getObject("sog") != null ? rs.getDouble("sog") : null)
                .cog(rs.getObject("cog") != null ? rs.getDouble("cog") : null)
                .rot(rs.getObject("rot") != null ? rs.getInt("rot") : null)
                .length(rs.getObject("length") != null ? rs.getInt("length") : null)
                .width(rs.getObject("width") != null ? rs.getInt("width") : null)
                .draught(rs.getObject("draught") != null ? rs.getDouble("draught") : null)
                .destination(rs.getString("destination"))
                .eta(rs.getObject("eta") != null ? rs.getObject("eta", OffsetDateTime.class) : null)
                .status(rs.getString("status"))
                .messageTimestamp(rs.getObject("message_timestamp") != null
                        ? rs.getObject("message_timestamp", OffsetDateTime.class) : null)
                .signalKindCode(rs.getString("signal_kind_code"))
                .classType(rs.getString("class_type"))
                .build();
    }
}
