package incheon.ags.pss.edit.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream; // Stream import 추가
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.annotation.Resource;

import org.geotools.api.data.DataStore;
import org.geotools.api.data.DataStoreFinder;
import org.geotools.api.data.FeatureSource;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.operation.MathTransform;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Geometry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import incheon.ags.pss.edit.mapper.LayerManageMapper;
import incheon.ags.pss.edit.service.LayerManageService;
import incheon.ags.pss.edit.vo.BoundaryDetailVO;
import incheon.ags.pss.edit.vo.BoundaryVO;
import incheon.ags.pss.project.mapper.ProjectMapper;

@Service("layerManageService")
public class LayerManageServiceImpl implements LayerManageService {
	@Autowired private ObjectMapper objectMapper;
    @Resource(name = "layerManageMapper")
    private LayerManageMapper mapper;
    
    @Resource(name = "projectMapper")
    private ProjectMapper projectMapper;

    @Override
    public List<BoundaryVO> selectBoundaryList(Long bizNo) throws Exception {
        return mapper.selectBoundaryList(bizNo);
    }
    
    @Override
    public BoundaryVO selectBoundary(Long bndryNo) throws Exception {
    	BoundaryVO vo = mapper.selectBoundary(bndryNo);
        if (vo != null) {
            vo.setDetailList( mapper.selectBoundaryDetailList(bndryNo) );
        }
    	return vo;
    }

    @Override
    @Transactional
    public void insertBoundary(BoundaryVO vo) throws Exception {
    	// 1. 마스터 INSERT (이름만)
        mapper.insertBoundary(vo);
        
        Long bndryNo = vo.getBndryNo();
        
        // 2. 상세 목록(detailList) INSERT
        if (!CollectionUtils.isEmpty(vo.getDetailList())) {
            for (BoundaryDetailVO detail : vo.getDetailList()) {
                detail.setBndryNo(vo.getBndryNo()); //방금 생성된 마스터 bndryNo set
                detail.setLOGIN_USER_ID(vo.getLOGIN_USER_ID());
                mapper.insertBoundaryDetail(detail);
            }
        }
        
        //3. 마스터 집계(Aggregation) 실행
        // (dtl -> mst)
        mapper.recalculateBoundaryMaster(bndryNo);
        
        //4. 안건지도 집계(Aggregation) 실행
        // (mst -> sc_biz_mst)
        projectMapper.recalculateProjectAggregates(vo.getBizNo());
        
    }

    @Override
    public void updateBoundary(BoundaryVO vo) throws Exception {
    	// 1. 마스터 UPDATE (이름만)
        mapper.updateBoundary(vo);
        
        // 2. 기존 상세 목록(sp_bndry_dtl)을 전부 삭제
        mapper.deleteBoundaryDetailByBndryNo(vo.getBndryNo());
        
        // 3. 프론트에서 받은 새 상세 목록(detailList)을 전부 INSERT
        if (!CollectionUtils.isEmpty(vo.getDetailList())) {
            for (BoundaryDetailVO detail : vo.getDetailList()) {
                detail.setBndryNo(vo.getBndryNo()); // 마스터 bndryNo set
                detail.setLOGIN_USER_ID(vo.getLOGIN_USER_ID());
                mapper.insertBoundaryDetail(detail);
            }
        }
        
        //4. 마스터 집계(Aggregation) 실행
        mapper.recalculateBoundaryMaster(vo.getBndryNo());
        
        //5. 안건지도 집계(Aggregation) 실행
        projectMapper.recalculateProjectAggregates(vo.getBizNo());
    }

    @Override
    public void deleteBoundary(Long bndryNo) throws Exception {
    	// 상세 구역 먼저 삭제
        mapper.deleteBoundaryDetailByBndryNo(bndryNo);
        // 마스터 구역 삭제
        mapper.deleteBoundary(bndryNo);
    }
    
    @Override
    public List<BoundaryDetailVO> selectBoundaryDetailList(Long bndryNo) throws Exception {
        return mapper.selectBoundaryDetailList(bndryNo);
    }

    @Override
    public void insertBoundaryDetail(BoundaryDetailVO vo) throws Exception {
        mapper.insertBoundaryDetail(vo);
    }

    @Override
    public void deleteBoundaryDetail(Long dgmNo) throws Exception {
        mapper.deleteBoundaryDetail(dgmNo);
    }
    
    //1. '구' 목록 조회
    @Override
    public List<Map<String, Object>> selectGuList() throws Exception {
        return mapper.selectGuList();
    }
    
    //2. '동' 목록 조회
    @Override
    public List<Map<String, Object>> selectDongList(String sigCd) throws Exception {
        return mapper.selectDongList(sigCd);
    }

    @Override
    public BoundaryDetailVO selectEmdDataForDetail(String emdCd) throws Exception {
        return mapper.selectEmdDataForDetail(emdCd); 
    }
    
    //교차 영역 검색
    @Override
    public BoundaryDetailVO findIntersectingArea(Map<String, Object> params) throws Exception {
        String geomWkt = (String) params.get("geom");
        String layerType = (String) params.get("layerType");
        
        if (!StringUtils.hasText(geomWkt) || !StringUtils.hasText(layerType)) {
            throw new Exception("좌표(geom) 또는 레이어 유형(layerType)이 없습니다.");
        }

        if ("emd".equals(layerType)) {
            // 읍면동(5179) 검색
            return mapper.findEmdByGeom(geomWkt);
        } 
        else if ("cadastral".equals(layerType)) {
            // 연속지적도(5186) 검색
            return mapper.findCadastralByGeom(geomWkt);
        }
        
        return null; // 해당 사항 없음
    }

    @Override
    public Map<String, Object> previewShape(MultipartFile file) throws Exception {
        Path tempDirPath = null;
        DataStore dataStore = null;
        
        try {
            // 1. 임시 디렉터리 생성
            tempDirPath = Files.createTempDirectory("shapefile_upload_");
            File tempZipFile = tempDirPath.resolve(file.getOriginalFilename()).toFile();
            file.transferTo(tempZipFile); // 업로드된 ZIP 파일을 임시 디렉터리에 저장

            // 2. ZIP 파일 압축 해제 및 .shp 파일 경로 찾기
            String shpFilePath = null;
            try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(tempZipFile.toPath()))) {
                ZipEntry zipEntry = zis.getNextEntry();
                byte[] buffer = new byte[1024];
                while (zipEntry != null) {
                    File newFile = tempDirPath.resolve(zipEntry.getName()).toFile();
                    // 디렉터리 구조 유지
                    if (zipEntry.isDirectory()) {
                        if (!newFile.isDirectory() && !newFile.mkdirs()) {
                            throw new IOException("Failed to create directory " + newFile.getAbsolutePath());
                        }
                    } else {
                        // 부모 디렉터리 생성
                        File parent = newFile.getParentFile();
                        if (parent != null && !parent.isDirectory() && !parent.mkdirs()) {
                            throw new IOException("Failed to create directory " + parent.getAbsolutePath());
                        }
                        // 파일 쓰기
                        try (FileOutputStream fos = new FileOutputStream(newFile)) {
                            int len;
                            while ((len = zis.read(buffer)) > 0) {
                                fos.write(buffer, 0, len);
                            }
                        }
                        if (newFile.getName().toLowerCase().endsWith(".shp")) {
                            shpFilePath = newFile.getAbsolutePath();
                        }
                    }
                    zipEntry = zis.getNextEntry();
                }
                zis.closeEntry();
            }

            if (shpFilePath == null) {
                throw new IOException("압축 파일 내에 .shp 파일이 없습니다.");
            }

            // 3. GeoTools가 Shapefile을 읽도록 URL 구성
            Map<String, Object> dataStoreParams = new HashMap<>();
            dataStoreParams.put("url", Paths.get(shpFilePath).toUri().toURL());
            dataStoreParams.put("charset", "EUC-KR"); // 한글 인코딩 설정

            dataStore = DataStoreFinder.getDataStore(dataStoreParams);
            String typeName = dataStore.getTypeNames()[0];
            FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);
            FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures();

            // 4. 좌표계 변환 및 FeatureCollection 생성
            CoordinateReferenceSystem sourceCRS = source.getSchema().getCoordinateReferenceSystem();
            CoordinateReferenceSystem targetCRS = DefaultGeographicCRS.WGS84;
            MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);

            SimpleFeatureType targetSchema = SimpleFeatureTypeBuilder.retype(source.getSchema(), targetCRS);
            List<SimpleFeature> transformedFeatures = new ArrayList<>();
            
            try (FeatureIterator<SimpleFeature> features = collection.features()) {
                while (features.hasNext()) {
                    SimpleFeature feature = features.next();
                    Geometry geom = (Geometry) feature.getDefaultGeometry();
                    Geometry transformedGeom = JTS.transform(geom, transform);

                    SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(targetSchema);
                    featureBuilder.addAll(feature.getAttributes());
                    featureBuilder.set(targetSchema.getGeometryDescriptor().getLocalName(), transformedGeom);
                    transformedFeatures.add(featureBuilder.buildFeature(feature.getID()));
                }
            }

            FeatureCollection<SimpleFeatureType, SimpleFeature> transformedCollection = new ListFeatureCollection(targetSchema, transformedFeatures);

            // 5. FeatureCollection을 GeoJSON Map으로 변환

            // try-with-resources 적용 (자동 close)
            try (StringWriter writer = new StringWriter()) {
                FeatureJSON fjson = new FeatureJSON();
                fjson.writeFeatureCollection(transformedCollection, writer);
                return objectMapper.readValue(writer.toString(), new TypeReference<Map<String, Object>>() {});
            } catch (IOException e) {
                throw new RuntimeException("GeoJSON 변환 실패", e);
            }

        } finally {
            // 6. 리소스 정리
            if (dataStore != null) {
                dataStore.dispose();
            }
            // 임시 디렉터리 및 내용 삭제
            if (tempDirPath != null && Files.exists(tempDirPath)) {
                try (Stream<Path> walk = Files.walk(tempDirPath)) {
                    walk.sorted(Comparator.reverseOrder())
                        .map(Path::toFile)
                        .forEach(File::delete);
                }
            }
        }
    }
}