package gc.mda.kcg.collector.osint;

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.config.CacheConfig;
import gc.mda.kcg.domain.osint.OsintDto;
import gc.mda.kcg.domain.osint.OsintFeed;
import gc.mda.kcg.domain.osint.OsintFeedRepository;
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 org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;

@Slf4j
@Component
@RequiredArgsConstructor
public class OsintCollector {

    private static final String IRAN_KEYWORDS =
            "\"Strait of Hormuz\" OR Hormuz OR \"Persian Gulf\" OR Iran OR IRGC";
    private static final String KOREA_KEYWORDS =
            "해양사고 OR 해경 OR 어선 OR NLL OR 독도 OR \"Korea maritime\"";

    private static final DateTimeFormatter GDELT_FORMATTER =
            DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneOffset.UTC);
    private static final DateTimeFormatter RSS_FORMATTER =
            DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);

    private final RestTemplate restTemplate;
    private final CacheManager cacheManager;
    private final OsintFeedRepository osintFeedRepository;
    private final AppProperties appProperties;
    private final ObjectMapper objectMapper;
    private final CollectorStatusTracker tracker;

    private boolean nextIsIran = true;

    @PostConstruct
    public void init() {
        Thread.ofVirtual().name("osint-init").start(() -> {
            log.info("OSINT 초기 캐시 로드 시작");
            refreshCache("iran");
            refreshCache("korea");
            log.info("OSINT 초기 캐시 로드 완료");
        });
    }

    @Scheduled(initialDelay = 30_000, fixedDelay = 10_000)
    public void collectOsint() {
        String region = nextIsIran ? "iran" : "korea";
        String keywords = nextIsIran ? IRAN_KEYWORDS : KOREA_KEYWORDS;
        nextIsIran = !nextIsIran;

        log.debug("OSINT {} 수집 시작", region);
        try {
            int gdeltCount = collectGdelt(region, keywords);

            // Google News는 GDELT 호출 후 5초 대기 (rate limit 대응)
            try {
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }

            String newsQuery = "iran".equals(region) ? "Iran Hormuz military" : KOREA_KEYWORDS;
            String newsLang = "iran".equals(region) ? "en" : "ko";
            int newsCount = collectGoogleNews(region, newsQuery, newsLang);
            refreshCache(region);

            tracker.recordSuccess("osint-" + region, region, gdeltCount + newsCount);
            log.debug("OSINT {} 수집 완료", region);
        } catch (Exception e) {
            tracker.recordFailure("osint-" + region, region, e.getMessage());
            log.warn("OSINT {} 수집 실패: {}", region, e.getMessage());
        }
    }

    private int collectGdelt(String region, String keywords) {
        try {
            String url = String.format(
                    "%s?query=%s&mode=ArtList&maxrecords=30&format=json&sort=DateDesc&timespan=24h",
                    appProperties.getCollector().getGdeltBaseUrl(),
                    encodeQuery(keywords)
            );
            String body = restTemplate.getForObject(url, String.class);
            if (body == null || body.isBlank()) return 0;

            JsonNode root = objectMapper.readTree(body);
            JsonNode articles = root.path("articles");
            if (!articles.isArray()) return 0;

            int saved = 0;
            for (JsonNode article : articles) {
                String articleUrl = article.path("url").asText(null);
                String title = article.path("title").asText(null);
                if (articleUrl == null || title == null || title.isBlank()) continue;

                if (osintFeedRepository.existsBySourceAndSourceUrl("gdelt", articleUrl)) continue;
                if (osintFeedRepository.existsByTitle(title)) continue;

                String seendate = article.path("seendate").asText(null);
                Instant publishedAt = parseGdeltDate(seendate);

                String language = article.path("language").asText("en");
                String imageUrl = article.path("socialimage").asText(null);
                if (imageUrl != null && imageUrl.isBlank()) imageUrl = null;

                OsintFeed feed = OsintFeed.builder()
                        .title(title)
                        .source("gdelt")
                        .sourceUrl(articleUrl)
                        .category(classifyCategory(title))
                        .language(language.toLowerCase())
                        .region(region)
                        .imageUrl(imageUrl)
                        .position(null)
                        .publishedAt(publishedAt)
                        .build();

                try {
                    osintFeedRepository.save(feed);
                    saved++;
                } catch (Exception ex) {
                    log.debug("GDELT 중복 스킵: {}", title);
                }
            }
            log.debug("GDELT {} 저장: {}건", region, saved);
            return saved;
        } catch (Exception e) {
            log.warn("GDELT {} 수집 실패: {}", region, e.getMessage());
            return 0;
        }
    }

    private int collectGoogleNews(String region, String query, String lang) {
        try {
            boolean isKorean = "ko".equals(lang);
            String hl = isKorean ? "ko" : "en";
            String gl = isKorean ? "KR" : "US";
            String ceid = isKorean ? "KR:ko" : "US:en";
            String sourceName = isKorean ? "google-news-ko" : "google-news-en";

            String url = String.format(
                    "%s?q=%s&hl=%s&gl=%s&ceid=%s",
                    appProperties.getCollector().getGoogleNewsBaseUrl(),
                    encodeQuery(query),
                    hl, gl, ceid
            );

            String body = restTemplate.getForObject(url, String.class);
            if (body == null || body.isBlank()) return 0;

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));

            NodeList items = doc.getElementsByTagName("item");
            int saved = 0;
            for (int i = 0; i < items.getLength(); i++) {
                Element item = (Element) items.item(i);
                String title = getTextContent(item, "title");
                String link = getTextContent(item, "link");
                String pubDate = getTextContent(item, "pubDate");

                if (link == null || title == null || title.isBlank()) continue;
                if (osintFeedRepository.existsBySourceAndSourceUrl(sourceName, link)) continue;
                if (osintFeedRepository.existsByTitle(title)) continue;

                Instant publishedAt = parseRssDate(pubDate);

                OsintFeed feed = OsintFeed.builder()
                        .title(title)
                        .source(sourceName)
                        .sourceUrl(link)
                        .category(classifyCategory(title))
                        .language(lang)
                        .region(region)
                        .imageUrl(null)
                        .position(null)
                        .publishedAt(publishedAt)
                        .build();

                try {
                    osintFeedRepository.save(feed);
                    saved++;
                } catch (Exception ex) {
                    log.debug("Google News 중복 스킵: {}", title);
                }
            }
            log.debug("Google News {} ({}) 저장: {}건", region, lang, saved);
            return saved;
        } catch (Exception e) {
            log.warn("Google News {} ({}) 수집 실패: {}", region, lang, e.getMessage());
            return 0;
        }
    }

    private void refreshCache(String region) {
        Instant since = Instant.now().minus(24, ChronoUnit.HOURS);
        List<OsintDto> dtos = osintFeedRepository
                .findByRegionAndCollectedAtAfterOrderByPublishedAtDesc(region, since)
                .stream().map(OsintDto::from).toList();
        String cacheName = "iran".equals(region) ? CacheConfig.OSINT_IRAN : CacheConfig.OSINT_KOREA;
        Cache cache = cacheManager.getCache(cacheName);
        if (cache != null) {
            cache.put("data", dtos);
        }
        log.debug("OSINT {} 캐시 갱신: {}건", region, dtos.size());
    }

    private String classifyCategory(String title) {
        String t = title.toLowerCase();
        if (t.matches(".*(strike|missile|attack|military|weapon|drone|전투|공습|미사일).*")) return "military";
        if (t.matches(".*(oil|crude|opec|유가|원유|석유).*")) return "oil";
        if (t.matches(".*(diplomat|sanction|treaty|외교|제재|협상).*")) return "diplomacy";
        if (t.matches(".*(ship|vessel|maritime|해운|선박|항만).*")) return "shipping";
        if (t.matches(".*(nuclear|uranium|핵|우라늄).*")) return "nuclear";
        if (t.matches(".*(해양사고|충돌|좌초|침몰|collision|capsiz).*")) return "maritime_accident";
        if (t.matches(".*(어선|어업|불법조업|fishing).*")) return "fishing";
        return "general";
    }

    private Instant parseGdeltDate(String seendate) {
        if (seendate == null || seendate.isBlank()) return null;
        try {
            return Instant.from(GDELT_FORMATTER.parse(seendate));
        } catch (DateTimeParseException e) {
            log.debug("GDELT 날짜 파싱 실패: {}", seendate);
            return null;
        }
    }

    private Instant parseRssDate(String pubDate) {
        if (pubDate == null || pubDate.isBlank()) return null;
        try {
            return Instant.from(RSS_FORMATTER.parse(pubDate));
        } catch (DateTimeParseException e) {
            log.debug("RSS 날짜 파싱 실패: {}", pubDate);
            return null;
        }
    }

    private String getTextContent(Element parent, String tagName) {
        NodeList nodes = parent.getElementsByTagName(tagName);
        if (nodes.getLength() == 0) return null;
        String text = nodes.item(0).getTextContent();
        return (text == null || text.isBlank()) ? null : text.trim();
    }

    private String encodeQuery(String query) {
        return java.net.URLEncoder.encode(query, StandardCharsets.UTF_8);
    }
}
