package gc.mda.kcg.collector.satellite;

import gc.mda.kcg.collector.CollectorStatusTracker;
import gc.mda.kcg.config.AppProperties;
import gc.mda.kcg.config.CacheConfig;
import gc.mda.kcg.domain.satellite.SatelliteDto;
import gc.mda.kcg.domain.satellite.SatelliteTle;
import gc.mda.kcg.domain.satellite.SatelliteTleRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.regex.Pattern;

@Slf4j
@Component
@RequiredArgsConstructor
public class SatelliteCollector {

    private static final Map<String, String> TLE_GROUPS = Map.of(
            "military", "reconnaissance",
            "gps-ops", "navigation",
            "geo", "communications",
            "weather", "weather",
            "stations", "other"
    );

    private static final Pattern RECON_PATTERN = Pattern.compile("SBIRS|DSP|STSS|NROL|USA");
    private static final Pattern COMMS_PATTERN = Pattern.compile("WGS|AEHF|MUOS|STARLINK|ONEWEB");
    private static final Pattern NAV_PATTERN = Pattern.compile("GPS|NAVSTAR");

    public volatile long lastUpdated = 0L;

    private final RestTemplate restTemplate;
    private final CacheManager cacheManager;
    private final SatelliteTleRepository repository;
    private final AppProperties appProperties;
    private final CollectorStatusTracker tracker;

    @PostConstruct
    public void init() {
        Thread.ofVirtual().name("satellite-init").start(() -> {
            log.info("위성 TLE 초기 캐시 로드 시작");
            loadCacheFromDb();
            log.info("위성 TLE 초기 캐시 로드 완료");
        });
    }

    @Scheduled(initialDelay = 60_000, fixedDelay = 600_000)
    public void collect() {
        log.info("위성 TLE 수집 시작");
        String baseUrl = appProperties.getCollector().getCelestrakBaseUrl();
        List<SatelliteTle> allEntities = new ArrayList<>();
        Set<Integer> seenNoradIds = new LinkedHashSet<>();
        Instant now = Instant.now();

        for (Map.Entry<String, String> entry : TLE_GROUPS.entrySet()) {
            String group = entry.getKey();
            String defaultCategory = entry.getValue();

            try {
                String url = baseUrl + "?GROUP=" + group + "&FORMAT=tle";
                String body = restTemplate.getForObject(url, String.class);
                if (body == null || body.isBlank()) {
                    log.warn("위성 TLE 응답 없음: group={}", group);
                    continue;
                }

                List<SatelliteTle> entities = parseTle(body, group, defaultCategory, now, seenNoradIds);
                allEntities.addAll(entities);
                log.debug("위성 TLE 파싱 완료: group={}, count={}", group, entities.size());

            } catch (Exception e) {
                log.warn("위성 TLE 수집 실패: group={}, error={}", group, e.getMessage());
            }

            sleep(1000);
        }

        if (!allEntities.isEmpty()) {
            try {
                repository.saveAll(allEntities);
                tracker.recordSuccess("satellite", "global", allEntities.size());
                log.info("위성 TLE DB 적재 완료: {} 건", allEntities.size());
            } catch (Exception e) {
                tracker.recordFailure("satellite", "global", e.getMessage());
                log.error("위성 TLE DB 적재 실패: {}", e.getMessage());
            }
        } else {
            tracker.recordFailure("satellite", "global", "TLE 데이터 없음");
        }

        loadCacheFromDb();
    }

    public long getLastUpdated() {
        return lastUpdated;
    }

    void loadCacheFromDb() {
        Instant since = Instant.now().minus(11, ChronoUnit.MINUTES);
        List<SatelliteDto> dtos = repository.findByCollectedAtAfterOrderByNoradIdAsc(since)
                .stream().map(SatelliteDto::from).toList();

        if (dtos.isEmpty()) {
            since = Instant.now().minus(24, ChronoUnit.HOURS);
            dtos = repository.findByCollectedAtAfterOrderByNoradIdAsc(since)
                    .stream().map(SatelliteDto::from).toList();
        }

        if (!dtos.isEmpty()) {
            Cache cache = cacheManager.getCache(CacheConfig.SATELLITES);
            if (cache != null) cache.put("data", dtos);
            lastUpdated = System.currentTimeMillis();
            log.debug("위성 TLE 캐시 갱신 완료: {} 건", dtos.size());
        }
    }

    private List<SatelliteTle> parseTle(String body, String group, String defaultCategory,
                                        Instant collectedAt, Set<Integer> seenNoradIds) {
        List<SatelliteTle> result = new ArrayList<>();
        String[] lines = body.lines()
                .map(String::trim)
                .filter(l -> !l.isBlank())
                .toArray(String[]::new);

        for (int i = 0; i + 2 < lines.length; i += 3) {
            String nameLine = lines[i];
            String line1 = lines[i + 1];
            String line2 = lines[i + 2];

            if (!line1.startsWith("1 ") || !line2.startsWith("2 ")) {
                log.debug("TLE 형식 불일치 스킵: name={}", nameLine);
                continue;
            }

            try {
                String name = nameLine.trim();
                int noradId = Integer.parseInt(line1.substring(2, 7).trim());

                if (!seenNoradIds.add(noradId)) {
                    continue;
                }

                Instant epoch = parseTleEpoch(line1);
                String category = resolveCategory(name, defaultCategory);

                result.add(SatelliteTle.builder()
                        .noradId(noradId)
                        .name(name)
                        .tleLine1(line1)
                        .tleLine2(line2)
                        .category(category)
                        .tleGroup(group)
                        .epoch(epoch)
                        .collectedAt(collectedAt)
                        .build());

            } catch (Exception e) {
                log.debug("TLE 파싱 실패 스킵: name={}, error={}", nameLine, e.getMessage());
            }
        }

        return result;
    }

    private Instant parseTleEpoch(String line1) {
        String epochStr = line1.substring(18, 32).trim();
        int yy = Integer.parseInt(epochStr.substring(0, 2));
        int year = (yy < 57) ? 2000 + yy : 1900 + yy;
        double dayOfYearFraction = Double.parseDouble(epochStr.substring(2));
        int dayOfYear = (int) dayOfYearFraction;
        double fraction = dayOfYearFraction - dayOfYear;

        return LocalDate.ofYearDay(year, dayOfYear)
                .atStartOfDay(ZoneOffset.UTC)
                .toInstant()
                .plusMillis((long) (fraction * 86_400_000));
    }

    private String resolveCategory(String name, String defaultCategory) {
        if (RECON_PATTERN.matcher(name).find()) return "reconnaissance";
        if (NAV_PATTERN.matcher(name).find()) return "navigation";
        if (COMMS_PATTERN.matcher(name).find()) return "communications";
        return defaultCategory;
    }

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