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

import incheon.ags.ias.dataHoprReg.mapper.UserSyncMapper;
import incheon.ags.ias.dataHoprReg.service.UserSyncService;
import incheon.ags.ias.user.vo.UserVO;
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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 사용자 동기화 서비스 구현
 *
 * 파일 구조 (9개 컬럼, 파이프 구분, 헤더 없음):
 * 0: user_id      - 사용자 ID
 * 1: user_nm      - 사용자 성명
 * 2: dept_cd      - 부서코드
 * 3: jbgd_cd      - 직급코드
 * 4: jbgd_nm      - 직급명
 * 5: jbps_nm      - 직위명
 * 6: jbps_cd      - 직위코드
 * 7: eml_addr     - 메일
 * 8: ofc_telno    - 사무실 전화번호
 */
@Slf4j
@Service("userSyncService")
@RequiredArgsConstructor
public class UserSyncServiceImpl extends EgovAbstractServiceImpl implements UserSyncService {

    private final UserSyncMapper userSyncMapper;
    private final ComFileService comFileService;

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

    /**
     * 대표기관명 매핑 (Fallback용)
     * - 부서 테이블에 rprs_inst_nm이 없을 때 사용
     */
    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("========== 사용자 파일 동기화 시작 ==========");

        if (atchFileId == null || atchFileId.trim().isEmpty()) {
            log.warn("atchFileId가 없습니다.");
            return 0;
        }

        // 1. 파일 정보 조회
        List<ComFileDtlVO> fileList = comFileService.selectComFileDtlList(atchFileId);
        if (fileList == null || fileList.isEmpty()) {
            log.warn("파일을 찾을 수 없습니다. atchFileId: {}", atchFileId);
            throw new BusinessException("파일을 찾을 수 없습니다.");
        }

        ComFileDtlVO fileInfo = fileList.get(0);
        log.info("파일 정보: {} ({})", fileInfo.getOrgnlFileNm(), fileInfo.getFileSz());

        // 2. 파일 경로 구성
        Path basePath = Paths.get(uploadPath).toAbsolutePath().normalize();
        Path filePath = basePath.resolve(fileInfo.getStrgPathNm()).resolve(fileInfo.getStrgFileNm());
        File file = filePath.toFile();

        if (!file.exists()) {
            log.error("파일이 존재하지 않습니다: {}", filePath);
            throw new BusinessException("파일이 존재하지 않습니다.");
        }

        // 3. 파일 파싱 (헤더 없음, 고정 순서)
        List<UserVO> userDataList = parseUserFile(file);
        log.info("파싱된 사용자 수: {}", userDataList.size());

        // 4. 동기화 실행
        Map<String, Integer> syncResult = syncUsers(userDataList, operatorId);

        log.info("========== 사용자 파일 동기화 결과 ==========");
        log.info("INSERT: {} 건, UPDATE: {} 건, DELETE: {} 건",
                syncResult.get("inserted"), syncResult.get("updated"), syncResult.get("deleted"));

        return syncResult.get("inserted") + syncResult.get("updated") + syncResult.get("deleted");
    }

    /**
     * 파일 파싱 (헤더 없음, 고정 순서)
     */
    private List<UserVO> parseUserFile(File file) throws Exception {
        List<UserVO> userList = new ArrayList<>();

        // CP949 우선 시도 (한글 Windows 인코딩)
        Charset primaryEncoding = Charset.forName("MS949");
        Charset fallbackEncoding = StandardCharsets.UTF_8;

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(file), primaryEncoding))) {
            userList = parseLines(reader);
            log.info("파일 파싱 완료 ({}) - 데이터 행 수: {}", primaryEncoding.name(), userList.size());
        } catch (Exception e) {
            log.warn("{} 인코딩으로 읽기 실패, {}로 재시도: {}", primaryEncoding.name(), fallbackEncoding.name(), e.getMessage());
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(new FileInputStream(file), fallbackEncoding))) {
                userList = parseLines(reader);
                log.info("파일 파싱 완료 ({}) - 데이터 행 수: {}", fallbackEncoding.name(), userList.size());
            }
        }

        return userList;
    }

    /**
     * 라인별 파싱
     */
    private List<UserVO> parseLines(BufferedReader reader) throws Exception {
        List<UserVO> userList = new ArrayList<>();
        String line;
        int lineNum = 0;

        while ((line = reader.readLine()) != null) {
            lineNum++;
            if (line.trim().isEmpty()) continue;

            // BOM 제거
            if (lineNum == 1 && line.startsWith("\uFEFF")) {
                line = line.substring(1);
            }

            List<String> columns = parseLine(line);
            if (columns.size() < 9) {
                log.warn("{}번째 줄: 컬럼 수 부족 ({}개), 스킵", lineNum, columns.size());
                continue;
            }

            UserVO userVO = convertToUserVO(columns);
            if (userVO.getUserId() != null && !userVO.getUserId().isEmpty()) {
                userList.add(userVO);
            }
        }

        return userList;
    }

    /**
     * 라인 파싱 (파이프 구분)
     */
    private List<String> parseLine(String line) {
        List<String> result = new ArrayList<>();
        String[] parts = line.split("\\|", -1);
        for (String part : parts) {
            result.add(part.trim());
        }
        return result;
    }

    /**
     * 파일 컬럼 → VO 변환 (고정 순서)
     *
     * 0: user_id, 1: user_nm, 2: dept_cd, 3: jbgd_cd, 4: jbgd_nm,
     * 5: jbps_nm, 6: jbps_cd, 7: eml_addr, 8: ofc_telno
     */
    @Override
    public UserVO convertToUserVO(List<String> columns) {
        UserVO vo = new UserVO();

        vo.setUserId(getColumnValue(columns, 0));
        vo.setUserNm(getColumnValue(columns, 1));
        vo.setDeptCd(getColumnValue(columns, 2));
        vo.setJbgdCd(getColumnValue(columns, 3));
        vo.setJbgdNm(getColumnValue(columns, 4));
        vo.setJbpsNm(getColumnValue(columns, 5));
        vo.setJbpsCd(getColumnValue(columns, 6));
        vo.setEmlAddr(getColumnValue(columns, 7));
        vo.setOfcTelno(getColumnValue(columns, 8));

        return vo;
    }

    private String getColumnValue(List<String> columns, int index) {
        if (index < columns.size()) {
            String value = columns.get(index);
            return (value == null || value.isEmpty()) ? null : value;
        }
        return null;
    }

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

        // 기존 사용자 조회
        List<UserVO> existingUsers = userSyncMapper.selectAllUsers();
        Map<String, UserVO> existingUserMap = new HashMap<>();
        for (UserVO user : existingUsers) {
            existingUserMap.put(user.getUserId(), user);  // user_id 기준
        }
        log.info("기존 DB 사용자 건수: {}", existingUserMap.size());

        // 파일 데이터 맵
        Map<String, UserVO> fileUserMap = new HashMap<>();
        for (UserVO fileUser : fileDataList) {
            String userId = fileUser.getUserId();
            if (userId != null && !userId.isEmpty()) {
                fileUserMap.put(userId, fileUser);
            }
        }

        // 분류
        List<UserVO> toInsert = new ArrayList<>();
        List<UserVO> toUpdate = new ArrayList<>();
        List<String> toDelete = new ArrayList<>();
        int unchangedCount = 0;

        for (Map.Entry<String, UserVO> entry : fileUserMap.entrySet()) {
            String userId = entry.getKey();
            UserVO fileUser = entry.getValue();

            if (!existingUserMap.containsKey(userId)) {
                // 신규
                applyInsertPreset(fileUser, operatorId);
                toInsert.add(fileUser);
            } else {
                // 기존 존재
                UserVO existingUser = existingUserMap.get(userId);
                if (isUserChanged(existingUser, fileUser)) {
                    // 변경됨 - 관심사 컬럼만 업데이트
                    fileUser.setUserUnqId(existingUser.getUserUnqId());
                    fileUser.setLastMdfcnId(operatorId);
                    // 부서 정보 조회 (dept_nm만 업데이트에 필요)
                    Map<String, Object> deptInfo = userSyncMapper.selectDeptInfo(fileUser.getDeptCd());
                    fileUser.setDeptNm(deptInfo != null ? (String) deptInfo.get("deptNm") : "");
                    toUpdate.add(fileUser);
                } else {
                    unchangedCount++;
                }
            }
        }

        // 삭제 대상 (파일에 없는 기존 데이터)
        for (String existingUserId : existingUserMap.keySet()) {
            if (!fileUserMap.containsKey(existingUserId)) {
                toDelete.add(existingUserId);
            }
        }

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

        // UPDATE 처리 (이력 기록 후 업데이트)
        for (UserVO user : toUpdate) {
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("userUnqId", user.getUserUnqId());
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "U");
            userSyncMapper.insertUserHistory(historyParam);
            userSyncMapper.updateUserSyncFields(user);
        }

        // DELETE 처리 (이력 기록 후 삭제)
        for (String userId : toDelete) {
            UserVO existingUser = existingUserMap.get(userId);
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("userUnqId", existingUser.getUserUnqId());
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "D");
            userSyncMapper.insertUserHistory(historyParam);
            userSyncMapper.deleteUser(existingUser.getUserUnqId());
        }

        // INSERT 처리 (삽입 후 이력 기록)
        for (UserVO user : toInsert) {
            userSyncMapper.insertUser(user);
            Map<String, Object> historyParam = new HashMap<>();
            historyParam.put("userUnqId", user.getUserUnqId());
            historyParam.put("frstRegId", operatorId);
            historyParam.put("lastMdfcnId", operatorId);
            historyParam.put("cngypCd", "C");
            userSyncMapper.insertUserHistory(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 시 프리셋 적용 (부서 테이블에서 기관 정보 조회)
     */
    private void applyInsertPreset(UserVO user, String operatorId) {
        // 기본 프리셋
        user.setUserUnqId(user.getUserId());  // user_unq_id = user_id
        user.setUserStcd("C");                 // 활성
        user.setFrstRegId(operatorId);
        user.setLastMdfcnId(operatorId);

        // NOT NULL 컬럼 빈 문자열 처리
        if (user.getUserNm() == null) user.setUserNm("");
        if (user.getDeptCd() == null) user.setDeptCd("");
        if (user.getJbgdCd() == null) user.setJbgdCd("");
        if (user.getJbgdNm() == null) user.setJbgdNm("");

        // 부서 정보 조회 (부서명, 대표기관, 도로관리기관, 시설관리기관)
        Map<String, Object> deptInfo = userSyncMapper.selectDeptInfo(user.getDeptCd());
        if (deptInfo != null) {
            user.setDeptNm((String) deptInfo.get("deptNm"));
            String rprsInstCd = (String) deptInfo.get("rprsInstCd");
            String rprsInstNm = (String) deptInfo.get("rprsInstNm");

            // rprsInstNm이 null이면 fallback 사용
            if (rprsInstNm == null && rprsInstCd != null) {
                rprsInstNm = RPRS_INST_NM_FALLBACK.get(rprsInstCd);
            }

            user.setRprsInstCd(rprsInstCd);
            user.setRprsInstNm(rprsInstNm);
            user.setRoadMngInstCd((String) deptInfo.get("roadMngInstCd"));
            user.setRoadMngInstNm((String) deptInfo.get("roadMngInstNm"));
            user.setFcltMngInstCd((String) deptInfo.get("fcltMngInstCd"));
            user.setFcltMngInstNm((String) deptInfo.get("fcltMngInstNm"));
        } else {
            // 부서 테이블에 없는 경우: dept_cd로 대표기관 산출 후 fallback 적용
            user.setDeptNm("");
            String rprsInstCd = calculateRprsInstCd(user.getDeptCd());
            if (rprsInstCd != null) {
                String rprsInstNm = RPRS_INST_NM_FALLBACK.get(rprsInstCd);
                user.setRprsInstCd(rprsInstCd);
                user.setRprsInstNm(rprsInstNm);
                user.setFcltMngInstCd(rprsInstCd);
                user.setFcltMngInstNm(rprsInstNm);
                user.setRoadMngInstCd(rprsInstCd);
                user.setRoadMngInstNm(rprsInstNm);
            }
            // V코드, D코드 등은 rprsInstCd가 null → 기관 정보 null 유지
        }
    }

    /**
     * 대표기관코드 산출
     * - 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;
    }

    /**
     * 변경 여부 확인 (관심사 9개 컬럼만 비교)
     */
    private boolean isUserChanged(UserVO existing, UserVO file) {
        if (!equalsNullSafe(existing.getUserId(), file.getUserId())) return true;
        if (!equalsNullSafe(existing.getUserNm(), file.getUserNm())) return true;
        if (!equalsNullSafe(existing.getDeptCd(), file.getDeptCd())) return true;
        if (!equalsNullSafe(existing.getJbgdCd(), file.getJbgdCd())) return true;
        if (!equalsNullSafe(existing.getJbgdNm(), file.getJbgdNm())) return true;
        if (!equalsNullSafe(existing.getJbpsNm(), file.getJbpsNm())) return true;
        if (!equalsNullSafe(existing.getJbpsCd(), file.getJbpsCd())) return true;
        if (!equalsNullSafe(existing.getEmlAddr(), file.getEmlAddr())) return true;
        if (!equalsNullSafe(existing.getOfcTelno(), file.getOfcTelno())) return true;
        return false;
    }

    @Override
    public UserVO selectUserByUnqId(String userUnqId) throws Exception {
        return userSyncMapper.selectUserByUnqId(userUnqId);
    }

    @Override
    public Map<String, Object> selectUserMapByUnqId(String userUnqId) throws Exception {
        return userSyncMapper.selectUserMapByUnqId(userUnqId);
    }

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

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

    @Override
    public Map<String, Object> selectUserHstryDetail(Long userHstrySn) throws Exception {
        return userSyncMapper.selectUserHstryDetail(userHstrySn);
    }

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

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