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

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

import com.kamco.cd.training.common.enums.ModelType;
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetReq;
import com.kamco.cd.training.dataset.dto.DatasetDto.SearchReq;
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectDataSet;
import com.kamco.cd.training.postgres.entity.DatasetEntity;
import com.kamco.cd.training.postgres.entity.QDatasetEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class DatasetRepositoryImpl implements DatasetRepositoryCustom {

  private final JPAQueryFactory queryFactory;
  private final QDatasetEntity dataset = QDatasetEntity.datasetEntity;

  /**
   * 데이터셋 목록 조회
   *
   * @param searchReq 검색 조건
   * @return 페이징 처리된 데이터셋 Entity 목록
   */
  @Override
  public Page<DatasetEntity> findDatasetList(SearchReq searchReq) {
    Pageable pageable = searchReq.toPageable();
    BooleanBuilder builder = new BooleanBuilder();

    // 제목
    if (StringUtils.isNotBlank(searchReq.getTitle())) {
      String contains = "%" + searchReq.getTitle() + "%";
      builder.and(dataset.title.likeIgnoreCase(contains));
    }

    // 구분
    if (StringUtils.isNotBlank(searchReq.getDataType())) {
      builder.and(dataset.dataType.eq(searchReq.getDataType()));
    }

    // Entity 직접 조회 (Projections 사용 지양)
    List<DatasetEntity> content =
        queryFactory
            .selectFrom(dataset)
            .where(builder.and(dataset.deleted.isFalse()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(dataset.createdDttm.desc())
            .fetch();

    // Count 쿼리 별도 실행 (null safe handling)
    long total =
        Optional.ofNullable(
                queryFactory.select(dataset.count()).from(dataset).where(builder).fetchOne())
            .orElse(0L);

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

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

  @Override
  public List<SelectDataSet> getDatasetSelectG1List(DatasetReq req) {

    BooleanBuilder builder = new BooleanBuilder();

    if (StringUtils.isNotBlank(req.getDataType()) && !"CURRENT".equals(req.getDataType())) {
      builder.and(dataset.dataType.eq(req.getDataType()));
    }

    if (StringUtils.isNotBlank(req.getDataType()) && !"CURRENT".equals(req.getDataType())) {
      builder.and(dataset.dataType.eq(req.getDataType()));
    }

    if (req.getIds() != null) {
      builder.and(dataset.id.in(req.getIds()));
    }

    return queryFactory
        .select(
            Projections.constructor(
                SelectDataSet.class,
                Expressions.constant(req.getModelNo()),
                dataset.id,
                dataset.uuid,
                dataset.dataType,
                dataset.title,
                dataset.roundNo,
                dataset.compareYyyy,
                dataset.targetYyyy,
                dataset.memo,
                new CaseBuilder()
                    .when(datasetObjEntity.targetClassCd.eq("building"))
                    .then(1)
                    .otherwise(0)
                    .sum(),
                new CaseBuilder()
                    .when(datasetObjEntity.targetClassCd.eq("container"))
                    .then(1)
                    .otherwise(0)
                    .sum()))
        .from(dataset)
        .leftJoin(datasetObjEntity)
        .on(dataset.id.eq(datasetObjEntity.datasetUid))
        .where(builder) // datasetObjEntity.targetClassCd.in("building", "container").and(
        .groupBy(
            dataset.id,
            dataset.uuid,
            dataset.dataType,
            dataset.title,
            dataset.roundNo,
            dataset.memo)
        .orderBy(dataset.createdDttm.desc())
        .fetch();
  }

  @Override
  public List<SelectDataSet> getDatasetSelectG2G3List(DatasetReq req) {

    BooleanBuilder builder = new BooleanBuilder();

    NumberExpression<Long> selectedCnt = null;
    NumberExpression<Long> wasteCnt =
        datasetObjEntity.targetClassCd.when("waste").then(1L).otherwise(0L).sum();

    NumberExpression<Long> elseCnt =
        new CaseBuilder()
            .when(datasetObjEntity.targetClassCd.notIn("building", "container", "waste"))
            .then(1L)
            .otherwise(0L)
            .sum();

    if (StringUtils.isNotBlank(req.getModelNo())) {
      if (req.getModelNo().equals(ModelType.G2.getId())) {
        selectedCnt = wasteCnt;
      } else {
        selectedCnt = elseCnt;
      }
    }

    if (StringUtils.isNotBlank(req.getDataType())) {
      if (!"CURRENT".equals(req.getDataType())) {
        builder.and(dataset.dataType.eq(req.getDataType()));
      }
    }

    if (req.getIds() != null) {
      builder.and(dataset.id.in(req.getIds()));
    }

    return queryFactory
        .select(
            Projections.constructor(
                SelectDataSet.class,
                Expressions.constant(req.getModelNo()),
                dataset.id,
                dataset.uuid,
                dataset.dataType,
                dataset.title,
                dataset.roundNo,
                dataset.compareYyyy,
                dataset.targetYyyy,
                dataset.memo,
                selectedCnt.as("cnt")))
        .from(dataset)
        .leftJoin(datasetObjEntity)
        .on(dataset.id.eq(datasetObjEntity.datasetUid))
        .where(builder)
        .groupBy(
            dataset.id,
            dataset.uuid,
            dataset.dataType,
            dataset.title,
            dataset.roundNo,
            dataset.memo)
        .orderBy(dataset.createdDttm.desc())
        .fetch();
  }

  @Override
  public Long getDatasetMaxStage(int compareYyyy, int targetYyyy) {
    return queryFactory
        .select(dataset.roundNo.max().coalesce(0L))
        .from(dataset)
        .where(dataset.compareYyyy.eq(compareYyyy), dataset.targetYyyy.eq(targetYyyy))
        .fetchOne();
  }

  @Override
  public Long insertDatasetMngData(DatasetMngRegDto mngRegDto) {
    queryFactory
        .insert(dataset)
        .columns(
            dataset.uid,
            dataset.dataType,
            dataset.compareYyyy,
            dataset.targetYyyy,
            dataset.roundNo,
            dataset.totalSize,
            dataset.title,
            dataset.memo,
            dataset.datasetPath)
        .values(
            mngRegDto.getUid(),
            mngRegDto.getDataType(),
            mngRegDto.getCompareYyyy(),
            mngRegDto.getTargetYyyy(),
            mngRegDto.getRoundNo(),
            mngRegDto.getTotalSize(),
            mngRegDto.getTitle(),
            mngRegDto.getMemo(),
            mngRegDto.getDatasetPath())
        .execute();

    return queryFactory
        .select(dataset.id)
        .from(dataset)
        .where(dataset.uid.eq(mngRegDto.getUid()))
        .fetchOne();
  }
}
