package com.kamco.cd.training.dataset.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetObjRegDto;
import com.kamco.cd.training.postgres.core.DatasetCoreService;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Log4j2
@RequiredArgsConstructor
public class DatasetBatchService {

  private final DatasetCoreService datasetCoreService;
  private final ObjectMapper mapper;

  /**
   * 배치 단위 데이터 저장
   *
   * <p>- 전달받은 데이터 목록을 순회하며 개별 insert 처리 - batch 단위로 트랜잭션 관리
   */
  @Transactional
  public void saveBatch(List<Map<String, Object>> batch, Long datasetUid, String type) {
    for (Map<String, Object> map : batch) {
      try {
        insertTrainTestData(map, datasetUid, type);
      } catch (Exception e) {
        log.error("파일 단위 실패. skip. file={}", batch, e);
        continue;
      }
    }
  }

  /**
   * 단일 데이터 처리 및 insert DTO 생성
   *
   * <p>처리 흐름: 1) 경로/JSON 데이터 추출 2) 파일명에서 연도 및 도엽번호 파싱 3) label JSON → feature 단위 분리 4) feature별 DTO
   * 생성 후 DB insert
   */
  private void insertTrainTestData(Map<String, Object> map, Long datasetUid, String subDir) {

    String comparePath = (String) map.get("input1");
    String targetPath = (String) map.get("input2");
    String labelPath = (String) map.get("label");
    String geojsonPath = (String) map.get("geojson_path");
    Object labelJson = map.get("label-json");

    // JSON 파싱
    JsonNode json;
    try {
      json = parseJson(labelJson);
      if (json == null) {
        log.warn("json null. skip. file={}", labelJson);
        return;
      }
    } catch (Exception e) {
      // 실패하면 skip, 다음 진행
      log.error("GeoJSON 파싱 실패. skip. file={}", geojsonPath, e);
      return;
    }

    // 파일명 파싱
    String fileName = Paths.get(comparePath).getFileName().toString();
    String[] fileNameStr = fileName.split("_");

    if (fileNameStr.length < 4) {
      log.error("파일명 파싱 실패: {}", fileName);
      return;
      //      throw new IllegalArgumentException("잘못된 파일명 형식: " + fileName);
    }

    int compareYyyy = 0;
    int targetYyyy = 0;
    try {
      compareYyyy = parseInt(fileNameStr[1], "compareYyyy", fileName);
      targetYyyy = parseInt(fileNameStr[2], "targetYyyy", fileName);
    } catch (Exception e) {
      log.error("기준년도 파싱 실패: {}", fileName);
      return;
    }

    String mapSheetNum = fileNameStr[3];

    // JSON 유효성 체크
    JsonNode featuresNode = json.path("features");

    // 2. 비어있는지 확인
    if (featuresNode.isEmpty()) {
      log.warn("features empty. skip. file={}", geojsonPath);
      return;
    }

    if (!featuresNode.isArray()) {
      log.warn("features array 아님. skip. file={}", geojsonPath);
      return; // skip
    }

    if (featuresNode.isMissingNode() || !featuresNode.isArray() || featuresNode.isEmpty()) {
      return; // skip
    }

    ObjectNode base = mapper.createObjectNode();
    base.put("type", "FeatureCollection");

    for (JsonNode feature : featuresNode) {

      try {
        JsonNode prop = feature.path("properties");

        String compareClassCd = prop.path("before").asText(null);
        String targetClassCd = prop.path("after").asText(null);

        // null 방어
        if (compareClassCd == null || targetClassCd == null) {
          log.warn("class 값 없음. skip. file={}", fileName);
          continue;
        }

        ArrayNode arr = mapper.createArrayNode();
        arr.add(feature);

        ObjectNode root = base.deepCopy();
        root.set("features", arr);

        DatasetObjRegDto objRegDto =
            DatasetObjRegDto.builder()
                .datasetUid(datasetUid)
                .compareYyyy(compareYyyy)
                .compareClassCd(compareClassCd)
                .targetYyyy(targetYyyy)
                .targetClassCd(targetClassCd)
                .comparePath(comparePath)
                .targetPath(targetPath)
                .labelPath(labelPath)
                .mapSheetNum(mapSheetNum)
                .geojson(root)
                .geojsonPath(geojsonPath)
                .fileName(fileName)
                .build();

        // 데이터 타입별 insert
        insertByType(subDir, objRegDto);
      } catch (Exception e) {
        // 개별 feature skip
        log.error("feature 처리 실패. skip. file={}", fileName, e);
      }
    }
  }

  /** 데이터 타입별 insert 처리 - type 값에 따라 대상 테이블 분기 - 잘못된 타입 입력 시 예외 발생 */
  private void insertByType(String type, DatasetObjRegDto dto) {

    switch (type) {
      case "train" -> datasetCoreService.insertDatasetObj(dto);
      case "val" -> datasetCoreService.insertDatasetValObj(dto);
      case "test" -> datasetCoreService.insertDatasetTestObj(dto);
      default -> throw new IllegalArgumentException("잘못된 타입: " + type);
    }
  }

  /**
   * label_json → JsonNode 변환
   *
   * <p>- JsonNode면 그대로 사용 - 문자열이면 파싱 수행 - 실패 시 로그 후 예외 발생
   */
  private JsonNode parseJson(Object labelJson) {
    try {
      if (labelJson instanceof JsonNode jn) {
        return jn;
      }
      return mapper.readTree(labelJson.toString());
    } catch (Exception e) {
      log.error("label_json parse error: {}", labelJson, e);
      return null;
    }
  }

  /**
   * 문자열 → 정수 변환
   *
   * <p>- 파싱 실패 시 어떤 필드/파일에서 발생했는지 로그 기록 - 잘못된 데이터는 즉시 예외 처리
   */
  private int parseInt(String value, String field, String fileName) {
    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException e) {
      log.error("{} 파싱 실패. fileName={}, value={}", field, fileName, value);
      throw new IllegalArgumentException(field + " 파싱 실패: " + fileName);
    }
  }
}
