package com.kamco.cd.kamcoback.geojson.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnDataEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnDataGeomEntity;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataGeomRepository;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataRepository;
import java.time.ZonedDateTime;
import java.util.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.geojson.GeoJsonReader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/** GeoJSON 데이터 처리 서비스 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeoJsonDataService {

  private final MapSheetLearnDataRepository mapSheetLearnDataRepository;
  private final MapSheetLearnDataGeomRepository mapSheetLearnDataGeomRepository;
  private final ObjectMapper objectMapper;
  private final GeoJsonReader geoJsonReader = new GeoJsonReader();

  /** GeoJSON 파일들을 데이터베이스에 저장 */
  public List<Long> processGeoJsonFiles(
      Map<String, String> geoJsonContents, String archiveFileName) {
    List<Long> savedIds = new ArrayList<>();

    log.info("GeoJSON 파일 처리 시작: {} ({}개 파일)", archiveFileName, geoJsonContents.size());

    // 개별 파일별로 독립적인 트랜잭션으로 처리
    for (Map.Entry<String, String> entry : geoJsonContents.entrySet()) {
      String fileName = entry.getKey();
      String geoJsonContent = entry.getValue();

      try {
        Long savedId = processGeoJsonFileWithTransaction(fileName, geoJsonContent, archiveFileName);
        if (savedId != null) {
          savedIds.add(savedId);
          log.debug("GeoJSON 파일 저장 성공: {} (ID: {})", fileName, savedId);
        }
      } catch (Exception e) {
        log.error("GeoJSON 파일 처리 실패: {}", fileName, e);
        // 개별 파일 처리 실패는 전체 처리를 중단시키지 않음
      }

      // 메모리 정리
      System.gc();
    }

    log.info(
        "GeoJSON 파일 처리 완료: {} (성공: {}개, 전체: {}개)",
        archiveFileName,
        savedIds.size(),
        geoJsonContents.size());

    return savedIds;
  }

  /** 개별 파일을 별도 트랜잭션으로 처리 */
  @Transactional
  public Long processGeoJsonFileWithTransaction(
      String fileName, String geoJsonContent, String archiveFileName) {
    try {
      Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName);
      if (savedId != null && isLearningModelResult(fileName, geoJsonContent)) {
        processLearningModelGeometryOptimized(savedId, geoJsonContent, fileName);
      }
      return savedId;
    } catch (Exception e) {
      log.error("파일 처리 중 트랜잭션 에러: {}", fileName, e);
      throw e; // 트랜잭션 롤백을 위해 재throw
    }
  }

  /** 개별 GeoJSON 파일을 MapSheetLearnDataEntity로 변환하여 저장 */
  private Long processGeoJsonFile(String fileName, String geoJsonContent, String archiveFileName) {
    try {
      // GeoJSON 파싱 및 검증
      JsonNode geoJsonNode = objectMapper.readTree(geoJsonContent);
      validateGeoJsonStructure(geoJsonNode);

      // 파일이 이미 처리되었는지 확인
      String dataPath = generateDataPath(archiveFileName, fileName);
      Optional<MapSheetLearnDataEntity> existingData =
          mapSheetLearnDataRepository.findByDataPath(dataPath);

      if (existingData.isPresent()) {
        log.warn("이미 처리된 파일입니다: {}", dataPath);
        return existingData.get().getId();
      }

      // 새 엔티티 생성 및 저장
      MapSheetLearnDataEntity entity =
          createMapSheetLearnDataEntity(fileName, geoJsonContent, archiveFileName, geoJsonNode);
      MapSheetLearnDataEntity savedEntity = mapSheetLearnDataRepository.save(entity);

      return savedEntity.getId();

    } catch (Exception e) {
      log.error("GeoJSON 파일 처리 중 오류 발생: {}", fileName, e);
      throw new RuntimeException("GeoJSON 파일 처리 실패: " + fileName, e);
    }
  }

  /** GeoJSON 구조 검증 */
  private void validateGeoJsonStructure(JsonNode geoJsonNode) {
    if (!geoJsonNode.has("type")) {
      throw new IllegalArgumentException("유효하지 않은 GeoJSON: 'type' 필드가 없습니다.");
    }

    String type = geoJsonNode.get("type").asText();
    if (!"FeatureCollection".equals(type) && !"Feature".equals(type) && !"Geometry".equals(type)) {
      throw new IllegalArgumentException("지원하지 않는 GeoJSON type: " + type);
    }
  }

  /** MapSheetLearnDataEntity 생성 */
  private MapSheetLearnDataEntity createMapSheetLearnDataEntity(
      String fileName, String geoJsonContent, String archiveFileName, JsonNode geoJsonNode) {

    MapSheetLearnDataEntity entity = new MapSheetLearnDataEntity();

    // 기본 정보 설정
    entity.setDataName(fileName);
    entity.setDataPath(generateDataPath(archiveFileName, fileName));
    entity.setDataType("GeoJSON");
    entity.setDataTitle(extractTitle(fileName, geoJsonNode));

    // CRS 정보 추출 및 설정
    setCrsInformation(entity, geoJsonNode);

    // JSON 데이터 저장
    try {
      @SuppressWarnings("unchecked")
      Map<String, Object> jsonMap = objectMapper.readValue(geoJsonContent, Map.class);
      entity.setDataJson(jsonMap);
    } catch (Exception e) {
      log.warn("JSON 파싱 실패, 원본 텍스트로 저장: {}", fileName, e);
      // JSON 파싱이 실패하면 원본을 Map 형태로 저장
      Map<String, Object> fallbackMap = new HashMap<>();
      fallbackMap.put("raw_content", geoJsonContent);
      fallbackMap.put("parse_error", e.getMessage());
      entity.setDataJson(fallbackMap);
    }

    // 연도 정보 추출 (파일명에서 추출 시도)
    setYearInformation(entity, fileName);

    // 상태 정보 설정
    entity.setDataState("PROCESSED");
    entity.setAnalState("PENDING");

    // 시간 정보 설정
    ZonedDateTime now = ZonedDateTime.now();
    entity.setCreatedDttm(now);
    entity.setUpdatedDttm(now);
    entity.setDataStateDttm(now);

    return entity;
  }

  /** CRS 정보 설정 */
  private void setCrsInformation(MapSheetLearnDataEntity entity, JsonNode geoJsonNode) {
    if (geoJsonNode.has("crs")) {
      JsonNode crsNode = geoJsonNode.get("crs");
      if (crsNode.has("type") && crsNode.has("properties")) {
        String crsType = crsNode.get("type").asText();
        entity.setDataCrsType(crsType);

        JsonNode propertiesNode = crsNode.get("properties");
        if (propertiesNode.has("name")) {
          String crsName = propertiesNode.get("name").asText();
          entity.setDataCrsTypeName(crsName);
        }
      }
    } else {
      // CRS가 명시되지 않은 경우 기본값 설정 (WGS84)
      entity.setDataCrsType("EPSG");
      entity.setDataCrsTypeName("EPSG:4326");
    }
  }

  /** 연도 정보 추출 */
  private void setYearInformation(MapSheetLearnDataEntity entity, String fileName) {
    // 학습 모델 결과 파일인지 확인하고 특별 처리
    if (fileName.matches(".*캠코_\\d{4}_\\d{4}_\\d+.*")) {
      String[] parts = fileName.split("_");
      if (parts.length >= 4) {
        String beforeYear = parts[1];
        String afterYear = parts[2];
        // 비교년도 정보를 첫 번째 연도의 정수로 저장
        try {
          entity.setCompareYyyy(Integer.parseInt(beforeYear));
          log.debug("학습 모델 연도 정보 설정: {}", beforeYear);
        } catch (NumberFormatException e) {
          log.warn("연도 파싱 실패: {}", beforeYear, e);
        }
        return;
      }
    }

    // 기존 로직: 파일명에서 연도 추출 시도
    String[] parts = fileName.split("_");
    for (String part : parts) {
      if (part.matches("\\d{4}")) { // 4자리 숫자 (연도)
        try {
          Integer year = Integer.parseInt(part);
          if (year >= 1900 && year <= 2100) {
            if (entity.getDataYyyy() == null) {
              entity.setDataYyyy(year);
            } else {
              entity.setCompareYyyy(year);
              break;
            }
          }
        } catch (NumberFormatException ignored) {
          // 무시
        }
      }
    }
  }

  /** 제목 추출 */
  private String extractTitle(String fileName, JsonNode geoJsonNode) {
    // GeoJSON 메타데이터에서 제목 추출 시도
    if (geoJsonNode.has("properties")) {
      JsonNode properties = geoJsonNode.get("properties");
      if (properties.has("title")) {
        return properties.get("title").asText();
      }
      if (properties.has("name")) {
        return properties.get("name").asText();
      }
    }

    // 파일명에서 확장자 제거하여 제목으로 사용
    int lastDotIndex = fileName.lastIndexOf('.');
    if (lastDotIndex > 0) {
      return fileName.substring(0, lastDotIndex);
    }

    return fileName;
  }

  /** 데이터 경로 생성 */
  private String generateDataPath(String archiveFileName, String fileName) {
    return archiveFileName + "/" + fileName;
  }

  /** 처리 가능한 파일 개수 확인 */
  public boolean isProcessable(Map<String, String> geoJsonContents) {
    if (geoJsonContents == null || geoJsonContents.isEmpty()) {
      return false;
    }

    // 최대 처리 가능한 파일 수 제한 (성능 고려)
    int maxFiles = 50;
    if (geoJsonContents.size() > maxFiles) {
      log.warn("처리 가능한 최대 파일 수를 초과했습니다: {} > {}", geoJsonContents.size(), maxFiles);
      return false;
    }

    return true;
  }

  /** 학습 모델 결과 파일인지 확인 */
  private boolean isLearningModelResult(String fileName, String geoJsonContent) {
    try {
      // 파일명으로 확인 (캠코_YYYY_YYYY_번호 패턴)
      if (fileName.matches(".*캠코_\\d{4}_\\d{4}_\\d+.*\\.geojson")) {
        return true;
      }

      // GeoJSON 내용으로 확인 (학습 모델 특화 필드 존재 여부)
      JsonNode rootNode = objectMapper.readTree(geoJsonContent);
      if (rootNode.has("features")) {
        JsonNode features = rootNode.get("features");
        if (features.isArray() && features.size() > 0) {
          JsonNode firstFeature = features.get(0);
          if (firstFeature.has("properties")) {
            JsonNode properties = firstFeature.get("properties");
            // 학습 모델 특화 필드 확인
            return properties.has("cd_prob")
                || properties.has("class")
                || (properties.has("before") && properties.has("after"));
          }
        }
      }
    } catch (Exception e) {
      log.debug("학습 모델 결과 파일 확인 중 오류: {}", fileName, e);
    }

    return false;
  }

  /** 학습 모델 결과의 geometry 데이터 처리 - 최적화된 배치 처리 */
  public void processLearningModelGeometryOptimized(
      Long dataUid, String geoJsonContent, String fileName) {
    try {
      log.info("학습 모델 geometry 데이터 처리 시작: {} (dataUid: {})", fileName, dataUid);

      JsonNode rootNode = objectMapper.readTree(geoJsonContent);

      // 메타데이터 추출
      String mapSheetName =
          rootNode.has("name") ? rootNode.get("name").asText() : fileName.replace(".geojson", "");

      // 파일명에서 연도 및 지도번호 추출 (캠코_2021_2022_35813023)
      String[] parts = mapSheetName.split("_");
      String beforeYear = null, afterYear = null, mapSheetNum = null;

      if (parts.length >= 4) {
        beforeYear = parts[1];
        afterYear = parts[2];
        mapSheetNum = parts[3];
      }

      if (beforeYear == null || afterYear == null) {
        log.warn("연도 정보를 추출할 수 없습니다: {}", fileName);
        return;
      }

      JsonNode features = rootNode.get("features");
      if (features == null || !features.isArray()) {
        log.warn("features 배열이 없습니다: {}", fileName);
        return;
      }

      // 소규모 배치로 나누어 처리
      int totalFeatures = features.size();
      int batchSize = 10; // 작은 배치 크기
      int processedCount = 0;

      log.info("총 {}개 feature를 {}개씩 배치로 나누어 처리", totalFeatures, batchSize);

      for (int i = 0; i < totalFeatures; i += batchSize) {
        int endIndex = Math.min(i + batchSize, totalFeatures);
        List<MapSheetLearnDataGeomEntity> batch = new ArrayList<>();

        for (int j = i; j < endIndex; j++) {
          try {
            JsonNode feature = features.get(j);
            MapSheetLearnDataGeomEntity geomEntity =
                createGeometryEntity(feature, dataUid, beforeYear, afterYear, mapSheetNum);
            if (geomEntity != null) {
              batch.add(geomEntity);
            }
          } catch (Exception e) {
            log.warn("Feature geometry 처리 실패 (feature {}): {}", j + 1, e.getMessage());
          }
        }

        // 배치별 저장
        if (!batch.isEmpty()) {
          saveBatchGeometry(batch);
          processedCount += batch.size();
          log.debug("배치 {}-{} 처리 완료 ({}개)", i + 1, endIndex, batch.size());
        }

        // 메모리 정리
        batch.clear();
      }

      log.info("학습 모델 geometry 데이터 저장 완료: {} ({}개 feature)", fileName, processedCount);

    } catch (Exception e) {
      log.error("학습 모델 geometry 데이터 처리 실패: {}", fileName, e);
      throw new RuntimeException("Geometry 처리 실패: " + fileName, e);
    }
  }

  /** 배치별 geometry 저장 - 별도 트랜잭션 */
  @Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
  public void saveBatchGeometry(List<MapSheetLearnDataGeomEntity> batch) {
    try {
      if (batch == null || batch.isEmpty()) {
        return;
      }
      mapSheetLearnDataGeomRepository.saveAll(batch);
    } catch (Exception e) {
      log.error("배치 geometry 저장 실패: {}", e.getMessage());
      // 개별 저장 시도
      for (MapSheetLearnDataGeomEntity entity : batch) {
        try {
          if (entity != null) {
            mapSheetLearnDataGeomRepository.save(entity);
          }
        } catch (Exception individualError) {
          log.warn("개별 geometry 저장 실패: {}", individualError.getMessage());
        }
      }
    }
  }

  /** 개별 feature에서 geometry entity 생성 */
  private MapSheetLearnDataGeomEntity createGeometryEntity(
      JsonNode feature, Long dataUid, String beforeYear, String afterYear, String mapSheetNum) {

    JsonNode properties = feature.get("properties");
    JsonNode geometry = feature.get("geometry");

    if (properties == null || geometry == null) {
      return null;
    }

    MapSheetLearnDataGeomEntity entity = new MapSheetLearnDataGeomEntity();

    // 기본 정보
    entity.setDataUid(dataUid);
    entity.setBeforeYyyy(Integer.parseInt(beforeYear));
    entity.setAfterYyyy(Integer.parseInt(afterYear));
    if (mapSheetNum != null) {
      try {
        entity.setMapSheetNum(Long.parseLong(mapSheetNum));
      } catch (NumberFormatException e) {
        log.warn("지도번호 파싱 실패: {}", mapSheetNum, e);
      }
    }

    // 변화 탐지 확률
    if (properties.has("cd_prob")) {
      entity.setCdProb(properties.get("cd_prob").asDouble());
    }

    // 면적 정보
    if (properties.has("area")) {
      entity.setArea(properties.get("area").asDouble());
    }

    // 분류 정보 처리
    if (properties.has("class")) {
      JsonNode classNode = properties.get("class");

      // before 분류
      if (classNode.has("before") && classNode.get("before").isArray()) {
        JsonNode beforeArray = classNode.get("before");
        if (beforeArray.size() > 0) {
          JsonNode firstBefore = beforeArray.get(0);
          if (firstBefore.has("class_name")) {
            entity.setClassBeforeName(firstBefore.get("class_name").asText());
          }
          if (firstBefore.has("probability")) {
            entity.setClassBeforeProb(firstBefore.get("probability").asDouble());
          }
        }
      }

      // after 분류
      if (classNode.has("after") && classNode.get("after").isArray()) {
        JsonNode afterArray = classNode.get("after");
        if (afterArray.size() > 0) {
          JsonNode firstAfter = afterArray.get(0);
          if (firstAfter.has("class_name")) {
            entity.setClassAfterName(firstAfter.get("class_name").asText());
          }
          if (firstAfter.has("probability")) {
            entity.setClassAfterProb(firstAfter.get("probability").asDouble());
          }
        }
      }
    }

    // geometry 변환
    try {
      Geometry geom = geoJsonReader.read(geometry.toString());
      if (geom != null) {
        geom.setSRID(5186); // EPSG:5186
        entity.setGeom(geom);
      }
    } catch (Exception e) {
      log.warn("Geometry 파싱 실패: {}", e.getMessage());
      return null;
    }

    return entity;
  }
}
