package incheon.ags.ias.rst.link.service.impl;

import java.net.URI;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import javax.transaction.Transactional;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import incheon.ags.ias.rst.link.mapper.AgsRstPlmvMapper;
import incheon.ags.ias.rst.link.service.AgsRstPlmvService;
import incheon.ags.ias.rst.link.vo.AgsRstPlmvVO;
import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName : AgsRstPlmvServiceImpl.java
 * @Description : 인구이동통계 API 연계 서비스 구현체
 * @author : 이주훈
 * @since : 2026.02.12
 * @version : 1.0
 */
@Slf4j
@Service
public class AgsRstPlmvServiceImpl  implements AgsRstPlmvService {
	
    private final AgsRstPlmvMapper agsRstPlmvMapper;
    
    /**
     * SSL 보안이 적용된 RestTemplate
     *  - Bean 초기화 시점에 1회 생성
     */
    private RestTemplate restTemplate; 
    @Autowired private ObjectMapper objectMapper;

    private static final int CONNECT_TIMEOUT = 3000;
    private static final int READ_TIMEOUT = 30000;
    
    @Value("${mois.host}")
    private String moisHost;
    
    private static final String SERVICE_KEY =
            "zPL9ZFSooUtJVVlB%2FB5Z1nYs3tLWn9jSRTPXxcTe4JHMe5UvXc9vVN8NGkN6mxngBIdekAQ8Ru2bGVQ9ncsh1Q%3D%3D";
    
    public AgsRstPlmvServiceImpl(AgsRstPlmvMapper agsRstPlmvMapper) {
        this.agsRstPlmvMapper = agsRstPlmvMapper;
    }
    
    /**
     *  Bean 초기화 시 1회 실행
     * - SSLContext 기반 RestTemplate 생성
     */
    @PostConstruct
    public void init() {
        try {
            this.restTemplate = createSecureRestTemplate();
            log.info("RestTemplate initialized (SSL OpenAPI 적용 완료)");
        } catch (Exception e) {
            log.error("RestTemplate 초기화 실패, 기본 RestTemplate 사용", e);
            this.restTemplate = new RestTemplate();
        }
    }
    
    /**
     *  SSL RestTemplate 생성
     */
    private RestTemplate createSecureRestTemplate() throws Exception {

        // 모든 인증서(사설 인증서 / 공공 인증서)를 신뢰하는 SSLContext 생성
        SSLContext sslContext = SSLContextBuilder.create()
                .loadTrustMaterial((chain, authType) -> true)
                .build();
        
        // 허용할 TLS 버전 전부 오픈
        String[] enabledProtocols = new String[]{
                "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"
        };

        // 특정 IP(host)만 허용
        SSLConnectionSocketFactory socketFactory =
                new SSLConnectionSocketFactory(
                        sslContext,
                        enabledProtocols,
                        null,
                        (hostname, session) -> {
                            boolean isAllowed = hostname.equalsIgnoreCase(moisHost);
                            if (!isAllowed) {
                                log.error("허용되지 않은 호스트 접근 시도: {}", hostname);
                            }
                            return isAllowed;
                        }
                );

        // Apache HttpClient 생성
        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(socketFactory)
                .build();

        // RestTemplate에 HttpClient 적용
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);

        // 타임아웃 설정 (LevyNextUtil 기준)
        factory.setConnectTimeout(CONNECT_TIMEOUT);
        factory.setReadTimeout(READ_TIMEOUT);

        return new RestTemplate(factory);
    }
    
    // 행정구역 코드
    private static final String INCHEON = "2800000000";
    private static final String SEOUL   = "1100000000";

    private static final String[] GYEONGGI = {
            "4157000000", 		// 경기도 김포시
            "4119000000", 		// 경기도 부천시
            "4148000000",		// 경기도 파주시
            "4139000000",		// 경기도 시흥시
            "4127000000",		// 경기도 안산시
            "4159000000"		// 경기도 화성시
    };
    
    // 전체 적재
    @Transactional
    @Override
    public Map<String, Object> insertPlmvStatsAll() {

        BatchResult result = new BatchResult();

        // DB 최신월 조회
        String dbLastYm = agsRstPlmvMapper.selectMaxYm();
        if (dbLastYm == null)
            dbLastYm = "202210";

        // API 최신월 탐색
        String apiLastYm = findLatestAvailableYm();

        log.info("전체적재 - DB 최신월 = {}", dbLastYm);
        log.info("전체적재 - API 최신월 = {}", apiLastYm);

        // 이미 최신이면 종료
        if (dbLastYm.compareTo(apiLastYm) >= 0) {
            return Map.of(
                "success", true,
                "totalUpserted", 0,
                "errorCount", 0,
                "errors", List.of(),
                "routeCounts", List.of(),
                "message", "이미 최신 데이터입니다."
            );
        }

        // 최초월 ~ 최신월까지 적재
        List<String> months = createMonthRange("202210", apiLastYm);

        for (String ym : months) {
            fetchMonth(ym, result);
        }

        String startYm = months.get(0);
        String endYm = months.get(months.size()-1);

        String msg = formatYmRange(startYm, endYm);

        return buildResult(result, msg);
    }
    
    // 증분 적재
    @Transactional
    @Override
    public Map<String, Object> insertPlmvStatsIncremental() {

        BatchResult result = new BatchResult();
        
        String dbLastYm = agsRstPlmvMapper.selectMaxYm();
        if (dbLastYm == null) 
        	dbLastYm = "202210";
        
        // API 최신월 탐색
        String apiLastYm = findLatestAvailableYm();
        
        log.info("DB 최신월 = {}", dbLastYm);
        log.info("API 최신월 = {}", apiLastYm);
        
        // 이미 최신이면 종료
        if(dbLastYm.compareTo(apiLastYm) >= 0){
            return Map.of("message","이미 최신 데이터입니다");
        }
        
        // DB 다음월부터 시작
        String startYm = YearMonth.parse(dbLastYm, DateTimeFormatter.ofPattern("yyyyMM"))
					                .plusMonths(1)
					                .format(DateTimeFormatter.ofPattern("yyyyMM"));

        
        // 현재월까지 → API 최신월까지
        List<String> months = createMonthRange(startYm, apiLastYm);

        for (String ym : months) {
            fetchMonth(ym, result);
        }

        String startYmMsg = months.get(0);
        String endYmMsg   = months.get(months.size()-1);

        String msg = formatYmRange(startYmMsg, endYmMsg);

        return buildResult(result, msg);
    }
    
    // API 최신월 탐색 로직
    private String findLatestAvailableYm() {

        YearMonth ym = YearMonth.now();
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMM");

        for(int i=0; i<12; i++){

            String targetYm = ym.format(fmt);

            try{
                if(checkApiDataExists(targetYm)){
                    log.info("API 최신 기준연월 발견 = {}", targetYm);
                    return targetYm;
                }
            }catch(Exception e){
                log.warn("최신월 탐색 실패 {}", targetYm);
            }

            ym = ym.minusMonths(1);
        }

        throw new RuntimeException("API 최신월 탐색 실패 (12개월)");
    }
    
    // 해당 월 데이터 존재 여부 확인
    private boolean checkApiDataExists(String ym) throws Exception {

        URI uri = UriComponentsBuilder.fromHttpUrl("https://apis.data.go.kr/1741000/ppltnDataStus/selectPpltnDataStus")
                .queryParam("serviceKey", SERVICE_KEY)
                .queryParam("mvinAdmmCd", INCHEON)
                .queryParam("mvtAdmmCd", SEOUL)
                .queryParam("srchFrYm", ym)
                .queryParam("srchToYm", ym)
                .queryParam("lv", "4")
                .queryParam("type", "json")
                .build(true)
                .toUri();

        ResponseEntity<String> res = restTemplate.getForEntity(uri, String.class);

        Map<String,Object> root = objectMapper.readValue(res.getBody(), new TypeReference<>() {});
        Map<String,Object> response = (Map<String,Object>) root.get("Response");

        if(response == null) return false;

        // 데이터 존재 여부 체크
        Map<String,Object> items = (Map<String,Object>) response.get("items");
        if(items == null) return false;

        Object item = items.get("item");
        return item != null;
    }
    
    // 월 범위 오버로드
    private List<String> createMonthRange(String startYm, String endYm) {

        List<String> months = new ArrayList<>();
        YearMonth start = YearMonth.parse(startYm, DateTimeFormatter.ofPattern("yyyyMM"));
        YearMonth end = YearMonth.parse(endYm, DateTimeFormatter.ofPattern("yyyyMM"));

        while (!start.isAfter(end)) {
            months.add(start.format(DateTimeFormatter.ofPattern("yyyyMM")));
            start = start.plusMonths(1);
        }

        return months;
    }

    // 월별 호출
    private void fetchMonth(String ym, BatchResult result) {

        // 서울 ↔ 인천 (lv=4)
        callApiLv4(INCHEON, SEOUL, ym, result);
        callApiLv4(SEOUL, INCHEON, ym, result);

        // 경기도 ↔ 인천 (lv=2 SUM)
        for (String gg : GYEONGGI) {
            callApiLv2Sum(INCHEON, gg, ym, result);
            callApiLv2Sum(gg, INCHEON, ym, result);
        }
    }
    
    // LV=4 호출 (서울/인천)
    private void callApiLv4(String fromCd, String toCd, String ym, BatchResult result) {

        try {
        	URI uri = UriComponentsBuilder.fromHttpUrl("https://apis.data.go.kr/1741000/ppltnDataStus/selectPpltnDataStus")
                    .queryParam("serviceKey", SERVICE_KEY)
                    .queryParam("mvinAdmmCd", toCd)
                    .queryParam("mvtAdmmCd", fromCd)
                    .queryParam("srchFrYm", ym)
                    .queryParam("srchToYm", ym)
                    .queryParam("lv", "4")
                    .queryParam("type", "json")
                    .build(true)
                    .toUri();

            log.info("API 호출 URI: {}", uri);

            ResponseEntity<String> res = restTemplate.getForEntity(uri, String.class);
            parseLv4AndSave(res.getBody(), ym, fromCd, toCd, result);

            result.addSuccess(1);

        } catch (Exception e) {
            log.error("LV4 실패 [{}→{} {}]", fromCd, toCd, ym, e);
            result.addError("LV4 실패 " + ym);
        }
    }
    
    // LV=2 호출 (경기도 SUM)
    private void callApiLv2Sum(String fromCd, String toCd, String ym, BatchResult result) {

        try {
        	URI uri = UriComponentsBuilder.fromHttpUrl("https://apis.data.go.kr/1741000/ppltnDataStus/selectPpltnDataStus")
                    .queryParam("serviceKey", SERVICE_KEY)
                    .queryParam("mvinAdmmCd", toCd)
                    .queryParam("mvtAdmmCd", fromCd)
                    .queryParam("srchFrYm", ym)
                    .queryParam("srchToYm", ym)
                    .queryParam("lv", "2")
                    .queryParam("type", "json")
                    .build(true)
                    .toUri();

            log.info("API 호출 URI: {}", uri);

            ResponseEntity<String> res = restTemplate.getForEntity(uri, String.class);
            parseLv2SumAndSave(res.getBody(), ym, fromCd, toCd, result);

            result.addSuccess(1);

        } catch (Exception e) {
            log.error("LV2 실패 [{}→{} {}]", fromCd, toCd, ym, e);
            result.addError("LV2 실패 " + ym);
        }
    }
    
    // LV=4 파싱
    private void parseLv4AndSave(String json, String ym, String fromCd, String toCd, BatchResult result) throws Exception {

        Map<String,Object> root = objectMapper.readValue(json, new TypeReference<>() {});
        Map<String,Object> response = (Map<String,Object>) root.get("Response");

        if(response == null) return;

        Map<String,Object> items = (Map<String,Object>) response.get("items");
        Map<String,Object> item  = (Map<String,Object>) items.get("item");

        int total  = Integer.parseInt(item.get("totNmprCnt").toString());
        int male   = Integer.parseInt(item.get("maleNmprCnt").toString());
        int female = Integer.parseInt(item.get("femlNmprCnt").toString());

        saveVo(ym, fromCd, toCd, item, total, male, female, result);
    }
    
    // LV=2 SUM 파싱
    private void parseLv2SumAndSave(String json, String ym, String fromCd, String toCd, BatchResult result) throws Exception {
		Map<String,Object> root = objectMapper.readValue(json, new TypeReference<>() {});
		Map<String,Object> response = (Map<String,Object>) root.get("Response");
		
		if(response == null) return;
		
		Map<String,Object> items = (Map<String,Object>) response.get("items");
		List<Map<String,Object>> list = (List<Map<String,Object>>) items.get("item");
		
		if(list == null || list.isEmpty()) return;
		
		int total=0, male=0, female=0;
		
		for(Map<String,Object> item : list){
		total  += Integer.parseInt(item.get("totNmprCnt").toString());
		male   += Integer.parseInt(item.get("maleNmprCnt").toString());
		female += Integer.parseInt(item.get("femlNmprCnt").toString());
		}
		
		Map<String,Object> first = list.get(0);
		
		saveVo(ym, fromCd, toCd, first, total, male, female, result);
	}
    
    private void saveVo(String ym, String fromCd, String toCd, Map<String,Object> item, int total, int male, int female, BatchResult result){
		AgsRstPlmvVO vo = new AgsRstPlmvVO();
		
		vo.setStatsYm(ym);
		vo.setMvoutCd(fromCd); 		// 전출코드
		vo.setMvinCd(toCd);    			// 전입코드
		
		// 지역명 정규화
		normalizeRegionNames(vo, fromCd, toCd, item);
		
		vo.setTppl(total);
		vo.setMaleCnt(male);
		vo.setFemaleCnt(female);
		
		agsRstPlmvMapper.upsertPlmv(vo);
		
		// 전출지/전입지 전체 이름 생성 (시군구 포함)
		String fromFull = buildFullRegionName(vo.getMvoutCtpvNm(), vo.getMvoutSggNm());
		String toFull   = buildFullRegionName(vo.getMvinCtpvNm(), vo.getMvinSggNm());

		// 경로 집계
		result.addRoute(fromFull, toFull);
	}
    
    // 전체 지역명 생성
    private String buildFullRegionName(String ctpv, String sgg){
        if(sgg == null || sgg.isBlank()){
            return ctpv;
        }
        return ctpv + " " + sgg;
    }
    
    // 행정구역 명칭 정규화
    private void normalizeRegionNames(AgsRstPlmvVO vo, String fromCd, String toCd, Map<String,Object> item){
        // 전출(출발지)
        if(fromCd.equals(INCHEON)){
            vo.setMvoutCtpvNm("인천광역시");
            vo.setMvoutSggNm(null);
        }
        else if(fromCd.equals(SEOUL)){
            vo.setMvoutCtpvNm("서울특별시");
            vo.setMvoutSggNm(null);
        }
        else{
            // 경기도 시
            vo.setMvoutCtpvNm((String)item.get("mvtCtpvNm"));
            vo.setMvoutSggNm((String)item.get("mvtSggNm"));
        }

        // 전입(도착지)
        if(toCd.equals(INCHEON)){
            vo.setMvinCtpvNm("인천광역시");
            vo.setMvinSggNm(null);
        }
        else if(toCd.equals(SEOUL)){
            vo.setMvinCtpvNm("서울특별시");
            vo.setMvinSggNm(null);
        }
        else{
            // 경기도 시
            vo.setMvinCtpvNm((String)item.get("mvinCtpvNm"));
            vo.setMvinSggNm((String)item.get("mvinSggNm"));
        }
    }
    
    // 경로 집계 DTO
    private static class RouteCount {
        String from;
        String to;
        int count;

        RouteCount(String from, String to, int count) {
            this.from = from;
            this.to = to;
            this.count = count;
        }
        
        public String getFrom() {
            return from;
        }

        public String getTo() {
            return to;
        }

        public int getCount() {
            return count;
        }
    }
    
    private String formatYmRange(String startYm, String endYm) {

        String start = startYm.substring(0,4) + "년 " + startYm.substring(4,6) + "월";
        String end   = endYm.substring(0,4) + "년 " + endYm.substring(4,6) + "월";

        // 같은 달이면 하나만 표시
        if(startYm.equals(endYm)) {
            return start + " 적재 완료";
        }

        return start + " ~ " + end + " 적재 완료";
    }
    
    /**
     * 배치 결과 누적용 내부 클래스
     */
    private static class BatchResult {
        int successCount = 0;
        List<String> errors = new ArrayList<>();

        // 내부 집계용 Map
        Map<String, Integer> routeMap = new java.util.LinkedHashMap<>();

        void addSuccess(int cnt) {
            successCount += cnt;
        }

        void addError(String msg) {
            errors.add(msg);
        }

        // 경로 집계
        void addRoute(String from, String to) {
            String key = from + "|" + to;
            routeMap.put(key, routeMap.getOrDefault(key, 0) + 1);
        }

        // 최종 JSON용 변환
        List<RouteCount> toRouteList() {
            List<RouteCount> list = new ArrayList<>();
            for (var entry : routeMap.entrySet()) {
                String[] parts = entry.getKey().split("\\|");
                list.add(new RouteCount(parts[0], parts[1], entry.getValue()));
            }
            return list;
        }
    }
    
    private Map<String, Object> buildResult(BatchResult r, String msg) {

        boolean success = r.errors.isEmpty();

        return Map.of(
                "success", success,
                "totalUpserted", r.successCount,
                "errorCount", r.errors.size(),
                "errors", r.errors,
                "routeCounts", r.toRouteList(),
                "message", msg
        );
    }
    
}
