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.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/** Geometry 데이터 변환 서비스 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeometryConversionService {

  private final MapSheetLearnDataRepository mapSheetLearnDataRepository;
  private final MapSheetLearnDataGeomRepository mapSheetLearnDataGeomRepository;
  private final ObjectMapper objectMapper;
  private final GeometryFactory geometryFactory = new GeometryFactory();

  /** MapSheetLearnData의 JSON 데이터를 기반으로 Geometry 테이블에 저장 */
  @Transactional
  public List<Long> convertToGeometryData(List<Long> learnDataIds) {
    List<Long> processedIds = new ArrayList<>();

    log.info("Geometry 변환 시작: {} 개의 학습 데이터", learnDataIds.size());

    for (Long dataId : learnDataIds) {
      try {
        if (dataId != null) {
          Optional<MapSheetLearnDataEntity> learnDataOpt =
              mapSheetLearnDataRepository.findById(dataId);
          if (learnDataOpt.isPresent()) {
            List<Long> geometryIds = processLearnDataToGeometry(learnDataOpt.get());
            processedIds.addAll(geometryIds);
            log.debug("학습 데이터 {} 에서 {} 개의 geometry 데이터 생성", dataId, geometryIds.size());
          } else {
            log.warn("학습 데이터를 찾을 수 없습니다: {}", dataId);
          }
        }
      } catch (Exception e) {
        log.error("Geometry 변환 실패 - 학습 데이터 ID: {}", dataId, e);
        // 개별 변환 실패는 전체 처리를 중단시키지 않음
      }
    }

    log.info(
        "Geometry 변환 완료: {} 개 처리, {} 개의 geometry 생성", learnDataIds.size(), processedIds.size());
    return processedIds;
  }

  /** 개별 학습 데이터를 Geometry 데이터로 변환 */
  private List<Long> processLearnDataToGeometry(MapSheetLearnDataEntity learnData) {
    List<Long> geometryIds = new ArrayList<>();

    try {
      // 기존 geometry 데이터 삭제 (재생성)
      mapSheetLearnDataGeomRepository.deleteByDataUid(learnData.getId());

      // JSON 데이터에서 GeoJSON 추출
      Map<String, Object> dataJson = learnData.getDataJson();
      if (dataJson == null || dataJson.isEmpty()) {
        log.warn("JSON 데이터가 없습니다: {}", learnData.getId());
        return geometryIds;
      }

      // JSON을 GeoJSON으로 파싱
      String geoJsonString = objectMapper.writeValueAsString(dataJson);
      JsonNode geoJsonNode = objectMapper.readTree(geoJsonString);

      // GeoJSON 타입에 따라 처리
      String type = geoJsonNode.get("type").asText();
      switch (type) {
        case "FeatureCollection":
          geometryIds.addAll(processFeatureCollection(geoJsonNode, learnData));
          break;
        case "Feature":
          Long geometryId = processFeature(geoJsonNode, learnData);
          if (geometryId != null) {
            geometryIds.add(geometryId);
          }
          break;
        case "Point":
        case "LineString":
        case "Polygon":
        case "MultiPoint":
        case "MultiLineString":
        case "MultiPolygon":
          Long directGeometryId = processDirectGeometry(geoJsonNode, learnData);
          if (directGeometryId != null) {
            geometryIds.add(directGeometryId);
          }
          break;
        default:
          log.warn("지원하지 않는 GeoJSON type: {} (데이터 ID: {})", type, learnData.getId());
      }

    } catch (Exception e) {
      log.error("Geometry 변환 실패: 학습 데이터 ID {}", learnData.getId(), e);
      throw new RuntimeException("Geometry 변환 실패", e);
    }

    return geometryIds;
  }

  /** FeatureCollection 처리 */
  private List<Long> processFeatureCollection(
      JsonNode featureCollectionNode, MapSheetLearnDataEntity learnData) {
    List<Long> geometryIds = new ArrayList<>();

    if (!featureCollectionNode.has("features")) {
      log.warn("FeatureCollection에 features 배열이 없습니다: {}", learnData.getId());
      return geometryIds;
    }

    JsonNode featuresNode = featureCollectionNode.get("features");
    if (featuresNode.isArray()) {
      for (JsonNode featureNode : featuresNode) {
        try {
          Long geometryId = processFeature(featureNode, learnData);
          if (geometryId != null) {
            geometryIds.add(geometryId);
          }
        } catch (Exception e) {
          log.error("Feature 처리 실패 (학습 데이터 ID: {})", learnData.getId(), e);
        }
      }
    }

    return geometryIds;
  }

  /** Feature 처리 */
  private Long processFeature(JsonNode featureNode, MapSheetLearnDataEntity learnData) {
    try {
      if (!featureNode.has("geometry")) {
        log.warn("Feature에 geometry가 없습니다: {}", learnData.getId());
        return null;
      }

      JsonNode geometryNode = featureNode.get("geometry");
      JsonNode propertiesNode =
          featureNode.has("properties") ? featureNode.get("properties") : null;

      return createGeometryEntity(geometryNode, propertiesNode, learnData);

    } catch (Exception e) {
      log.error("Feature 처리 중 오류 (학습 데이터 ID: {})", learnData.getId(), e);
      return null;
    }
  }

  /** 직접 Geometry 처리 */
  private Long processDirectGeometry(JsonNode geometryNode, MapSheetLearnDataEntity learnData) {
    return createGeometryEntity(geometryNode, null, learnData);
  }

  /** GeometryEntity 생성 및 저장 */
  private Long createGeometryEntity(
      JsonNode geometryNode, JsonNode propertiesNode, MapSheetLearnDataEntity learnData) {
    try {
      MapSheetLearnDataGeomEntity geometryEntity = new MapSheetLearnDataGeomEntity();

      // 기본 정보 설정
      geometryEntity.setDataUid(learnData.getId());
      geometryEntity.setBeforeYyyy(learnData.getDataYyyy());
      geometryEntity.setAfterYyyy(learnData.getCompareYyyy());

      // Geometry 변환 및 설정
      Geometry geometry = parseGeometryFromGeoJson(geometryNode);
      if (geometry != null) {
        geometryEntity.setGeom(geometry);
        geometryEntity.setGeoType(geometry.getGeometryType());

        // 면적 계산 (Polygon인 경우)
        if (geometry instanceof Polygon || geometry.getGeometryType().contains("Polygon")) {
          double area = geometry.getArea();
          geometryEntity.setArea(area);
        }
      } else {
        log.warn("Geometry 변환 실패: {}", geometryNode);
        return null;
      }

      // Properties에서 추가 정보 추출
      if (propertiesNode != null) {
        extractPropertiesData(geometryEntity, propertiesNode, learnData);
      }

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

      // 저장
      MapSheetLearnDataGeomEntity savedEntity =
          mapSheetLearnDataGeomRepository.save(geometryEntity);
      return savedEntity.getId();

    } catch (Exception e) {
      log.error("GeometryEntity 생성 실패 (학습 데이터 ID: {})", learnData.getId(), e);
      return null;
    }
  }

  /** GeoJSON 노드에서 JTS Geometry 객체 생성 */
  private Geometry parseGeometryFromGeoJson(JsonNode geometryNode) {
    try {
      if (!geometryNode.has("type") || !geometryNode.has("coordinates")) {
        log.warn("유효하지 않은 Geometry 형식: type 또는 coordinates가 없습니다.");
        return null;
      }

      String geometryType = geometryNode.get("type").asText();
      JsonNode coordinatesNode = geometryNode.get("coordinates");

      switch (geometryType.toLowerCase()) {
        case "point":
          return createPoint(coordinatesNode);
        case "linestring":
          return createLineString(coordinatesNode);
        case "polygon":
          return createPolygon(coordinatesNode);
        case "multipoint":
          return createMultiPoint(coordinatesNode);
        case "multilinestring":
          return createMultiLineString(coordinatesNode);
        case "multipolygon":
          return createMultiPolygon(coordinatesNode);
        default:
          log.warn("지원하지 않는 Geometry 타입: {}", geometryType);
          return null;
      }

    } catch (Exception e) {
      log.error("Geometry 파싱 실패", e);
      return null;
    }
  }

  private Point createPoint(JsonNode coordinatesNode) {
    if (coordinatesNode.size() < 2) return null;
    double x = coordinatesNode.get(0).asDouble();
    double y = coordinatesNode.get(1).asDouble();
    return geometryFactory.createPoint(new Coordinate(x, y));
  }

  private LineString createLineString(JsonNode coordinatesNode) {
    List<Coordinate> coords = new ArrayList<>();
    for (JsonNode coordNode : coordinatesNode) {
      if (coordNode.size() >= 2) {
        coords.add(new Coordinate(coordNode.get(0).asDouble(), coordNode.get(1).asDouble()));
      }
    }
    return geometryFactory.createLineString(coords.toArray(new Coordinate[0]));
  }

  private Polygon createPolygon(JsonNode coordinatesNode) {
    if (coordinatesNode.size() == 0) return null;

    // Exterior ring
    JsonNode exteriorRing = coordinatesNode.get(0);
    List<Coordinate> coords = new ArrayList<>();
    for (JsonNode coordNode : exteriorRing) {
      if (coordNode.size() >= 2) {
        coords.add(new Coordinate(coordNode.get(0).asDouble(), coordNode.get(1).asDouble()));
      }
    }

    if (coords.size() < 3) return null;

    // Close ring if not already closed
    if (!coords.get(0).equals2D(coords.get(coords.size() - 1))) {
      coords.add(new Coordinate(coords.get(0)));
    }

    LinearRing shell = geometryFactory.createLinearRing(coords.toArray(new Coordinate[0]));

    // Interior rings (holes)
    LinearRing[] holes = new LinearRing[coordinatesNode.size() - 1];
    for (int i = 1; i < coordinatesNode.size(); i++) {
      JsonNode holeRing = coordinatesNode.get(i);
      List<Coordinate> holeCoords = new ArrayList<>();
      for (JsonNode coordNode : holeRing) {
        if (coordNode.size() >= 2) {
          holeCoords.add(new Coordinate(coordNode.get(0).asDouble(), coordNode.get(1).asDouble()));
        }
      }
      if (holeCoords.size() >= 3) {
        if (!holeCoords.get(0).equals2D(holeCoords.get(holeCoords.size() - 1))) {
          holeCoords.add(new Coordinate(holeCoords.get(0)));
        }
        holes[i - 1] = geometryFactory.createLinearRing(holeCoords.toArray(new Coordinate[0]));
      }
    }

    return geometryFactory.createPolygon(shell, holes);
  }

  private MultiPoint createMultiPoint(JsonNode coordinatesNode) {
    List<Point> points = new ArrayList<>();
    for (JsonNode pointNode : coordinatesNode) {
      Point point = createPoint(pointNode);
      if (point != null) {
        points.add(point);
      }
    }
    return geometryFactory.createMultiPoint(points.toArray(new Point[0]));
  }

  private MultiLineString createMultiLineString(JsonNode coordinatesNode) {
    List<LineString> lineStrings = new ArrayList<>();
    for (JsonNode lineNode : coordinatesNode) {
      LineString line = createLineString(lineNode);
      if (line != null) {
        lineStrings.add(line);
      }
    }
    return geometryFactory.createMultiLineString(lineStrings.toArray(new LineString[0]));
  }

  private MultiPolygon createMultiPolygon(JsonNode coordinatesNode) {
    List<Polygon> polygons = new ArrayList<>();
    for (JsonNode polygonNode : coordinatesNode) {
      Polygon polygon = createPolygon(polygonNode);
      if (polygon != null) {
        polygons.add(polygon);
      }
    }
    return geometryFactory.createMultiPolygon(polygons.toArray(new Polygon[0]));
  }

  /** Properties에서 추가 정보 추출 */
  private void extractPropertiesData(
      MapSheetLearnDataGeomEntity geometryEntity,
      JsonNode propertiesNode,
      MapSheetLearnDataEntity learnData) {
    // CD 정확도 정보
    if (propertiesNode.has("cd_prob")) {
      try {
        double cdProb = propertiesNode.get("cd_prob").asDouble();
        geometryEntity.setCdProb(cdProb);
      } catch (Exception e) {
        log.debug("cd_prob 파싱 실패", e);
      }
    }

    // Before class 정보
    if (propertiesNode.has("class_before_name")) {
      geometryEntity.setClassBeforeName(propertiesNode.get("class_before_name").asText());
    }
    if (propertiesNode.has("class_before_prob")) {
      try {
        double beforeProb = propertiesNode.get("class_before_prob").asDouble();
        geometryEntity.setClassBeforeProb(beforeProb);
      } catch (Exception e) {
        log.debug("class_before_prob 파싱 실패", e);
      }
    }

    // After class 정보
    if (propertiesNode.has("class_after_name")) {
      geometryEntity.setClassAfterName(propertiesNode.get("class_after_name").asText());
    }
    if (propertiesNode.has("class_after_prob")) {
      try {
        double afterProb = propertiesNode.get("class_after_prob").asDouble();
        geometryEntity.setClassAfterProb(afterProb);
      } catch (Exception e) {
        log.debug("class_after_prob 파싱 실패", e);
      }
    }

    // 도엽 번호
    if (propertiesNode.has("map_sheet_num")) {
      try {
        long mapSheetNum = propertiesNode.get("map_sheet_num").asLong();
        geometryEntity.setMapSheetNum(mapSheetNum);
      } catch (Exception e) {
        log.debug("map_sheet_num 파싱 실패", e);
      }
    }

    // 면적 (properties에서 제공되는 경우)
    if (propertiesNode.has("area")) {
      try {
        double area = propertiesNode.get("area").asDouble();
        geometryEntity.setArea(area);
      } catch (Exception e) {
        log.debug("area 파싱 실패", e);
      }
    }
  }

  /** 미처리된 학습 데이터들을 찾아서 자동으로 geometry 변환 수행 */
  @Transactional
  public List<Long> processUnprocessedLearnData() {
    // 분석 상태가 PENDING인 학습 데이터 조회
    List<MapSheetLearnDataEntity> unprocessedData =
        mapSheetLearnDataRepository.findByAnalState("PENDING");

    if (unprocessedData.isEmpty()) {
      log.debug("처리할 미완료 학습 데이터가 없습니다.");
      return new ArrayList<>();
    }

    log.info("미처리 학습 데이터 {}개에 대해 geometry 변환을 수행합니다.", unprocessedData.size());

    List<Long> processedIds = new ArrayList<>();
    for (MapSheetLearnDataEntity data : unprocessedData) {
      try {
        List<Long> geometryIds = processLearnDataToGeometry(data);
        processedIds.addAll(geometryIds);

        // 처리 완료 상태로 업데이트
        data.setAnalState("COMPLETED");
        mapSheetLearnDataRepository.save(data);

      } catch (Exception e) {
        log.error("미처리 학습 데이터 처리 실패: {}", data.getId(), e);
        // 실패한 경우 ERROR 상태로 설정
        data.setAnalState("ERROR");
        mapSheetLearnDataRepository.save(data);
      }
    }

    return processedIds;
  }
}
