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.config.AppProperties;
import gc.mda.kcg.domain.aircraft.AircraftDto;
import gc.mda.kcg.domain.aircraft.AircraftPosition;
import gc.mda.kcg.domain.aircraft.AircraftPositionRepository;
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.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class OpenSkyCollector {

    private static final String IRAN_PARAMS = "lamin=24&lomin=30&lamax=42&lomax=62";
    private static final String KOREA_PARAMS = "lamin=20&lomin=115&lamax=45&lomax=145";
    private static final long TOKEN_REFRESH_MARGIN_SEC = 120;

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

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

    private String accessToken;
    private Instant tokenExpiresAt = Instant.EPOCH;

    @Scheduled(initialDelay = 30_000, fixedDelay = 300_000)
    public void collectIran() {
        List<AircraftDto> aircraft = fetchStates(IRAN_PARAMS);
        if (!aircraft.isEmpty()) {
            cacheStore.updateSource("iran", AircraftCacheStore.SOURCE_OPENSKY, aircraft);
            cacheStore.mergeAndUpdate("iran");
            persistAll(aircraft, "opensky", "iran");
            tracker.recordSuccess("opensky-iran", "iran", aircraft.size());
        } else {
            tracker.recordFailure("opensky-iran", "iran", "빈 응답 또는 인증 실패");
        }
        log.debug("OpenSky 이란 수집 완료: {} 항공기", aircraft.size());
    }

    @Scheduled(initialDelay = 180_000, fixedDelay = 300_000)
    public void collectKorea() {
        List<AircraftDto> aircraft = fetchStates(KOREA_PARAMS);
        if (!aircraft.isEmpty()) {
            cacheStore.updateSource("korea", AircraftCacheStore.SOURCE_OPENSKY, aircraft);
            cacheStore.mergeAndUpdate("korea");
            persistAll(aircraft, "opensky", "korea");
            tracker.recordSuccess("opensky-korea", "korea", aircraft.size());
        } else {
            tracker.recordFailure("opensky-korea", "korea", "빈 응답 또는 인증 실패");
        }
        log.debug("OpenSky 한국 수집 완료: {} 항공기", aircraft.size());
    }

    private List<AircraftDto> fetchStates(String bboxParams) {
        try {
            String token = getAccessToken();
            String url = appProperties.getCollector().getOpenSkyBaseUrl() + "/states/all?" + bboxParams;

            HttpHeaders headers = new HttpHeaders();
            if (token != null) {
                headers.setBearerAuth(token);
            }
            HttpEntity<Void> entity = new HttpEntity<>(headers);

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
            if (response.getStatusCode().value() == 429) {
                log.warn("OpenSky 429 rate limited, 스킵");
                return List.of();
            }
            JsonNode root = objectMapper.readTree(response.getBody());
            return OpenSkyParser.parse(root);
        } catch (Exception e) {
            log.warn("OpenSky 요청 실패: {}", e.getMessage());
            return List.of();
        }
    }

    private synchronized String getAccessToken() {
        if (accessToken != null && Instant.now().isBefore(tokenExpiresAt.minusSeconds(TOKEN_REFRESH_MARGIN_SEC))) {
            return accessToken;
        }

        String clientId = appProperties.getCollector().getOpenSkyClientId();
        String clientSecret = appProperties.getCollector().getOpenSkyClientSecret();
        if (clientId == null || clientSecret == null) {
            log.debug("OpenSky OAuth2 미설정, 익명 모드로 동작");
            return null;
        }

        try {
            String authUrl = appProperties.getCollector().getOpenSkyAuthUrl();

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

            MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
            body.add("grant_type", "client_credentials");
            body.add("client_id", clientId);
            body.add("client_secret", clientSecret);

            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
            ResponseEntity<String> response = restTemplate.postForEntity(authUrl, request, String.class);

            JsonNode json = objectMapper.readTree(response.getBody());
            accessToken = json.get("access_token").asText();
            int expiresIn = json.get("expires_in").asInt();
            tokenExpiresAt = Instant.now().plusSeconds(expiresIn);

            log.info("OpenSky OAuth2 토큰 발급 완료 (만료: {}초)", expiresIn);
            return accessToken;
        } catch (Exception e) {
            log.warn("OpenSky OAuth2 토큰 발급 실패: {}, 익명 모드로 폴백", e.getMessage());
            accessToken = null;
            tokenExpiresAt = Instant.EPOCH;
            return null;
        }
    }

    private void persistAll(List<AircraftDto> aircraft, String source, String region) {
        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());
        }
    }
}
