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

import incheon.com.cmm.exception.BusinessException;
import incheon.com.cmm.exception.EntityNotFoundException;
import incheon.product.common.config.GeoViewProperties;
import incheon.product.common.geo.GisServerClient;
import incheon.product.geoview2d.layer.mapper.LayerMapper;
import incheon.product.geoview2d.layer.service.TaskLayerService;
import incheon.product.geoview2d.layer.vo.TaskLayerSearchVO;
import incheon.product.geoview2d.layer.vo.TaskLayerVO;
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 org.springframework.web.client.RestClientException;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 업무 레이어 관리 서비스 구현체.
 * 업무 레이어 CRUD 및 GIS 서버 연동을 담당한다.
 */
@Slf4j
@Service("productTaskLayerService")
public class TaskLayerServiceImpl extends EgovAbstractServiceImpl implements TaskLayerService {

    @Resource(name = "productLayerMapper")
    private LayerMapper layerMapper;

    @Resource(name = "gisServerClient")
    private GisServerClient gisServerClient;

    @Resource(name = "geoViewProperties")
    private GeoViewProperties geoViewProperties;

    @Override
    public TaskLayerVO getById(Integer taskLyrId) {
        return layerMapper.selectTaskLayerById(taskLyrId);
    }

    @Override
    public List<TaskLayerVO> getAll() {
        return layerMapper.findAll();
    }

    @Override
    public List<TaskLayerVO> getList(String searchKeyword, String searchType, int page, int size) {
        int offset = (page - 1) * size;
        return layerMapper.findAllWithPaging(searchKeyword, searchType, size, offset);
    }

    @Override
    public int getTotalCount(String searchKeyword, String searchType) {
        return layerMapper.count(searchKeyword, searchType);
    }

    @Override
    public List<TaskLayerVO> getTaskLayerList(TaskLayerSearchVO searchVO, Set<String> removedGroupCd) {
        if (removedGroupCd == null || removedGroupCd.isEmpty()) {
            return layerMapper.selectTaskLayerList(searchVO);
        }
        return layerMapper.selectTaskLayerListNotInGroupCd(searchVO, removedGroupCd);
    }

    @Override
    public long getTaskLayerListTotCnt(TaskLayerSearchVO searchVO, Set<String> removedGroupCd) {
        if (removedGroupCd == null || removedGroupCd.isEmpty()) {
            return layerMapper.selectTaskLayerListTotCnt(searchVO);
        }
        return layerMapper.selectTaskLayerListNotInGroupCdTotCnt(searchVO, removedGroupCd);
    }

    @Override
    @Transactional
    public void create(TaskLayerVO taskLayer) {
        String servicePrefix = geoViewProperties.getLayer().getServicePrefix();
        taskLayer.setLyrSrvcPrefix(servicePrefix);

        publishToGisServer(taskLayer);
        layerMapper.insert(taskLayer);
        log.info("업무 레이어 생성 완료 - lyrSrvcNm: {}", taskLayer.getLyrSrvcNm());
    }

    @Override
    @Transactional
    public void update(TaskLayerVO taskLayer) {
        TaskLayerVO existing = layerMapper.selectTaskLayerById(taskLayer.getTaskLyrId());
        if (existing == null) {
            throw new EntityNotFoundException("업무 레이어를 찾을 수 없습니다: " + taskLayer.getTaskLyrId());
        }

        boolean serviceNameChanged = !Objects.equals(existing.getLyrSrvcNm(), taskLayer.getLyrSrvcNm());
        if (serviceNameChanged) {
            deleteFromGisServer(existing);
            publishToGisServer(taskLayer);
        } else {
            updateToGisServer(taskLayer);
        }

        layerMapper.update(taskLayer);
        log.info("업무 레이어 수정 완료 - taskLyrId: {}", taskLayer.getTaskLyrId());
    }

    @Override
    @Transactional
    public void metaUpdate(TaskLayerVO taskLayer) {
        layerMapper.update(taskLayer);
        log.info("업무 레이어 메타 수정 완료 - taskLyrId: {}", taskLayer.getTaskLyrId());
    }

    @Override
    @Transactional
    public void delete(Integer taskLyrId) {
        TaskLayerVO existing = layerMapper.selectTaskLayerById(taskLyrId);
        if (existing != null) {
            deleteFromGisServer(existing);
        }
        layerMapper.delete(taskLyrId);
        log.info("업무 레이어 삭제 완료 - taskLyrId: {}", taskLyrId);
    }

    @Override
    public List<Map<String, Object>> getGroupCodes() {
        return layerMapper.selectGroupCodes();
    }

    @Override
    public Map<String, Object> verifyGeometryTable(String schema, String tableNm, String lyrSrvcNm, String lyrPhysNm) {
        Map<String, Object> result = new HashMap<>();
        List<String> errors = new ArrayList<>();

        // geometry_columns 확인
        List<Map<String, Object>> geomInfo = layerMapper.verifyGeometryTable(schema, tableNm);
        if (geomInfo == null || geomInfo.isEmpty()) {
            errors.add("공간 테이블을 찾을 수 없습니다: " + schema + "." + tableNm);
            result.put("success", false);
            result.put("errors", errors);
            return result;
        }

        Map<String, Object> firstGeom = geomInfo.get(0);
        result.put("geomType", firstGeom.get("type"));
        result.put("srid", firstGeom.get("srid"));

        // 물리명 중복 체크
        Map<String, Object> phyParams = new HashMap<>();
        phyParams.put("lyrPhysNm", lyrPhysNm);
        Integer phyCount = layerMapper.verifyTaskPhyTable(phyParams);
        if (phyCount != null && phyCount > 0) {
            errors.add("이미 등록된 물리명입니다: " + lyrPhysNm);
        }

        // 서비스명 중복 체크
        Map<String, Object> srvParams = new HashMap<>();
        srvParams.put("lyrSrvcNm", lyrSrvcNm);
        Integer srvCount = layerMapper.verifyTaskSrvTable(srvParams);
        if (srvCount != null && srvCount > 0) {
            errors.add("이미 등록된 서비스명입니다: " + lyrSrvcNm);
        }

        // GIS 서버 레이어 존재 확인
        String workspace = geoViewProperties.getGisServer().getWorkspace();
        if (gisServerClient.layerExists(workspace, lyrSrvcNm)) {
            errors.add("GIS 서버에 이미 존재하는 레이어입니다: " + lyrSrvcNm);
        }

        result.put("success", errors.isEmpty());
        result.put("errors", errors);
        return result;
    }

    // ========== GIS 서버 연동 ==========

    private void publishToGisServer(TaskLayerVO layer) {
        String physNm = layer.getLyrPhysNm();
        if (physNm == null || !physNm.contains(".")) {
            throw new BusinessException("레이어 물리 명은 '스키마.테이블' 형식이어야 합니다: " + physNm, HttpStatus.BAD_REQUEST);
        }
        String workspace = geoViewProperties.getGisServer().getWorkspace();
        int srid = layer.getCntm() != null ? layer.getCntm() : geoViewProperties.getCoordinate().getServiceSrid();

        gisServerClient.publishLayer(workspace, layer.getLyrSrvcNm(), physNm, srid);
    }

    private void updateToGisServer(TaskLayerVO layer) {
        String workspace = geoViewProperties.getGisServer().getWorkspace();
        try {
            if (gisServerClient.layerExists(workspace, layer.getLyrSrvcNm())) {
                gisServerClient.updateLayer(workspace, layer.getLyrSrvcNm());
            } else {
                publishToGisServer(layer);
            }
        } catch (RestClientException e) {
            log.warn("GIS 서버 업데이트 실패 - lyrSrvcNm: {}", layer.getLyrSrvcNm(), e);
            publishToGisServer(layer);
        }
    }

    private void deleteFromGisServer(TaskLayerVO layer) {
        String workspace = geoViewProperties.getGisServer().getWorkspace();
        try {
            gisServerClient.deleteLayer(workspace, layer.getLyrSrvcNm());
        } catch (RestClientException e) {
            log.warn("GIS 서버 레이어 삭제 실패 (무시) - lyrSrvcNm: {}", layer.getLyrSrvcNm(), e);
        }
    }
}
