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

import static com.kamco.cd.training.postgres.entity.QModelHyperParamEntity.modelHyperParamEntity;

import com.kamco.cd.training.common.enums.ModelType;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.HyperType;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.SearchReq;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class HyperParamRepositoryImpl implements HyperParamRepositoryCustom {

  private final JPAQueryFactory queryFactory;

  // TODO 미사용시작
  @Override
  public Optional<ModelHyperParamEntity> findHyperParamVer() {

    return Optional.ofNullable(
        queryFactory
            .select(modelHyperParamEntity)
            .from(modelHyperParamEntity)
            .where(modelHyperParamEntity.delYn.isFalse())
            .orderBy(modelHyperParamEntity.hyperVer.desc())
            .limit(1)
            .fetchOne());
  }

  // TODO 미사용 끝

  @Override
  public Optional<ModelHyperParamEntity> findHyperParamVerByModelType(ModelType modelType) {

    return Optional.ofNullable(
        queryFactory
            .select(modelHyperParamEntity)
            .from(modelHyperParamEntity)
            .where(
                modelHyperParamEntity
                    .delYn
                    .isFalse()
                    .and(modelHyperParamEntity.modelType.eq(modelType)))
            .orderBy(modelHyperParamEntity.hyperVer.desc())
            .limit(1)
            .fetchOne());
  }

  // TODO 미사용시작
  @Override
  public Optional<ModelHyperParamEntity> findHyperParamByHyperVer(String hyperVer) {

    return Optional.ofNullable(
        queryFactory
            .select(modelHyperParamEntity)
            .from(modelHyperParamEntity)
            .where(
                modelHyperParamEntity
                    .delYn
                    .isFalse()
                    .and(modelHyperParamEntity.hyperVer.eq(hyperVer)))
            .limit(1)
            .fetchOne());
  }

  // TODO 미사용 끝

  @Override
  public Optional<ModelHyperParamEntity> findHyperParamByUuid(UUID uuid) {
    return Optional.ofNullable(
        queryFactory
            .select(modelHyperParamEntity)
            .from(modelHyperParamEntity)
            .where(modelHyperParamEntity.uuid.eq(uuid))
            .fetchOne());
  }

  @Override
  public Page<HyperParamDto.List> findByHyperVerList(ModelType model, SearchReq req) {
    Pageable pageable = req.toPageable();

    BooleanBuilder builder = new BooleanBuilder();

    builder.and(modelHyperParamEntity.delYn.isFalse());

    if (model != null) {
      builder.and(modelHyperParamEntity.modelType.eq(model));
    }

    if (req.getHyperVer() != null && !req.getHyperVer().isEmpty()) {
      // 버전
      builder.and(modelHyperParamEntity.hyperVer.contains(req.getHyperVer()));
    }

    if (req.getStartDate() != null && req.getEndDate() != null) {

      ZoneId zoneId = ZoneId.systemDefault();

      ZonedDateTime start = req.getStartDate().atStartOfDay(zoneId);

      ZonedDateTime end = req.getEndDate().atTime(23, 59, 59).atZone(zoneId);

      if (HyperType.CREATE_DATE.getId().equals(req.getType())) {
        // 생성일
        builder.and(modelHyperParamEntity.createdDttm.between(start, end));
      } else if (HyperType.LAST_USED_DATE.getId().equals(req.getType())) {
        // 최종 사용일
        builder.and(modelHyperParamEntity.lastUsedDttm.between(start, end));
      }
    }

    JPAQuery<HyperParamDto.List> query =
        queryFactory
            .select(
                Projections.constructor(
                    HyperParamDto.List.class,
                    modelHyperParamEntity.uuid,
                    modelHyperParamEntity.modelType.as("model"),
                    modelHyperParamEntity.hyperVer,
                    modelHyperParamEntity.createdDttm,
                    modelHyperParamEntity.lastUsedDttm,
                    modelHyperParamEntity.memo,
                    modelHyperParamEntity.totalUseCnt))
            .from(modelHyperParamEntity)
            .where(builder);

    Sort.Order sortOrder = pageable.getSort().stream().findFirst().orElse(null);

    if (sortOrder == null) {
      // 기본값
      query.orderBy(modelHyperParamEntity.createdDttm.desc());
    } else {
      String property = sortOrder.getProperty();
      boolean asc = sortOrder.isAscending();

      switch (property) {
        case "createdDttm" ->
            query.orderBy(
                asc
                    ? modelHyperParamEntity.createdDttm.asc()
                    : modelHyperParamEntity.createdDttm.desc());

        case "lastUsedDttm" ->
            query.orderBy(
                asc
                    ? modelHyperParamEntity.lastUsedDttm.asc()
                    : modelHyperParamEntity.lastUsedDttm.desc());
        case "totalUseCnt" ->
            query.orderBy(
                asc
                    ? modelHyperParamEntity.totalUseCnt.asc()
                    : modelHyperParamEntity.totalUseCnt.desc());

        default -> query.orderBy(modelHyperParamEntity.createdDttm.desc());
      }
    }

    List<HyperParamDto.List> content =
        query.offset(pageable.getOffset()).limit(pageable.getPageSize()).fetch();

    Long total =
        queryFactory
            .select(modelHyperParamEntity.count())
            .from(modelHyperParamEntity)
            .where(builder)
            .fetchOne();

    long totalCount = (total != null) ? total : 0L;

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

  @Override
  public List<ModelHyperParamEntity> getHyperParamByType(ModelType modelType) {
    return queryFactory
        .select(modelHyperParamEntity)
        .from(modelHyperParamEntity)
        .where(
            modelHyperParamEntity
                .delYn
                .isFalse()
                .and(modelHyperParamEntity.modelType.eq(modelType)))
        .fetch();
  }
}
