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.mapper.AgsRstPpltnMapper;
import incheon.ags.ias.rst.link.service.AgsRstPlmvService;
import incheon.ags.ias.rst.link.service.AgsRstPpltnService;
import incheon.ags.ias.rst.link.vo.AgsRstPlmvVO;
import incheon.ags.ias.rst.link.vo.AgsRstPpltnVO;
import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName : AgsRstPpltnServiceImpl.java
 * @Description : 인구현황통계 API 연계 서비스 구현체
 * @author : 이주훈
 * @since : 2026.02.25
 * @version : 1.0
 */
@Slf4j
@Service
public class AgsRstPpltnServiceImpl  implements AgsRstPpltnService {
	
    private final AgsRstPpltnMapper agsRstPpltnMapper;
    @Autowired private ObjectMapper objectMapper;

    /**
     * SSL 보안이 적용된 RestTemplate
     *  - Bean 초기화 시점에 1회 생성
     */
    private RestTemplate restTemplate; 
    
    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 AgsRstPpltnServiceImpl(AgsRstPpltnMapper agsRstPpltnMapper) {
        this.agsRstPpltnMapper = agsRstPpltnMapper;
    }
    
    /**
     *  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_SGG_CODES = {
    	    "2811000000", // 중구
    	    "2814000000", // 동구
    	    "2817700000", // 미추홀구
    	    "2818500000", // 연수구
    	    "2820000000", // 남동구
    	    "2823700000", // 부평구
    	    "2824500000", // 계양구
    	    "2826000000", // 서구
    	    "2871000000", // 강화군
    	    "2872000000"  // 옹진군
    	};
    
    // 전체 적재
    @Transactional
    @Override
    public Map<String, Object> insertPpltnStatsAll(String level) {

        BatchResult result = new BatchResult();

        String dbLastYm = agsRstPpltnMapper.selectMaxYm(level);
        if (dbLastYm == null)
            dbLastYm = "202210";

        String apiLastYm = findLatestAvailableYm(level);

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

        List<String> months = createMonthRange("202210", apiLastYm);

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

        return buildResult(result, formatYmRange(months.get(0), months.get(months.size()-1)));
    }
    
    // 증분 적재
    @Transactional
    @Override
    public Map<String, Object> insertPpltnStatsIncremental(String level) {

        BatchResult result = new BatchResult();

        String dbLastYm = agsRstPpltnMapper.selectMaxYm(level);
        if (dbLastYm == null)
            dbLastYm = "202210";

        String apiLastYm = findLatestAvailableYm(level);
        log.info("DB YM = {}", dbLastYm);
        log.info("API YM = {}", apiLastYm);

        if (dbLastYm.compareTo(apiLastYm) >= 0) {
        	return Map.of(
        		    "success", true,
        		    "totalUpserted", 0,
        		    "errorCount", 0,
        		    "errors", List.of(),
        		    "message", "현재 DB가 API 최신 기준까지 모두 반영된 상태입니다.",
        		    "latestYm", apiLastYm,
        		    "isLatest", true
    		);
        }

        String startYm = YearMonth.parse(dbLastYm, DateTimeFormatter.ofPattern("yyyyMM"))
                .plusMonths(1)
                .format(DateTimeFormatter.ofPattern("yyyyMM"));

        List<String> months = createMonthRange(startYm, apiLastYm);

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

        return buildResult(result, formatYmRange(months.get(0), months.get(months.size()-1)));
    }
    
    // 시작월 ~ 종료월까지 월 리스트 생성
    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 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 + " 적재 완료";
    }
    
    // 인구현황 API 기준 최신월 탐색 분기
    private String findLatestAvailableYm(String level) {

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

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

            String targetYm = ym.format(fmt);

            try {

                boolean exists;

                if ("EMDLI".equals(level)) {
                    exists = checkApiDataExists("2811000000", "3", targetYm); // 중구 하나만 체크
                } else {
                    exists = checkApiDataExists("2800000000", convertLevel(level), targetYm);
                }

                if (exists) {
                    return targetYm;
                }

            } catch (Exception e) {
                log.warn("최신월 탐색 실패 {}", targetYm);
            }

            ym = ym.minusMonths(1);
        }

        throw new RuntimeException("API 최신월 탐색 실패 (12개월)");
    }
    
    // API 최신 날짜 조회
    private boolean checkApiDataExists(String stdgCd, String lv, String ym) throws Exception {
	
		URI uri = UriComponentsBuilder
			.fromHttpUrl("https://apis.data.go.kr/1741000/stdgPpltnHhStus/selectStdgPpltnHhStus")
			.queryParam("serviceKey", SERVICE_KEY)
			.queryParam("stdgCd", stdgCd)
			.queryParam("srchFrYm", ym)
			.queryParam("srchToYm", ym)
			.queryParam("lv", lv)
			.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;
	}
    
    // API 호출 분기
    private void fetchMonth(String level, String ym, BatchResult result) {

        switch(level){

            case "SIDO":
                callApi("2800000000", "5", level, ym, result);
                break;

            case "SGG":
                callApi("2800000000", "2", level, ym, result);
                break;

            case "EMDLI":
                fetchEmd(ym, result);
                break;

        }
    }
    
    // API 호출
    private void callApi(String stdgCd, String lv, String level, String ym, BatchResult result){
		try {
			URI uri = UriComponentsBuilder
			       .fromHttpUrl("https://apis.data.go.kr/1741000/stdgPpltnHhStus/selectStdgPpltnHhStus")
			       .queryParam("serviceKey", SERVICE_KEY)
			       .queryParam("stdgCd", stdgCd)
			       .queryParam("srchFrYm", ym)
			       .queryParam("srchToYm", ym)
			       .queryParam("lv", lv)
			       .queryParam("regSeCd", "1")
			       .queryParam("numOfRows", "100")
			       .queryParam("pageNo", "1")
			       .queryParam("type", "json")
			       .build(true)
			       .toUri();
			
			log.info("API 호출 → {}", uri);
			
			ResponseEntity<String> res = restTemplate.getForEntity(uri, String.class);
			
			int saved = parseAndSave(level, res.getBody(), ym, result);
			result.addSuccess(saved);
			
		} catch (Exception e) {
			log.error("API 실패 [{} {} {}]", level, stdgCd, ym, e);
			result.addError(level + " " + ym + " 실패");
		}
	}
    
    // 읍면동리 API 호출
    private void fetchEmd(String ym, BatchResult result){

        for(String sggCd : INCHEON_SGG_CODES){

            int pageNo = 1;
            int totalCount = 0;

            do {

                try {

                    URI uri = UriComponentsBuilder
                            .fromHttpUrl("https://apis.data.go.kr/1741000/stdgPpltnHhStus/selectStdgPpltnHhStus")
                            .queryParam("serviceKey", SERVICE_KEY)
                            .queryParam("stdgCd", sggCd)
                            .queryParam("srchFrYm", ym)
                            .queryParam("srchToYm", ym)
                            .queryParam("lv", "3")
                            .queryParam("regSeCd", "1")
                            .queryParam("numOfRows", "100")
                            .queryParam("pageNo", pageNo)
                            .queryParam("type", "json")
                            .build(true)
                            .toUri();

                    log.info("EMDLI API 호출 → {}", uri);

                    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");

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

                    totalCount = Integer.parseInt(head.get("totalCount").toString());

                    int saved = parseAndSave("EMDLI", res.getBody(), ym, result);

                    result.addSuccess(saved);

                    pageNo++;

                } catch (Exception e) {
                    log.error("EMDLI 실패 {} {}", sggCd, ym, e);
                    result.addError("EMDLI " + sggCd + " 실패");
                    break;
                }

            } while((pageNo - 1) * 100 < totalCount);
        }
    }
    
    // Level 변환
    private String convertLevel(String level) {
        switch (level) {
            case "SIDO": return "5"; 	 		// 단일 시도
            case "SGG":  return "2";  			// 시군구
            case "EMDLI":  return "3";  		// 읍면동리
            default: throw new IllegalArgumentException("지원하지 않는 level");
        }
    }

    // 파싱 & 저장
    private int parseAndSave(String level, String json, String ym, BatchResult result) throws Exception {

        int savedCount = 0;

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

        Map<String,Object> items = (Map<String,Object>) response.get("items");
        if(items == null) return 0;

        Object rawItem = items.get("item");
        if(rawItem == null) return 0;

        List<Map<String,Object>> list = new ArrayList<>();
        if (rawItem instanceof List) {
            list = (List<Map<String,Object>>) rawItem;
        } else {
            list.add((Map<String,Object>) rawItem);
        }

        for(Map<String,Object> item : list){

            AgsRstPpltnVO vo = new AgsRstPpltnVO();

            vo.setStatsYm(ym);
            vo.setStdgCd(normalizeStdgCd(item.get("stdgCd")));
            vo.setCtpvNm((String)item.get("ctpvNm"));
            vo.setSggNm((String)item.get("sggNm"));
            vo.setEmdNm((String)item.get("stdgNm"));
            vo.setLiNm((String)item.get("liNm"));

            vo.setTppl(parseIntSafe(item.get("totNmprCnt")));
            vo.setHhCnt(parseIntSafe(item.get("hhCnt")));
            vo.setHhPpltnRt(parseDoubleSafe(item.get("hhNmpr")));
            vo.setMalePpltnCnt(parseIntSafe(item.get("maleNmprCnt")));
            vo.setFemalePpltnCnt(parseIntSafe(item.get("femlNmprCnt")));
            vo.setMawRt(parseDoubleSafe(item.get("maleFemlRate")));

            saveByLevel(level, vo);

            savedCount++;
            
            String regionName;

            switch(level) {
                case "SIDO":
                    regionName = vo.getCtpvNm();
                    break;
                case "SGG":
                    regionName = vo.getSggNm();
                    break;
                case "EMDLI":
                    if (vo.getLiNm() != null && !vo.getLiNm().isBlank()) {
                        regionName = vo.getEmdNm() + " " + vo.getLiNm();
                    } else {
                        regionName = vo.getEmdNm();
                    }
                    break;
                default:
                    regionName = "UNKNOWN";
            }

            result.addDetail(ym, level, regionName, 1);
        }

        return savedCount;
    }
    
    // 테이블 분기
    private void saveByLevel(String level, AgsRstPpltnVO vo){

        switch(level){
            case "SIDO":
                agsRstPpltnMapper.upsertSido(vo);
                break;

            case "SGG":
                agsRstPpltnMapper.upsertSgg(vo);
                break;

            case "EMDLI":
                agsRstPpltnMapper.upsertEmdLi(vo);
                break;

            default:
                throw new IllegalArgumentException("지원하지 않는 level");
        }
    }
    
    private String normalizeStdgCd(Object code){
        if(code == null) return null;
        return String.format("%-10s", code.toString()).replace(' ', '0');
    }

    private int parseIntSafe(Object value){
        if(value == null) return 0;
        return Integer.parseInt(value.toString().replace(",", ""));
    }

    private double parseDoubleSafe(Object value){
        if(value == null) return 0.0;
        return Double.parseDouble(value.toString().replace(",", ""));
    }
    
    // buildResult
    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,
                "message", msg,
                "detailCounts", r.detailCounts
        );
    }
    
    // BatchResult
    private static class BatchResult {

        int successCount = 0;
        List<String> errors = new ArrayList<>();

        List<Map<String, Object>> detailCounts = new ArrayList<>();

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

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

        void addDetail(String ym, String level, String region, int count) {
            detailCounts.add(Map.of(
                    "ym", ym,
                    "level", level,
                    "region", region,
                    "count", count
            ));
        }
    }
    
}
