package com.kamco.cd.kamcoback.postgres.repository.model;

import static com.kamco.cd.kamcoback.postgres.entity.QModelDeployHstEntity.modelDeployHstEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelMngBakEntity.modelMngBakEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity.modelVerEntity;

import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.postgres.QuerydslOrderUtil;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngBakEntity;
import com.kamco.cd.kamcoback.postgres.entity.ModelVerEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import io.micrometer.common.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;

public class ModelMngRepositoryImpl extends QuerydslRepositorySupport
    implements ModelMngRepositoryCustom {

  private final JPAQueryFactory queryFactory;
  private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");

  public ModelMngRepositoryImpl(JPAQueryFactory queryFactory) {
    super(ModelMngBakEntity.class);
    this.queryFactory = queryFactory;
  }

  @Override
  public List<ModelMngBakEntity> findModelMngAll() {
    return queryFactory.selectFrom(modelMngBakEntity).orderBy(modelMngBakEntity.id.desc()).fetch();
  }

  @Override
  public Optional<ModelMngDto.FinalModelDto> getFinalModelInfo() {
    return queryFactory
        .select(
            Projections.constructor(
                ModelMngDto.FinalModelDto.class,
                modelMngBakEntity.id.as("modelUid"),
                modelMngBakEntity.modelNm,
                modelMngBakEntity.modelCate,
                modelVerEntity.id.as("modelVerUid"),
                modelVerEntity.modelVer,
                Expressions.stringTemplate(
                    "fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.usedState), // 사용여부 한글 명칭
                Expressions.stringTemplate(
                    "fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.modelState), // 모델상태 한글 명칭
                modelVerEntity.qualityProb,
                Expressions.stringTemplate(
                    "fn_codenm_to_misc({0}, {1})", 52, modelVerEntity.deployState), // 배포상태 한글 명칭
                modelVerEntity.modelPath))
        .from(modelMngBakEntity)
        .innerJoin(modelVerEntity)
        .on(modelMngBakEntity.id.eq(modelVerEntity.modelUid))
        .where(modelVerEntity.usedState.eq("USED")) // USED 인 것 중에
        .orderBy(modelVerEntity.modelVer.desc()) // Version 높은 것 기준
        .stream()
        .findFirst();
  }

  @Override
  public Page<ModelMngDto.ModelRegHistory> getRegHistoryList(
      ModelMngDto.searchReq searchReq, LocalDate startDate, LocalDate endDate, String searchVal) {

    Pageable pageable = searchReq.toPageable();
    List<ModelMngDto.ModelRegHistory> foundContent =
        queryFactory
            .select(
                Projections.constructor(
                    ModelMngDto.ModelRegHistory.class,
                    modelMngBakEntity.modelNm,
                    modelMngBakEntity.modelCate,
                    modelVerEntity.modelVer,
                    Expressions.stringTemplate(
                            "to_char({0}, 'YYYY-MM-DD')", modelVerEntity.createdDate)
                        .as("createdDttm"),
                    Expressions.stringTemplate(
                        "fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.usedState), // 사용여부 한글 명칭
                    Expressions.stringTemplate(
                        "fn_codenm_to_misc({0}, {1})",
                        52, modelVerEntity.deployState), // 배포상태 한글 명칭
                    Expressions.stringTemplate(
                            "to_char({0}, 'YYYY-MM-DD')", modelDeployHstEntity.deployDttm)
                        .as("deployDttm")))
            .from(modelMngBakEntity)
            .innerJoin(modelVerEntity)
            .on(modelMngBakEntity.id.eq(modelVerEntity.modelUid))
            .leftJoin(modelDeployHstEntity)
            .on(
                modelVerEntity
                    .id
                    .eq(modelDeployHstEntity.modelVerUid)
                    .and(modelDeployHstEntity.serverId.eq(1L)) // 1건만 조회해야 하기에 1번 서버만 조회하기
                )
            .where(eventEndedAtBetween(startDate, endDate), searchModelVerLike(searchVal))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(
                QuerydslOrderUtil.getOrderSpecifiers(
                    pageable, ModelVerEntity.class, "modelVerEntity"))
            .fetch();

    Long countQuery =
        queryFactory
            .select(modelVerEntity.id.count())
            .from(modelMngBakEntity)
            .innerJoin(modelVerEntity)
            .on(modelMngBakEntity.id.eq(modelVerEntity.modelUid))
            .where(eventEndedAtBetween(startDate, endDate), searchModelVerLike(searchVal))
            .fetchOne();

    return new PageImpl<>(foundContent, pageable, countQuery);
  }

  private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) {
    if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
      return null;
    }
    LocalDateTime startDateTime = startDate.atStartOfDay();
    LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
    return modelMngBakEntity
        .createdDate
        .goe(ZonedDateTime.from(startDateTime))
        .and(modelMngBakEntity.modifiedDate.lt(ZonedDateTime.from(endDateTime)));
  }

  private BooleanExpression searchModelVerLike(String searchVal) {
    if (StringUtils.isBlank(searchVal)) {
      return null;
    }
    return modelVerEntity.modelVer.contains(searchVal);
  }
}
