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

import static com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity.modelMngEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelResultMetricEntity.modelResultMetricEntity;

import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.postgres.QuerydslOrderUtil;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Expression;
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.NumberPath;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.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(ModelMngEntity.class);
    this.queryFactory = queryFactory;
  }

  @Override
  public Page<ModelMngDto.ModelList> findModelMgmtList(
      ModelMngDto.searchReq searchReq,
      LocalDate startDate,
      LocalDate endDate,
      String modelType,
      String searchVal) {
    Pageable pageable = searchReq.toPageable();
    Sort sort = pageable.getSort();
    String property = "createCompleteDttm"; // 기본으로 생성완료일 기준

    Map<String, Expression<?>> sortColumnMap =
        Map.of(
            "createCompleteDttm", modelMngEntity.createCompleteDttm,
            "recentUseDttm", modelMngEntity.recentUseDttm);

    if (sort.isSorted()) {
      Sort.Order order = sort.iterator().next();
      property = order.getProperty();
    }

    Expression<?> sortColumn = sortColumnMap.get(property);

    List<ModelMngDto.ModelList> foundContent =
        queryFactory
            .select(
                Projections.constructor(
                    ModelMngDto.ModelList.class,
                    Expressions.numberTemplate(
                        Integer.class, "row_number() over(order by {0} desc)", sortColumn),
                    Expressions.stringTemplate(
                            "concat({0}, {1}, {2}, {3}, {4})",
                            modelMngEntity.modelVer,
                            ".",
                            modelMngEntity.hyperVer,
                            ".",
                            modelMngEntity.epochVer)
                        .as("modelVer"),
                    modelMngEntity.dockerFileNm,
                    modelMngEntity.modelVer.as("modelType"),
                    Expressions.stringTemplate(
                        "to_char({0}, 'YYYY-MM-DD')", modelMngEntity.createCompleteDttm),
                    Expressions.stringTemplate(
                        "to_char({0}, 'YYYY-MM-DD')", modelMngEntity.recentUseDttm),
                    roundNumericToPercent(modelResultMetricEntity.f1Score),
                    roundNumericToPercent(modelResultMetricEntity.precision),
                    roundNumericToPercent(modelResultMetricEntity.recall),
                    roundNumericToPercent(modelResultMetricEntity.loss),
                    roundNumericToPercent(modelResultMetricEntity.iou),
                    modelMngEntity.deleted))
            .from(modelMngEntity)
            .innerJoin(modelResultMetricEntity)
            .on(modelMngEntity.modelUid.eq(modelResultMetricEntity.modelUid))
            .where(
                eventEndedAtBetween(startDate, endDate, property),
                searchModelVersion(modelType, searchVal))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(
                QuerydslOrderUtil.getOrderSpecifiers(
                    pageable, ModelMngEntity.class, "modelMngEntity"))
            .fetch();

    Long countQuery =
        queryFactory
            .select(modelMngEntity.modelUid.count())
            .from(modelMngEntity)
            .where(
                eventEndedAtBetween(startDate, endDate, property),
                searchModelVersion(modelType, searchVal))
            .fetchOne();

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

  private BooleanExpression eventEndedAtBetween(
      LocalDate startDate, LocalDate endDate, String sortColumn) {
    if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
      return null;
    }
    ZoneId zone = ZoneId.systemDefault();
    LocalDateTime startDateTime = startDate.atStartOfDay();
    LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();

    if (sortColumn.equals("createCompleteDttm")) {
      return modelMngEntity
          .createCompleteDttm
          .goe(startDateTime.atZone(zone))
          .and(modelMngEntity.createCompleteDttm.lt(endDateTime.atZone(zone)));
    } else {
      return modelMngEntity
          .recentUseDttm
          .goe(startDateTime.atZone(zone))
          .and(modelMngEntity.recentUseDttm.lt(endDateTime.atZone(zone)));
    }
  }

  private BooleanBuilder searchModelVersion(String modelType, String searchVal) {
    BooleanBuilder builder = new BooleanBuilder();

    if (Objects.nonNull(modelType)) {
      builder.and(modelMngEntity.modelVer.eq(modelType));
    }

    if (Objects.nonNull(searchVal)) {
      builder.and(modelMngEntity.dockerFileNm.likeIgnoreCase("%" + searchVal + "%"));
    }

    return builder;
  }

  private Expression<BigDecimal> roundNumericToPercent(NumberPath<Double> ratio) {
    return Expressions.numberTemplate(BigDecimal.class, "function('round', {0} * 100, 2)", ratio);
  }
}
