package incheon.ags.dss.regen.service.impl;

import incheon.ags.dss.regen.mapper.UrbFcltyAnlsMapper;
import incheon.ags.dss.regen.service.AnalysisService;
import incheon.ags.dss.regen.service.UrbFcltyAnlsService;
import incheon.ags.dss.regen.service.UrbTrgtClsfService;
import incheon.ags.dss.regen.vo.UrbFcltyAnlsDtlVO;
import incheon.ags.dss.regen.vo.UrbFcltyAnlsMstVO;
import incheon.ags.dss.regen.vo.UrbTrgtClsfMstVO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

@Service("analysisService")
@RequiredArgsConstructor
@Slf4j
public class AnalysisServiceImpl implements AnalysisService {

    @Autowired
    @Lazy
    private UrbTrgtClsfService urbTrgtClsfService;

    @Autowired
    @Lazy
    private UrbFcltyAnlsService urbFcltyAnlsService;
    
    private final UrbFcltyAnlsMapper mapper;

    /**
     * 도로망 그래프 구성 및 다익스트라 최단거리 계산
     */
    @Async("dssAnalysisExecutor")
    @Transactional
    @Override
    public void executeAnalysisInJava(Integer fcltAnlsNo) {
    	String resultCode = null;
        try {
            log.info(">>> [Async Start] Facility Analysis No: {}", fcltAnlsNo);

            // 1. 마스터 정보 조회
            UrbFcltyAnlsMstVO mst = mapper.selectUrbFcltyAnlsMstDetail(fcltAnlsNo);
            if (mst == null) throw new Exception("분석 마스터 정보를 찾을 수 없습니다.");

            // 2. 타겟 분류 번호(trgtClsfNo) 확인 및 자동 생성
            if (mst.getTrgtClsfNo() == null) {
                // [수정] 프로시저 호출 -> Java 로직 호출
                UrbTrgtClsfMstVO trgtVO = new UrbTrgtClsfMstVO();
                trgtVO.setZoneNo(mst.getZoneNo());
                trgtVO.setMdlAtrbCd(mst.getMainUsgCd());
                trgtVO.setRadu(mst.getRadu()); // 누락되었던 반경(radu) 세팅
                trgtVO.setTrgtClsfNm("[자동] 분석번호:" + fcltAnlsNo);
                trgtVO.setUserDsgnYn("N"); // 시스템 자동 생성
                trgtVO.setFrstRegId(mst.getUserId());
                trgtVO.setLastMdfcnId(mst.getUserId());

                int newClsfNo = urbTrgtClsfService.createTargetClassificationInJava(trgtVO);

                // 마스터 업데이트
                mst.setTrgtClsfNo(newClsfNo);
                mapper.updateUrbFcltyAnlsMstTarget(mst);
            }

            // 3. 기존 상세 데이터 삭제
            mapper.deleteUrbFcltyAnlsDtl(fcltAnlsNo);

            // --------------------------------------------------------
            // 4. 그래프 데이터 로딩 (DB -> Java)
            // --------------------------------------------------------
            log.info("Fetching Network Graph Data...");

            // 4-1. 도로망 엣지(Edge) 조회 (수정됨)
            log.info("Creating Temp Road Network Table...");
            mapper.createTempRoadNetwork(mst); // 1. 테이블 생성

            log.info("Indexing Temp Road Network...");
            mapper.createTempRoadIndex();      // 2. 인덱스 생성

            log.info("Fetching Edges from Temp Table...");
            List<Map<String, Object>> roadEdges = mapper.selectRoadEdgesFromTemp(); // 3. 조회

            // 4-2. 시작점(시설물) 초기 비용 조회 (시설물 -> 가장 가까운 도로)
            // Map Keys: pnu (road_pnu), cost (offset distance)
            List<Map<String, Object>> startNodes = mapper.selectFacilityStartNodes(mst);

            if (startNodes.isEmpty()) {
                log.warn("대상 시설물이 검색 범위 내에 없습니다.");
                resultCode = "NO_DATA";
                return;
            }

            // --------------------------------------------------------
            // 5. 다익스트라(Dijkstra) 알고리즘 수행
            // --------------------------------------------------------
            log.info("Running Dijkstra Algorithm...");

            // 그래프 구성: Adjacency List (PNU -> List<Neighbor>)
            Map<String, List<Node>> graph = new HashMap<>();
            for (Map<String, Object> edge : roadEdges) {
                String u = (String) edge.get("u");
                String v = (String) edge.get("v");
                double w = ((Number) edge.get("w")).doubleValue();

                graph.computeIfAbsent(u, k -> new ArrayList<>()).add(new Node(v, w));
                graph.computeIfAbsent(v, k -> new ArrayList<>()).add(new Node(u, w));
            }

            // 최단 거리 맵 (Road PNU -> Shortest Dist)
            Map<String, Double> distMap = new HashMap<>();
            PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingDouble(Node::getWeight));

            // 시작점 초기화 (Multi-Source Dijkstra)
            for (Map<String, Object> start : startNodes) {
                String startPnu = (String) start.get("pnu");
                double initialCost = ((Number) start.get("cost")).doubleValue();

                if (initialCost > mst.getRadu().doubleValue()) continue;

                // 이미 더 짧은 경로로 등록된 경우 스킵, 아니면 업데이트
                if (!distMap.containsKey(startPnu) || distMap.get(startPnu) > initialCost) {
                    distMap.put(startPnu, initialCost);
                    pq.add(new Node(startPnu, initialCost));
                }
            }

            double maxRadius = mst.getRadu().doubleValue();

            while (!pq.isEmpty()) {
                Node current = pq.poll();
                String u = current.getId();
                double d = current.getWeight();

                // 이미 처리된 거리보다 크면 스킵
                if (d > distMap.getOrDefault(u, Double.MAX_VALUE)) continue;
                // 최대 반경 초과 시 탐색 중단 (가지치기)
                if (d > maxRadius) continue;

                if (graph.containsKey(u)) {
                    for (Node neighbor : graph.get(u)) {
                        String v = neighbor.getId();
                        double weight = neighbor.getWeight();
                        double newDist = d + weight;

                        if (newDist <= maxRadius) {
                            if (newDist < distMap.getOrDefault(v, Double.MAX_VALUE)) {
                                distMap.put(v, newDist);
                                pq.add(new Node(v, newDist));
                            }
                        }
                    }
                }
            }

            // --------------------------------------------------------
            // 6. 결과 매핑 및 저장
            // --------------------------------------------------------
            log.info("Mapping results to parcels and Saving...");

            // 6-1. 분석 대상 필지 조회 (이제 한 필지당 여러 roadPnu가 올 수 있음)
            List<Map<String, Object>> destParcels = mapper.selectDestinationParcels(mst);

            // [신규] 최단 거리 중복 제거용 맵 (Key: Parcel_PNU, Value: UrbFcltyAnlsDtlVO)
            Map<String, UrbFcltyAnlsDtlVO> minDestMap = new HashMap<>();

            for (Map<String, Object> parcel : destParcels) {
                String parcelPnu = (String) parcel.get("parcel_pnu");
                String roadPnu = (String) parcel.get("road_pnu");
                double offset = ((Number) parcel.get("offset_dist")).doubleValue();

                // 도로망에 도달 가능한 경우만 계산
                if (distMap.containsKey(roadPnu)) {
                    double totalDist = distMap.get(roadPnu) + offset;

                    if (totalDist <= maxRadius) {
                        // [수정] 기존 값과 비교하여 더 짧은 거리면 갱신 (Min Value Logic)
                        if (!minDestMap.containsKey(parcelPnu) ||
                            totalDist < minDestMap.get(parcelPnu).getDstnc().doubleValue()) {

                            UrbFcltyAnlsDtlVO dtl = new UrbFcltyAnlsDtlVO();
                            dtl.setFcltAnlsNo(String.valueOf(fcltAnlsNo));
                            dtl.setLtlndUnqNo(parcelPnu);
                            dtl.setDstnc(BigDecimal.valueOf(totalDist));
                            dtl.setFrstRegId(mst.getUserId());
                            dtl.setLastMdfcnId(mst.getUserId());

                            minDestMap.put(parcelPnu, dtl);
                        }
                    }
                }
            }

            // [수정] Map에서 최종 리스트 추출
            List<UrbFcltyAnlsDtlVO> insertList = new ArrayList<>(minDestMap.values());

            // 6-2. Batch Insert
            int batchSize = 1000;
            if (!insertList.isEmpty()) {
                for (int i = 0; i < insertList.size(); i += batchSize) {
                    List<UrbFcltyAnlsDtlVO> batch = insertList.subList(i, Math.min(i + batchSize, insertList.size()));
                    mapper.insertUrbFcltyAnlsDtlBatch(batch);
                }
            }

            log.info("Analysis Complete. Inserted {} rows.", insertList.size());

            resultCode = "SUCCESS";

            log.info("<<< [Async End] Facility Analysis Complete No: {}", fcltAnlsNo);

         // 정상적으로 다 돌았으면
            resultCode = "SUCCESS";
            
        } catch (Exception e) {
            log.error("시설물 분석 비동기 처리 중 예외 발생 (fcltAnlsNo: {})", fcltAnlsNo);
            log.error("Analysis Failed", e);
            resultCode = "ERROR"; // 명시적 에러 설정
        } finally {
        	try {
                if (urbFcltyAnlsService != null) {
                    // null이면 기본값 SUCCESS로 처리하여 ID 복구
                    String code = (resultCode == null) ? "SUCCESS" : resultCode;
                    urbFcltyAnlsService.completeAnalysisStatus(fcltAnlsNo, code);
                } else {
                    log.error("urbFcltyAnlsService is null. Cannot update status.");
                }
            } catch (Exception e) {
                log.error("Analysis Failed", e);
                resultCode = "ERROR"; // 에러 상태 명시
            } finally {
                try {
                    // 서비스가 null이 아니고, 상태가 아직 업데이트되지 않았다면 수행
                    if (urbFcltyAnlsService != null) {
                        // [수정 3] resultCode 그대로 사용 (ERROR, NO_DATA, SUCCESS 중 하나)
                        urbFcltyAnlsService.completeAnalysisStatus(fcltAnlsNo, resultCode);
                    } else {
                        log.error("urbFcltyAnlsService is null. Cannot update status for No: {}", fcltAnlsNo);
                    }
                } catch (Exception ex) {
                    log.error("Failed to update final status", ex);
                }
                log.info("<<< [Async End] Analysis No: {}", fcltAnlsNo);
            }
            
            log.info("<<< [Async End] Analysis No: {}", fcltAnlsNo);
        }
    }

    // --- 내부 Helper Class ---
    @AllArgsConstructor
    @Getter
    private static class Node {
        private String id;      // PNU
        private double weight;  // Distance
    }
}
