package incheon.ags.ias.dataHoprReg.service.impl;

import incheon.ags.ias.dataHoprReg.mapper.DeptSyncMapper;
import incheon.ags.ias.dataHoprReg.service.DeptSyncService;
import incheon.ags.ias.dataHoprReg.util.DataFileParseUtil;
import incheon.ags.ias.dept.vo.DeptVO;
import incheon.com.cmm.exception.BusinessException;
import incheon.com.file.service.ComFileService;
import incheon.com.file.vo.ComFileDtlVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.nio.file.Paths;
import java.util.*;

/**
 * 부서 동기화 서비스 구현
 *
 * 파일 구조 (in_org.sam - 8개 컬럼, 헤더 없음):
 * - 인덱스 0: dept_nm (부서명)
 * - 인덱스 1: shghrk_inst_cd (차상위부서코드)
 * - 인덱스 2: dept_cd (부서코드, PK)
 * - 인덱스 3: sfrnd_se_cd (자치단체구분코드)
 * - 인덱스 4: dept_whol_nm (전체부서명)
 * - 인덱스 5: rank_no (서열)
 * - 인덱스 6: vr_ognz_tr_yn (가상조직트리여부)
 * - 인덱스 7: vr_ognz_tr_up_cd (가상조직트리상위코드)
 */
@Slf4j
@Service("deptSyncService")
@RequiredArgsConstructor
public class DeptSyncServiceImpl extends EgovAbstractServiceImpl implements DeptSyncService {

    private final DeptSyncMapper deptSyncMapper;
    private final ComFileService comFileService;

    @Value("${Globals.comfile.storage.path:../upload}")
    private String uploadPath;

    /**
     * 대표기관명 매핑 (Fallback용)
     * - 대표기관 부서가 아직 DB에 없을 때 사용
     */
    private static final Map<String, String> RPRS_INST_NM_FALLBACK = new HashMap<>();
    static {
        RPRS_INST_NM_FALLBACK.put("6280000", "인천광역시");
        RPRS_INST_NM_FALLBACK.put("3490000", "인천광역시 중구");
        RPRS_INST_NM_FALLBACK.put("3500000", "인천광역시 동구");
        RPRS_INST_NM_FALLBACK.put("3510500", "인천광역시 미추홀구");
        RPRS_INST_NM_FALLBACK.put("3520000", "인천광역시 연수구");
        RPRS_INST_NM_FALLBACK.put("3530000", "인천광역시 남동구");
        RPRS_INST_NM_FALLBACK.put("3540000", "인천광역시 부평구");
        RPRS_INST_NM_FALLBACK.put("3550000", "인천광역시 계양구");
        RPRS_INST_NM_FALLBACK.put("3560000", "인천광역시 서구");
        RPRS_INST_NM_FALLBACK.put("3570000", "인천광역시 강화군");
        RPRS_INST_NM_FALLBACK.put("3580000", "인천광역시 옹진군");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int syncFromFile(String atchFileId, String operatorId) throws Exception {
        log.info("========== 부서 파일 동기화 시작 (atchFileId: {}) ==========", atchFileId);

        // 1. 파일 정보 조회
        List<ComFileDtlVO> fileList = comFileService.selectComFileDtlList(atchFileId);
        if (fileList == null || fileList.isEmpty()) {
            throw new BusinessException("첨부파일 정보를 찾을 수 없습니다.");
        }
        ComFileDtlVO fileInfo = fileList.get(0);

        // 2. 파일 경로 구성
        String filePath = Paths.get(uploadPath, fileInfo.getStrgPathNm(), fileInfo.getStrgFileNm()).toString();
        File file = new File(filePath);
        if (!file.exists()) {
            throw new BusinessException("파일이 존재하지 않습니다: " + filePath);
        }
        log.info("파일 경로: {}", filePath);

        // 3. 파일 파싱 (헤더 없음, CP949 우선)
        List<List<String>> dataRows = DataFileParseUtil.parseFile(file, false, null);
        log.info("파싱 완료 - 데이터 행 수: {}", dataRows.size());

        // 4. VO 변환 (고정 인덱스 기반)
        List<DeptVO> deptList = new ArrayList<>();
        int skippedCount = 0;
        for (List<String> row : dataRows) {
            DeptVO vo = convertToDeptVO(null, row);
            if (vo.getDeptCd() != null && !vo.getDeptCd().isEmpty()) {
                deptList.add(vo);
            } else {
                skippedCount++;
            }
        }
        log.info("유효한 부서 데이터 건수: {}, 스킵된 건수: {}", deptList.size(), skippedCount);
        if (!deptList.isEmpty()) {
            log.info("첫 번째 유효 데이터 예시 - deptCd: {}, deptNm: {}, deptWholNm: {}",
                    deptList.get(0).getDeptCd(), deptList.get(0).getDeptNm(), deptList.get(0).getDeptWholNm());
        }

        // 5. 동기화 실행
        Map<String, Integer> syncResult = syncDepts(deptList, operatorId);
        int totalProcessed = syncResult.get("inserted") + syncResult.get("updated") + syncResult.get("deleted");

        log.info("========== 부서 파일 동기화 완료 ==========");
        return totalProcessed;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Integer> syncDepts(List<DeptVO> fileDataList, String operatorId) throws Exception {
        log.info("========== 부서 동기화 시작 ==========");
        log.info("파일 데이터 건수: {}", fileDataList.size());

        // 기존 DB 부서 조회
        List<DeptVO> existingDepts = deptSyncMapper.selectAllDepts();
        Map<String, DeptVO> existingDeptMap = new HashMap<>();
        for (DeptVO dept : existingDepts) {
            existingDeptMap.put(dept.getDeptCd(), dept);
        }
        log.info("기존 DB 부서 건수: {}", existingDeptMap.size());

        // 파일 데이터 Map 변환
        Map<String, DeptVO> fileDeptMap = new HashMap<>();
        for (DeptVO fileDept : fileDataList) {
            if (fileDept.getDeptCd() != null && !fileDept.getDeptCd().isEmpty()) {
                fileDeptMap.put(fileDept.getDeptCd(), fileDept);
            }
        }

        List<DeptVO> toInsert = new ArrayList<>();
        List<DeptVO> toUpdate = new ArrayList<>();
        List<String> toDelete = new ArrayList<>();
        int unchangedCount = 0;

        // INSERT/UPDATE 분류
        for (Map.Entry<String, DeptVO> entry : fileDeptMap.entrySet()) {
            String deptCd = entry.getKey();
            DeptVO fileDept = entry.getValue();

            if (!existingDeptMap.containsKey(deptCd)) {
                // 신규 INSERT - 프리셋 적용
                applyPreset(fileDept);
                fileDept.setFrstRegId(operatorId);
                fileDept.setLastMdfcnId(operatorId);
                fileDept.setLOGIN_USER_ID(operatorId);
                toInsert.add(fileDept);
            } else {
                // UPDATE 여부 판단 (관심사 8개 컬럼만 비교)
                DeptVO existingDept = existingDeptMap.get(deptCd);
                if (isDeptChanged(existingDept, fileDept)) {
                    fileDept.setLastMdfcnId(operatorId);
                    fileDept.setLOGIN_USER_ID(operatorId);
                    toUpdate.add(fileDept);
                } else {
                    unchangedCount++;
                }
            }
        }

        // DELETE 분류
        for (String existingCd : existingDeptMap.keySet()) {
            if (!fileDeptMap.containsKey(existingCd)) {
                toDelete.add(existingCd);
            }
        }

        log.info("INSERT: {}, UPDATE: {}, DELETE: {}, 변경없음: {}",
                toInsert.size(), toUpdate.size(), toDelete.size(), unchangedCount);

        // UPDATE 처리 (이력 저장 후 관심사 컬럼만 업데이트)
        for (DeptVO dept : toUpdate) {
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("deptCd", dept.getDeptCd());
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "U");
            deptSyncMapper.insertDeptHistory(historyParam);
            deptSyncMapper.updateDeptSyncFields(dept);  // 관심사 컬럼만 UPDATE
        }

        // DELETE 처리 (이력 저장 후 삭제)
        for (String deptCd : toDelete) {
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("deptCd", deptCd);
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "D");
            deptSyncMapper.insertDeptHistory(historyParam);
            deptSyncMapper.deleteDept(deptCd);
        }

        // INSERT 처리 (프리셋 적용 후 저장, 이력 저장)
        for (DeptVO dept : toInsert) {
            deptSyncMapper.insertDept(dept);
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("deptCd", dept.getDeptCd());
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "C");
            deptSyncMapper.insertDeptHistory(historyParam);
        }

        log.info("========== 부서 동기화 완료 ==========");

        Map<String, Integer> result = new HashMap<>();
        result.put("inserted", toInsert.size());
        result.put("updated", toUpdate.size());
        result.put("deleted", toDelete.size());
        result.put("unchanged", unchangedCount);
        return result;
    }

    /**
     * 프리셋 컬럼 적용 (INSERT 시에만)
     * - dept_stcd: 'C' (활성)
     * - rprs_inst_cd/nm: 대표기관 코드로 부서 정보 조회
     * - fclt_mng_inst_cd/nm: 대표기관 부서의 값 사용
     * - road_mng_inst_cd/nm: 대표기관 부서의 값 사용 (예외 처리 포함)
     */
    private void applyPreset(DeptVO dept) {
        // 1. 상태코드 기본값
        dept.setDeptStcd("C");

        // 2. 대표기관코드 산출
        String rprsInstCd = calculateRprsInstCd(dept.getDeptCd());
        dept.setRprsInstCd(rprsInstCd);

        // 3. 대표기관 코드로 기존 부서 정보 조회
        if (rprsInstCd != null) {
            Map<String, Object> rprsInfo = deptSyncMapper.selectDeptInfo(rprsInstCd);
            if (rprsInfo != null && rprsInfo.get("rprsInstNm") != null) {
                // DB에서 조회 성공
                dept.setRprsInstNm((String) rprsInfo.get("rprsInstNm"));
                dept.setFcltMngInstCd((String) rprsInfo.get("fcltMngInstCd"));
                dept.setFcltMngInstNm((String) rprsInfo.get("fcltMngInstNm"));
                dept.setRoadMngInstCd((String) rprsInfo.get("roadMngInstCd"));
                dept.setRoadMngInstNm((String) rprsInfo.get("roadMngInstNm"));
            } else {
                // Fallback: 하드코딩 매핑 사용
                String fallbackNm = RPRS_INST_NM_FALLBACK.get(rprsInstCd);
                dept.setRprsInstNm(fallbackNm);
                dept.setFcltMngInstCd(rprsInstCd);
                dept.setFcltMngInstNm(fallbackNm);
                dept.setRoadMngInstCd(rprsInstCd);
                dept.setRoadMngInstNm(fallbackNm);
            }
        }

        // 4. 도로관리기관 예외 처리
        applyRoadMngInstException(dept);

        log.debug("프리셋 적용 - deptCd: {}, rprsInstCd: {}, rprsInstNm: {}",
                dept.getDeptCd(), dept.getRprsInstCd(), dept.getRprsInstNm());
    }

    /**
     * 도로관리기관 예외 처리
     * - 서구 검단행정과 (3560297) → 3560038
     * - 미추홀구 도시경관과 (3510597) → 3510572
     */
    private void applyRoadMngInstException(DeptVO dept) {
        String deptCd = dept.getDeptCd();
        if (deptCd == null) return;

        // 서구 검단행정과
        if ("3560297".equals(deptCd)) {
            dept.setRoadMngInstCd("3560038");
            dept.setRoadMngInstNm("인천광역시 서구 검단행정과");
            return;
        }

        // 미추홀구 도시경관과
        if ("3510597".equals(deptCd)) {
            dept.setRoadMngInstCd("3510572");
            dept.setRoadMngInstNm("인천광역시 미추홀구 도시경관과");
        }
    }

    /**
     * 대표기관코드 산출
     * - V코드, D코드: null
     * - 미추홀구 (351*): 3510500
     * - 일반: 앞 3자리 + "0000"
     */
    private String calculateRprsInstCd(String deptCd) {
        if (deptCd == null || deptCd.isEmpty()) {
            return null;
        }

        // V코드(가상분류), D코드(관리용) 제외
        if (deptCd.startsWith("V") || deptCd.startsWith("D")) {
            return null;
        }

        // 숫자 코드만 처리
        if (!deptCd.matches("^[0-9]+$")) {
            return null;
        }

        // 미추홀구 특수 처리
        if (deptCd.startsWith("351")) {
            return "3510500";
        }

        // 일반 룰: 앞 3자리 + "0000"
        if (deptCd.length() >= 3) {
            return deptCd.substring(0, 3) + "0000";
        }

        return null;
    }

    /**
     * 변경 감지 (관심사 8개 컬럼만 비교)
     * - dept_stcd는 비교 대상에서 제외 (프리셋 컬럼)
     */
    private boolean isDeptChanged(DeptVO existing, DeptVO file) {
        if (!equalsNullSafe(existing.getDeptNm(), file.getDeptNm())) return true;
        if (!equalsNullSafe(existing.getShghrkInstCd(), file.getShghrkInstCd())) return true;
        if (!equalsNullSafe(existing.getSfrndSeCd(), file.getSfrndSeCd())) return true;
        if (!equalsNullSafe(existing.getDeptWholNm(), file.getDeptWholNm())) return true;
        if (!equalsIntNullSafe(existing.getRankNo(), file.getRankNo())) return true;
        if (!equalsNullSafe(existing.getVrOgnzTrYn(), file.getVrOgnzTrYn())) return true;
        if (!equalsNullSafe(existing.getVrOgnzTrUpCd(), file.getVrOgnzTrUpCd())) return true;
        return false;
    }

    /**
     * VO 변환 (고정 인덱스 기반, 헤더 없음)
     *
     * 파일 컬럼 순서:
     * - 인덱스 0: dept_nm (부서명)
     * - 인덱스 1: shghrk_inst_cd (차상위부서코드)
     * - 인덱스 2: dept_cd (부서코드, PK)
     * - 인덱스 3: sfrnd_se_cd (자치단체구분코드)
     * - 인덱스 4: dept_whol_nm (전체부서명)
     * - 인덱스 5: rank_no (서열)
     * - 인덱스 6: vr_ognz_tr_yn (가상조직트리여부)
     * - 인덱스 7: vr_ognz_tr_up_cd (가상조직트리상위코드)
     */
    @Override
    public DeptVO convertToDeptVO(List<String> headers, List<String> values) {
        DeptVO vo = new DeptVO();

        if (values == null || values.size() < 3) {
            log.warn("유효하지 않은 데이터 행: {}", values);
            return vo;
        }

        // 고정 인덱스 기반 매핑
        if (values.size() > 0) vo.setDeptNm(getValueSafe(values, 0));
        if (values.size() > 1) vo.setShghrkInstCd(getValueSafe(values, 1));
        if (values.size() > 2) vo.setDeptCd(getValueSafe(values, 2));
        if (values.size() > 3) vo.setSfrndSeCd(getValueSafe(values, 3));
        if (values.size() > 4) vo.setDeptWholNm(getValueSafe(values, 4));
        if (values.size() > 5) {
            String rankNoStr = getValueSafe(values, 5);
            if (rankNoStr != null && !rankNoStr.isEmpty()) {
                try {
                    vo.setRankNo(Integer.parseInt(rankNoStr));
                } catch (NumberFormatException e) {
                    vo.setRankNo(null);
                }
            }
        }
        if (values.size() > 6) vo.setVrOgnzTrYn(getValueSafe(values, 6));
        if (values.size() > 7) vo.setVrOgnzTrUpCd(getValueSafe(values, 7));

        return vo;
    }

    /**
     * 안전한 값 추출
     */
    private String getValueSafe(List<String> values, int index) {
        if (values == null || index >= values.size()) {
            return null;
        }
        String value = values.get(index);
        return (value == null || value.trim().isEmpty()) ? null : value.trim();
    }

    @Override
    public DeptVO selectDeptByCd(String deptCd) throws Exception {
        return deptSyncMapper.selectDeptByCd(deptCd);
    }

    @Override
    public List<Map<String, Object>> selectDeptHstryList(Map<String, Object> param) throws Exception {
        return deptSyncMapper.selectDeptHstryList(param);
    }

    @Override
    public int selectDeptHstryCnt(Map<String, Object> param) throws Exception {
        return deptSyncMapper.selectDeptHstryCnt(param);
    }

    @Override
    public Map<String, Object> selectDeptHstryDetail(Long deptHstrySn) throws Exception {
        return deptSyncMapper.selectDeptHstryDetail(deptHstrySn);
    }

    // ==================== 유틸 메서드 ====================

    private boolean equalsNullSafe(String a, String b) {
        String sa = (a == null) ? "" : a.trim();
        String sb = (b == null) ? "" : b.trim();
        return sa.equals(sb);
    }

    private boolean equalsIntNullSafe(Integer a, Integer b) {
        int ia = (a == null) ? 0 : a;
        int ib = (b == null) ? 0 : b;
        return ia == ib;
    }
}
