package incheon.product.geoview2d.search.service.impl;

import incheon.com.cmm.exception.BusinessException;
import incheon.product.geoview2d.search.mapper.SearchMapper;
import incheon.product.geoview2d.search.service.SearchService;
import incheon.product.geoview2d.search.vo.AddressCoordinateVO;
import incheon.product.geoview2d.search.vo.AdmResultVO;
import incheon.product.geoview2d.search.vo.AdmSearchVO;
import incheon.product.geoview2d.search.vo.JibunSearchRequestVO;
import incheon.product.geoview2d.search.vo.JibunSearchResultVO;
import incheon.product.geoview2d.search.vo.PoiVO;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 통합 검색 서비스 구현체.
 */
@Slf4j
@Service("productSearchService")
@Transactional(readOnly = true)
public class SearchServiceImpl extends EgovAbstractServiceImpl implements SearchService {

    private static final Pattern JIBUN_PATTERN = Pattern.compile("\\d+(?:-\\d+)?");
    private static final Set<String> ALLOWED_SORT_COLUMNS = Set.of(
            "ctprvn_cd", "ctp_eng_nm", "ctp_kor_nm",
            "sig_cd", "sig_eng_nm", "sig_kor_nm",
            "emd_cd", "emd_eng_nm", "emd_kor_nm",
            "li_cd", "li_eng_nm", "li_kor_nm",
            "t.ctprvn_cd", "t.ctp_eng_nm", "t.ctp_kor_nm",
            "t.sig_cd", "t.sig_eng_nm", "t.sig_kor_nm",
            "t.emd_cd", "t.emd_eng_nm", "t.emd_kor_nm",
            "t.li_cd", "t.li_eng_nm", "t.li_kor_nm");
    private static final Set<String> ALLOWED_SORT_DIRECTIONS = Set.of("ASC", "DESC");

    @Resource(name = "productSearchMapper")
    private SearchMapper searchMapper;

    // ========== 주소 검색 ==========

    @Override
    public AddressCoordinateVO getCoordinateByBldgMngNo(String bldgMngNo) {
        return searchMapper.getCoordinateByBldgMngNo(bldgMngNo);
    }

    @Override
    public List<AddressCoordinateVO> getCoordinatesByBldgMngNo(List<String> bldgMngNoList) {
        return searchMapper.getCoordinatesByBldgMngNo(bldgMngNoList);
    }

    @Override
    public List<JibunSearchResultVO> getJibunSearchInfo(String keyword, int page, int size) {
        if (keyword == null || keyword.trim().isEmpty()) {
            throw new BusinessException("검색어를 입력해주세요.", HttpStatus.BAD_REQUEST);
        }

        JibunSearchRequestVO request = parseJibunKeyword(keyword.trim(), page, size);
        return searchMapper.getJibunSearchInfo(request);
    }

    // ========== 행정구역 검색 ==========

    @Override
    public List<AdmResultVO> getAdmList(AdmSearchVO searchVO) {
        sanitizeSortParams(searchVO);
        return switch (searchVO.getType().toUpperCase()) {
            case "CTP" -> searchMapper.selectCtpList(searchVO);
            case "SIG" -> searchMapper.selectSigList(searchVO);
            case "EMD" -> searchMapper.selectEmdList(searchVO);
            case "LI"  -> searchMapper.selectLiList(searchVO);
            default -> throw new BusinessException("유효하지 않은 행정구역 유형: " + searchVO.getType(), HttpStatus.BAD_REQUEST);
        };
    }

    @Override
    public long getAdmListTotCnt(AdmSearchVO searchVO) {
        sanitizeSortParams(searchVO);
        return switch (searchVO.getType().toUpperCase()) {
            case "CTP" -> searchMapper.selectCtpListTotCnt(searchVO);
            case "SIG" -> searchMapper.selectSigListTotCnt(searchVO);
            case "EMD" -> searchMapper.selectEmdListTotCnt(searchVO);
            case "LI"  -> searchMapper.selectLiListTotCnt(searchVO);
            default -> 0L;
        };
    }

    // ========== POI ==========

    @Override
    public PoiVO getPoiById(String nfId) {
        return searchMapper.findPoiById(nfId);
    }

    @Override
    public List<PoiVO> getAllPoi() {
        return searchMapper.findAllPoi();
    }

    @Override
    public List<PoiVO> getPoiList(String searchKeyword, String searchType, int page, int size) {
        int offset = (page - 1) * size;
        return searchMapper.findPoiWithPaging(searchKeyword, searchType, size, offset);
    }

    @Override
    public int getPoiTotalCount(String searchKeyword, String searchType) {
        return searchMapper.countPoi(searchKeyword, searchType);
    }

    @Override
    @Transactional
    public void createPoi(PoiVO poi) {
        searchMapper.insertPoi(poi);
    }

    @Override
    @Transactional
    public void updatePoi(PoiVO poi) {
        searchMapper.updatePoi(poi);
    }

    @Override
    @Transactional
    public void deletePoi(String nfId) {
        searchMapper.deletePoi(nfId);
    }

    // ========== Private ==========

    /** ${} 문자열 치환 SQL Injection 방어: sortColumn/sortDirection 화이트리스트 검증 */
    private void sanitizeSortParams(AdmSearchVO searchVO) {
        if (searchVO.getSortColumn() != null) {
            if (!ALLOWED_SORT_COLUMNS.contains(searchVO.getSortColumn().toLowerCase())) {
                searchVO.setSortColumn(null);
            }
        }
        if (searchVO.getSortDirection() != null) {
            if (!ALLOWED_SORT_DIRECTIONS.contains(searchVO.getSortDirection().toUpperCase())) {
                searchVO.setSortDirection("ASC");
            }
        }
    }

    private JibunSearchRequestVO parseJibunKeyword(String keyword, int page, int size) {
        JibunSearchRequestVO request = new JibunSearchRequestVO();
        request.setPage(page);
        request.setSize(size);

        String[] tokens = keyword.split("\\s+");
        String jibun = null;
        String lastAdm = null;

        // 마지막 토큰에서 지번 추출
        for (int i = tokens.length - 1; i >= 0; i--) {
            Matcher matcher = JIBUN_PATTERN.matcher(tokens[i]);
            if (matcher.matches()) {
                jibun = tokens[i];
                if (i > 0) {
                    lastAdm = tokens[i - 1];
                }
                break;
            }
        }

        if (jibun == null) {
            throw new BusinessException("지번 번호를 포함해주세요 (예: 123 또는 123-45)", HttpStatus.BAD_REQUEST);
        }

        request.setJibun(jibun);
        request.setLastAdm(lastAdm);

        // 행정구역 유형 추정
        if (lastAdm != null) {
            if (lastAdm.endsWith("리")) {
                request.setAdmType(JibunSearchRequestVO.AdmType.LI);
            } else if (lastAdm.endsWith("동") || lastAdm.endsWith("읍") || lastAdm.endsWith("면")) {
                request.setAdmType(JibunSearchRequestVO.AdmType.EMD);
            } else {
                request.setAdmType(JibunSearchRequestVO.AdmType.SIG);
            }
        }

        return request;
    }
}
