package gc.mda.kcg.collector.aircraft;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import gc.mda.kcg.collector.CollectorStatusTracker;
import gc.mda.kcg.domain.aircraft.AircraftDto;
import gc.mda.kcg.domain.aircraft.AircraftPosition;
import gc.mda.kcg.domain.aircraft.AircraftPositionRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
@RequiredArgsConstructor
public class AirplanesLiveCollector {

    private static final String BASE_URL = "https://api.airplanes.live/v2";
    private static final int DELAY_MS = 1500;
    private static final int BACKOFF_MS = 5000;
    private static final int POINTS_PER_CYCLE = 2;

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    private final AircraftCacheStore cacheStore;
    private final AircraftPositionRepository positionRepository;
    private final CollectorStatusTracker tracker;

    private final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);

    // 이란/중동 쿼리 포인트 (프론트엔드 LIVE_QUERIES)
    private static final List<RegionQuery> IRAN_QUERIES = List.of(
            new RegionQuery(35.5, 51.5, 250),
            new RegionQuery(30.0, 52.0, 250),
            new RegionQuery(33.0, 57.0, 250),
            new RegionQuery(33.5, 44.0, 250),
            new RegionQuery(33.0, 36.0, 250),
            new RegionQuery(38.0, 40.0, 250),
            new RegionQuery(25.0, 55.0, 250),
            new RegionQuery(26.0, 44.0, 250),
            new RegionQuery(16.0, 44.0, 250),
            new RegionQuery(22.0, 62.0, 250)
    );

    // 한국 쿼리 포인트 (프론트엔드 KR_QUERIES)
    private static final List<RegionQuery> KOREA_QUERIES = List.of(
            new RegionQuery(37.5, 127.0, 250),
            new RegionQuery(35.0, 129.0, 250),
            new RegionQuery(33.5, 126.5, 200),
            new RegionQuery(36.0, 127.0, 250),
            new RegionQuery(38.5, 128.0, 200),
            new RegionQuery(35.5, 131.0, 250),
            new RegionQuery(34.0, 124.0, 200),
            new RegionQuery(40.0, 130.0, 250)
    );

    // 이란 mil 필터 bbox
    private static final double IRAN_LAT_MIN = 12, IRAN_LAT_MAX = 42;
    private static final double IRAN_LNG_MIN = 25, IRAN_LNG_MAX = 68;

    // 한국 mil 필터 bbox
    private static final double KOREA_LAT_MIN = 15, KOREA_LAT_MAX = 50;
    private static final double KOREA_LNG_MIN = 110, KOREA_LNG_MAX = 150;

    private final AtomicInteger iranQueryIdx = new AtomicInteger(0);
    private final AtomicInteger koreaQueryIdx = new AtomicInteger(0);

    // 지역별 서브캐시 (point/radius 결과를 지점 인덱스별로 보관)
    private final Map<String, List<AircraftDto>> iranRegionBuffers = new ConcurrentHashMap<>();
    private final Map<String, List<AircraftDto>> koreaRegionBuffers = new ConcurrentHashMap<>();

    private volatile boolean iranInitDone = false;
    private volatile boolean koreaInitDone = false;

    @PostConstruct
    public void init() {
        Thread.ofVirtual().name("aircraft-init").start(() -> {
            doInitialLoad("iran", IRAN_QUERIES, iranRegionBuffers);
            iranInitDone = true;
            mergePointResults("iran", iranRegionBuffers);
            log.info("Airplanes.live 이란 초기 로드 완료");

            doInitialLoad("korea", KOREA_QUERIES, koreaRegionBuffers);
            koreaInitDone = true;
            mergePointResults("korea", koreaRegionBuffers);
            log.info("Airplanes.live 한국 초기 로드 완료");
        });
    }

    private void doInitialLoad(String region, List<RegionQuery> queries, Map<String, List<AircraftDto>> buffers) {
        log.info("Airplanes.live {} 초기 로드 시작 ({} 지점)", region, queries.size());
        for (int i = 0; i < queries.size(); i++) {
            if (i > 0) sleep(DELAY_MS);
            List<AircraftDto> ac = fetchPoint(queries.get(i));
            buffers.put(String.valueOf(i), ac);
            log.debug("  {} 지점 {}: {} 항공기", region, i, ac.size());
        }
    }

    /**
     * 이란 지역 수집 — 60초마다 2개 지점 순환 갱신 + mil
     */
    @Scheduled(initialDelay = 60_000, fixedDelay = 60_000)
    public void collectIran() {
        if (!iranInitDone) return;

        // 2개 지점 순환 갱신
        int startIdx = iranQueryIdx.getAndUpdate(i -> (i + POINTS_PER_CYCLE) % IRAN_QUERIES.size());
        for (int i = 0; i < POINTS_PER_CYCLE; i++) {
            int idx = (startIdx + i) % IRAN_QUERIES.size();
            if (i > 0) sleep(DELAY_MS);
            List<AircraftDto> ac = fetchPoint(IRAN_QUERIES.get(idx));
            iranRegionBuffers.put(String.valueOf(idx), ac);
        }
        mergePointResults("iran", iranRegionBuffers);

        // mil 엔드포인트 (이란 bbox 필터)
        sleep(DELAY_MS);
        List<AircraftDto> mil = fetchMilitary(IRAN_LAT_MIN, IRAN_LAT_MAX, IRAN_LNG_MIN, IRAN_LNG_MAX);
        cacheStore.updateSource("iran", AircraftCacheStore.SOURCE_MIL, mil);
        cacheStore.mergeAndUpdate("iran");

        // DB 적재
        List<AircraftDto> iranMerged = mergeForPersistence(iranRegionBuffers, mil);
        persistAll(iranMerged, "airplaneslive", "iran");
        tracker.recordSuccess("airplaneslive-iran", "iran", cacheStore.get("iran").size());
        log.debug("Airplanes.live 이란 수집 완료 — 캐시: {}", cacheStore.get("iran").size());
    }

    /**
     * 한국 지역 수집 — 60초마다 2개 지점 순환 갱신 + mil
     */
    @Scheduled(initialDelay = 65_000, fixedDelay = 60_000)
    public void collectKorea() {
        if (!koreaInitDone) return;

        int startIdx = koreaQueryIdx.getAndUpdate(i -> (i + POINTS_PER_CYCLE) % KOREA_QUERIES.size());
        for (int i = 0; i < POINTS_PER_CYCLE; i++) {
            int idx = (startIdx + i) % KOREA_QUERIES.size();
            if (i > 0) sleep(DELAY_MS);
            List<AircraftDto> ac = fetchPoint(KOREA_QUERIES.get(idx));
            koreaRegionBuffers.put(String.valueOf(idx), ac);
        }
        mergePointResults("korea", koreaRegionBuffers);

        sleep(DELAY_MS);
        List<AircraftDto> mil = fetchMilitary(KOREA_LAT_MIN, KOREA_LAT_MAX, KOREA_LNG_MIN, KOREA_LNG_MAX);
        cacheStore.updateSource("korea", AircraftCacheStore.SOURCE_MIL, mil);
        cacheStore.mergeAndUpdate("korea");

        List<AircraftDto> koreaMerged = mergeForPersistence(koreaRegionBuffers, mil);
        persistAll(koreaMerged, "airplaneslive", "korea");
        tracker.recordSuccess("airplaneslive-korea", "korea", cacheStore.get("korea").size());
        log.debug("Airplanes.live 한국 수집 완료 — 캐시: {}", cacheStore.get("korea").size());
    }

    /**
     * point/radius 결과를 dedup 후 캐시의 live 소스로 업데이트.
     */
    private void mergePointResults(String region, Map<String, List<AircraftDto>> buffers) {
        Set<String> seen = new HashSet<>();
        List<AircraftDto> merged = new ArrayList<>();
        for (List<AircraftDto> ac : buffers.values()) {
            for (AircraftDto a : ac) {
                if (seen.add(a.getIcao24())) merged.add(a);
            }
        }
        cacheStore.updateSource(region, AircraftCacheStore.SOURCE_LIVE, merged);
        cacheStore.mergeAndUpdate(region);
    }

    /**
     * DB 적재용 병합 (point + mil dedup)
     */
    private List<AircraftDto> mergeForPersistence(Map<String, List<AircraftDto>> pointBuffers, List<AircraftDto> mil) {
        Map<String, AircraftDto> map = new LinkedHashMap<>();
        for (List<AircraftDto> ac : pointBuffers.values()) {
            for (AircraftDto a : ac) map.putIfAbsent(a.getIcao24(), a);
        }
        for (AircraftDto m : mil) map.putIfAbsent(m.getIcao24(), m);
        return new ArrayList<>(map.values());
    }

    private List<AircraftDto> fetchPoint(RegionQuery q) {
        try {
            String url = String.format("%s/point/%s/%s/%d", BASE_URL, q.lat(), q.lon(), q.radius());
            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
            if (response.getStatusCode().value() == 429) {
                log.warn("Airplanes.live 429 rate limited, 백오프 {}ms", BACKOFF_MS);
                sleep(BACKOFF_MS);
                return List.of();
            }
            JsonNode root = objectMapper.readTree(response.getBody());
            return AirplanesLiveParser.parse(root);
        } catch (Exception e) {
            log.warn("Airplanes.live point 요청 실패 ({},{}): {}", q.lat(), q.lon(), e.getMessage());
            return List.of();
        }
    }

    private List<AircraftDto> fetchMilitary(double latMin, double latMax, double lngMin, double lngMax) {
        try {
            String url = BASE_URL + "/mil";
            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
            if (response.getStatusCode().value() == 429) {
                log.warn("Airplanes.live mil 429, 스킵");
                return List.of();
            }
            JsonNode root = objectMapper.readTree(response.getBody());
            return AirplanesLiveParser.parse(root).stream()
                    .filter(a -> a.getLat() >= latMin && a.getLat() <= latMax
                            && a.getLng() >= lngMin && a.getLng() <= lngMax)
                    .toList();
        } catch (Exception e) {
            log.warn("Airplanes.live mil 요청 실패: {}", e.getMessage());
            return List.of();
        }
    }

    private void persistAll(List<AircraftDto> aircraft, String source, String region) {
        if (aircraft.isEmpty()) return;
        try {
            Instant now = Instant.now();
            List<AircraftPosition> entities = aircraft.stream()
                    .map(dto -> AircraftPosition.builder()
                            .icao24(dto.getIcao24())
                            .callsign(dto.getCallsign())
                            .position(geometryFactory.createPoint(new Coordinate(dto.getLng(), dto.getLat())))
                            .altitude(dto.getAltitude())
                            .velocity(dto.getVelocity())
                            .heading(dto.getHeading())
                            .verticalRate(dto.getVerticalRate())
                            .onGround(dto.isOnGround())
                            .category(dto.getCategory())
                            .typecode(dto.getTypecode())
                            .typeDesc(dto.getTypeDesc())
                            .registration(dto.getRegistration())
                            .operator(dto.getOperator())
                            .squawk(dto.getSquawk())
                            .source(source)
                            .region(region)
                            .collectedAt(now)
                            .lastSeen(Instant.ofEpochMilli(dto.getLastSeen()))
                            .build())
                    .toList();
            positionRepository.saveAll(entities);
            log.debug("DB 적재 완료: {} {} — {} 건", source, region, entities.size());
        } catch (Exception e) {
            log.error("DB 적재 실패 ({} {}): {}", source, region, e.getMessage());
        }
    }

    private static void sleep(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    record RegionQuery(double lat, double lon, int radius) {}
}
