package com.kamco.cd.training.postgres.repository.dataset;

import static com.kamco.cd.training.postgres.entity.QDatasetEntity.datasetEntity;
import static com.kamco.cd.training.postgres.entity.QDatasetObjEntity.datasetObjEntity;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetClass;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.DatasetObjRegDto;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.SearchReq;
import com.kamco.cd.training.postgres.entity.DatasetEntity;
import com.kamco.cd.training.postgres.entity.DatasetObjEntity;
import com.kamco.cd.training.postgres.entity.QDatasetEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
@RequiredArgsConstructor
public class DatasetObjRepositoryImpl implements DatasetObjRepositoryCustom {

  private final JPAQueryFactory queryFactory;
  private final QDatasetEntity dataset = datasetEntity;
  private final ObjectMapper objectMapper = new ObjectMapper();

  @PersistenceContext EntityManager em;

  @Override
  public Optional<DatasetObjEntity> findByUuid(UUID id) {
    return Optional.ofNullable(
        queryFactory
            .select(datasetObjEntity)
            .from(datasetObjEntity)
            .where(datasetObjEntity.uuid.eq(id), datasetObjEntity.deleted.isFalse())
            .fetchOne());
  }

  @Override
  public void insertDatasetTestObj(DatasetObjRegDto objRegDto) {
    String json;
    Geometry geometry;
    String geometryJson;
    try {
      json = objectMapper.writeValueAsString(objRegDto.getGeojson());
      geometryJson =
          objectMapper.writeValueAsString(
              objRegDto.getGeojson().path("features").get(0).path("geometry"));
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }

    try {
      em.createNativeQuery(
              """
            insert into tb_dataset_test_obj
          (dataset_uid, target_yyyy, target_class_cd,
           compare_yyyy, compare_class_cd,
           target_path, compare_path, label_path, geo_jsonb, map_sheet_num, file_name, geom, geojson_path)
          values
          (?, ?, ?, ?, ?, ?, ?, ?, cast(? as jsonb), ?, ?, ST_SetSRID(ST_GeomFromGeoJSON(?), 5186), ?)
          """)
          .setParameter(1, objRegDto.getDatasetUid())
          .setParameter(2, objRegDto.getTargetYyyy())
          .setParameter(3, objRegDto.getTargetClassCd())
          .setParameter(4, objRegDto.getCompareYyyy())
          .setParameter(5, objRegDto.getCompareClassCd())
          .setParameter(6, objRegDto.getTargetPath())
          .setParameter(7, objRegDto.getComparePath())
          .setParameter(8, objRegDto.getLabelPath())
          .setParameter(9, json)
          .setParameter(10, objRegDto.getMapSheetNum())
          .setParameter(11, objRegDto.getFileName())
          .setParameter(12, geometryJson)
          .setParameter(13, objRegDto.getGeojsonPath())
          .executeUpdate();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void insertDatasetValObj(DatasetObjRegDto objRegDto) {
    String json;
    String geometryJson;
    try {
      json = objectMapper.writeValueAsString(objRegDto.getGeojson());
      geometryJson =
          objectMapper.writeValueAsString(
              objRegDto.getGeojson().path("features").get(0).path("geometry"));
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }

    try {
      em.createNativeQuery(
              """
        insert into tb_dataset_val_obj
      (dataset_uid, target_yyyy, target_class_cd,
       compare_yyyy, compare_class_cd,
       target_path, compare_path, label_path, geo_jsonb, map_sheet_num, file_name, geom, geojson_path)
      values
      (?, ?, ?, ?, ?, ?, ?, ?, cast(? as jsonb), ?, ?, ST_SetSRID(ST_GeomFromGeoJSON(?), 5186), ?)
      """)
          .setParameter(1, objRegDto.getDatasetUid())
          .setParameter(2, objRegDto.getTargetYyyy())
          .setParameter(3, objRegDto.getTargetClassCd())
          .setParameter(4, objRegDto.getCompareYyyy())
          .setParameter(5, objRegDto.getCompareClassCd())
          .setParameter(6, objRegDto.getTargetPath())
          .setParameter(7, objRegDto.getComparePath())
          .setParameter(8, objRegDto.getLabelPath())
          .setParameter(9, json)
          .setParameter(10, objRegDto.getMapSheetNum())
          .setParameter(11, objRegDto.getFileName())
          .setParameter(12, geometryJson)
          .setParameter(13, objRegDto.getGeojsonPath())
          .executeUpdate();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public Page<DatasetObjEntity> searchDatasetObjectList(SearchReq searchReq) {
    Pageable pageable = searchReq.toPageable();
    BooleanBuilder builder = new BooleanBuilder();

    if (StringUtils.isNotBlank(searchReq.getCompareClassCd())) {
      builder.and(datasetObjEntity.compareClassCd.eq(searchReq.getCompareClassCd()));
    }
    if (StringUtils.isNotBlank(searchReq.getTargetClassCd())) {
      builder.and(datasetObjEntity.targetClassCd.eq(searchReq.getTargetClassCd()));
    }
    if (StringUtils.isNotBlank(searchReq.getMapSheetNum())) {
      builder.and(datasetObjEntity.mapSheetNum.eq(searchReq.getMapSheetNum()));
    }

    DatasetEntity entity =
        queryFactory
            .selectFrom(datasetEntity)
            .where(datasetEntity.uuid.eq(searchReq.getUuid()))
            .fetchOne();

    if (Objects.isNull(entity)) {
      throw new EntityNotFoundException("DatasetEntity not found for uuid: " + searchReq.getUuid());
    }

    List<DatasetObjEntity> content =
        queryFactory
            .selectFrom(datasetObjEntity)
            .where(
                datasetObjEntity
                    .deleted
                    .isFalse()
                    .and(datasetObjEntity.datasetUid.eq(entity.getId()))
                    .and(builder))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    // Count 쿼리 별도 실행 (null safe handling)
    long total =
        Optional.ofNullable(
                queryFactory
                    .select(datasetObjEntity.count())
                    .from(datasetObjEntity)
                    .where(
                        datasetObjEntity
                            .deleted
                            .isFalse()
                            .and(datasetObjEntity.datasetUid.eq(entity.getId()))
                            .and(builder))
                    .fetchOne())
            .orElse(0L);

    return new PageImpl<>(content, pageable, total);
  }

  @Override
  public List<DatasetClass> findDatasetObjClassByUuid(UUID uuid, String type) {

    StringPath classCd =
        type.equals("compare") ? datasetObjEntity.compareClassCd : datasetObjEntity.targetClassCd;

    BooleanBuilder builder = new BooleanBuilder();
    builder.and(datasetEntity.uuid.eq(uuid));
    builder.and(datasetEntity.deleted.isFalse());
    builder.and(datasetObjEntity.deleted.isFalse());

    return queryFactory
        .select(Projections.constructor(DatasetClass.class, classCd))
        .from(datasetEntity)
        .join(datasetObjEntity)
        .on(datasetEntity.id.eq(datasetObjEntity.datasetUid))
        .where(builder)
        .groupBy(classCd)
        .fetch();
  }

  @Override
  public void insertDatasetObj(DatasetObjRegDto objRegDto) {
    String json;
    String geometryJson;
    try {
      json = objectMapper.writeValueAsString(objRegDto.getGeojson());
      geometryJson =
          objectMapper.writeValueAsString(
              objRegDto.getGeojson().path("features").get(0).path("geometry"));
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }

    try {
      em.createNativeQuery(
              """
            insert into tb_dataset_obj
          (dataset_uid, target_yyyy, target_class_cd,
           compare_yyyy, compare_class_cd,
           target_path, compare_path, label_path, geo_jsonb, map_sheet_num, file_name, geom, geojson_path)
          values
          (?, ?, ?, ?, ?, ?, ?, ?, cast(? as jsonb), ?, ?, ST_SetSRID(ST_GeomFromGeoJSON(?), 5186), ?)
          """)
          .setParameter(1, objRegDto.getDatasetUid())
          .setParameter(2, objRegDto.getTargetYyyy())
          .setParameter(3, objRegDto.getTargetClassCd())
          .setParameter(4, objRegDto.getCompareYyyy())
          .setParameter(5, objRegDto.getCompareClassCd())
          .setParameter(6, objRegDto.getTargetPath())
          .setParameter(7, objRegDto.getComparePath())
          .setParameter(8, objRegDto.getLabelPath())
          .setParameter(9, json)
          .setParameter(10, objRegDto.getMapSheetNum())
          .setParameter(11, objRegDto.getFileName())
          .setParameter(12, geometryJson)
          .setParameter(13, objRegDto.getGeojsonPath())
          .executeUpdate();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public String getFilePathByUUIDPathType(UUID uuid, String pathType) {
    Expression<String> pathExpr =
        switch (pathType) {
          case "compare" -> datasetObjEntity.comparePath;
          case "target" -> datasetObjEntity.targetPath;
          case "label" -> datasetObjEntity.labelPath;
          default -> Expressions.constant("");
        };

    return queryFactory
        .select(pathExpr)
        .from(datasetObjEntity)
        .where(datasetObjEntity.uuid.eq(uuid))
        .fetchOne();
  }

  @Override
  public int deleteAllDatasetObj(Long datasetUid) {
    int cnt = 0;
    cnt =
        em.createNativeQuery(
                """
        delete from tb_dataset_obj
        where dataset_uid = ?
    """)
            .setParameter(1, datasetUid)
            .executeUpdate();

    cnt +=
        em.createNativeQuery(
                """
        delete from tb_dataset_val_obj
        where dataset_uid = ?
    """)
            .setParameter(1, datasetUid)
            .executeUpdate();

    cnt +=
        em.createNativeQuery(
                """
        delete from tb_dataset_test_obj
        where dataset_uid = ?
    """)
            .setParameter(1, datasetUid)
            .executeUpdate();

    em.clear();
    return cnt;
  }
}
