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

import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity;
import static com.kamco.cd.training.postgres.entity.QModelConfigEntity.modelConfigEntity;
import static com.kamco.cd.training.postgres.entity.QModelHyperParamEntity.modelHyperParamEntity;
import static com.kamco.cd.training.postgres.entity.QModelMasterEntity.modelMasterEntity;
import static com.kamco.cd.training.postgres.entity.QModelTrainJobEntity.modelTrainJobEntity;

import com.kamco.cd.training.common.enums.TrainStatusType;
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.ListDto;
import com.kamco.cd.training.postgres.entity.ModelMasterEntity;
import com.kamco.cd.training.postgres.entity.QModelMasterEntity;
import com.kamco.cd.training.train.dto.TrainRunRequest;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Expression;
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.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 ModelMngRepositoryImpl implements ModelMngRepositoryCustom {

  private final JPAQueryFactory queryFactory;

  /**
   * 모델 목록 조회
   *
   * @param req
   * @return
   */
  @Override
  public Page<ListDto> findByModels(ModelTrainMngDto.SearchReq req) {
    QModelMasterEntity beforeModel = new QModelMasterEntity("beforeModel"); // alias

    Expression<UUID> beforeModelUuid =
        com.querydsl.jpa.JPAExpressions.select(beforeModel.uuid)
            .from(beforeModel)
            .where(beforeModel.id.eq(modelMasterEntity.beforeModelId));

    Pageable pageable = req.toPageable();
    BooleanBuilder builder = new BooleanBuilder();

    if (req.getStatus() != null && !req.getStatus().isEmpty()) {
      builder.and(
          modelMasterEntity
              .step1State
              .eq(req.getStatus())
              .or(modelMasterEntity.step2State.eq(req.getStatus())));
    }

    if (req.getModelNo() != null && !req.getModelNo().isEmpty()) {
      builder.and(modelMasterEntity.modelNo.eq(req.getModelNo()));
    }

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

    List<ListDto> content =
        queryFactory
            .select(
                Projections.constructor(
                    ListDto.class,
                    modelMasterEntity.id,
                    modelMasterEntity.uuid,
                    modelMasterEntity.modelVer,
                    modelMasterEntity.strtDttm,
                    modelMasterEntity.step1StrtDttm,
                    modelMasterEntity.step1EndDttm,
                    modelMasterEntity.step2StrtDttm,
                    modelMasterEntity.step2EndDttm,
                    modelMasterEntity.step1State,
                    modelMasterEntity.step2State,
                    modelMasterEntity.statusCd,
                    modelMasterEntity.trainType,
                    modelMasterEntity.modelNo,
                    modelMasterEntity.currentAttemptId,
                    modelMasterEntity.requestPath,
                    modelMasterEntity.packingState,
                    modelMasterEntity.packingStrtDttm,
                    modelMasterEntity.packingEndDttm,
                    modelConfigEntity.memo,
                    memberEntity.name,
                    beforeModelUuid))
            .from(modelMasterEntity)
            .innerJoin(modelConfigEntity)
            .on(modelMasterEntity.id.eq(modelConfigEntity.model.id))
            .leftJoin(memberEntity)
            .on(modelMasterEntity.createdUid.eq(memberEntity.id))
            .where(builder)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(modelMasterEntity.createdDttm.desc())
            .fetch();

    // Count 쿼리 별도 실행 (null safe handling)
    Long total =
        queryFactory
            .select(modelMasterEntity.count())
            .from(modelMasterEntity)
            .innerJoin(modelConfigEntity)
            .on(modelMasterEntity.id.eq(modelConfigEntity.model.id))
            .leftJoin(memberEntity)
            .on(modelMasterEntity.createdUid.eq(memberEntity.id))
            .where(builder)
            .fetchOne();

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

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

  /**
   * 모델 조회
   *
   * @param uuid
   * @return
   */
  @Override
  public Optional<ModelMasterEntity> findByUuid(UUID uuid) {
    return Optional.ofNullable(
        queryFactory
            .select(modelMasterEntity)
            .from(modelMasterEntity)
            .where(modelMasterEntity.uuid.eq(uuid))
            .fetchOne());
  }

  // TODO 미사용시작
  @Override
  public Optional<ModelMasterEntity> findFirstByStatusCdAndDelYn(String statusCd, Boolean delYn) {
    return Optional.empty();
  }

  // TODO 미사용 끝

  @Override
  public TrainRunRequest findTrainRunRequest(Long modelId) {
    QModelMasterEntity beforeModel = new QModelMasterEntity("beforeModel");

    return queryFactory
        .select(
            Projections.constructor(
                TrainRunRequest.class,
                modelMasterEntity.requestPath, // datasetFolder
                modelMasterEntity.uuid, // outputFolder
                modelHyperParamEntity.inputSize,
                modelHyperParamEntity.cropSize,
                modelHyperParamEntity.batchSize,
                modelHyperParamEntity.gpuIds,
                modelHyperParamEntity.gpuCnt,
                modelHyperParamEntity.learningRate,
                modelHyperParamEntity.backbone,
                modelConfigEntity.epochCount,
                modelHyperParamEntity.trainNumWorkers,
                modelHyperParamEntity.valNumWorkers,
                modelHyperParamEntity.testNumWorkers,
                modelHyperParamEntity.trainShuffle,
                modelHyperParamEntity.trainPersistent,
                modelHyperParamEntity.valPersistent,
                modelHyperParamEntity.dropPathRate,
                modelHyperParamEntity.frozenStages,
                modelHyperParamEntity.neckPolicy,
                modelHyperParamEntity.classWeight,
                modelHyperParamEntity.decoderChannels,
                modelHyperParamEntity.weightDecay,
                modelHyperParamEntity.layerDecayRate,
                modelHyperParamEntity.ignoreIndex,
                modelHyperParamEntity.ddpFindUnusedParams,
                modelHyperParamEntity.numLayers,
                modelHyperParamEntity.metrics,
                modelHyperParamEntity.saveBest,
                modelHyperParamEntity.saveBestRule,
                modelHyperParamEntity.valInterval,
                modelHyperParamEntity.logInterval,
                modelHyperParamEntity.visInterval,
                modelHyperParamEntity.rotProb,
                modelHyperParamEntity.rotDegree,
                modelHyperParamEntity.flipProb,
                modelHyperParamEntity.exchangeProb,
                modelHyperParamEntity.brightnessDelta,
                modelHyperParamEntity.contrastRange,
                modelHyperParamEntity.saturationRange,
                modelHyperParamEntity.hueDelta,
                Expressions.nullExpression(Integer.class),
                Expressions.nullExpression(String.class),
                modelHyperParamEntity.uuid,
                modelMasterEntity.reqTmpYn,
                modelMasterEntity.trainType,
                beforeModel.uuid,
                beforeModel.bestEpoch))
        .from(modelMasterEntity)
        .leftJoin(modelHyperParamEntity)
        .on(modelHyperParamEntity.id.eq(modelMasterEntity.hyperParamId))
        .leftJoin(modelConfigEntity)
        .on(modelConfigEntity.model.id.eq(modelMasterEntity.id))
        .leftJoin(beforeModel)
        .on(modelMasterEntity.beforeModelId.eq(beforeModel.id))
        .where(modelMasterEntity.id.eq(modelId))
        .fetchOne();
  }

  @Override
  public Long findModelStep1InProgressCnt() {
    return queryFactory
        .select(modelMasterEntity.id.count())
        .from(modelMasterEntity)
        .where(
            modelMasterEntity
                .step1State
                .eq(TrainStatusType.IN_PROGRESS.getId())
                .or(modelMasterEntity.step2State.eq(TrainStatusType.IN_PROGRESS.getId())))
        .fetchOne();
  }

  @Override
  public List<ModelMasterEntity> findByModelNoAndDelYnOrderByCreatedDttmDesc(
      String modelNo, Boolean delYn) {
    return queryFactory
        .selectFrom(modelMasterEntity)
        .where(modelMasterEntity.modelNo.eq(modelNo), modelMasterEntity.delYn.eq(delYn))
        .orderBy(modelMasterEntity.createdDttm.desc())
        .fetch();
  }

  @Override
  public List<ModelMasterEntity> findByHyperParamId(Long hyperParamId) {
    return queryFactory
        .selectFrom(modelMasterEntity)
        .where(modelMasterEntity.hyperParamId.eq(hyperParamId), modelMasterEntity.delYn.eq(false))
        .orderBy(modelMasterEntity.createdDttm.desc())
        .fetch();
  }

  @Override
  public ModelTrainMngDto.InProgressModel findInprogressModel() {
    return queryFactory
        .select(
            Projections.constructor(
                ModelTrainMngDto.InProgressModel.class,
                modelMasterEntity.modelNo,
                modelMasterEntity.uuid))
        .from(modelMasterEntity)
        .where(
            modelMasterEntity
                .step1State
                .eq(TrainStatusType.IN_PROGRESS.getId())
                .or(modelMasterEntity.step2State.eq(TrainStatusType.IN_PROGRESS.getId())))
        .fetchOne();
  }

  @Override
  public ModelTrainMngDto.ProgressPercent findTrainProgressPercent(Long id) {
    NumberExpression<Integer> currentEpoch =
        new CaseBuilder()
            .when(
                modelTrainJobEntity
                    .jobType
                    .eq("TEST")
                    .and(modelTrainJobEntity.statusCd.eq("SUCCESS")))
            .then(1)
            .otherwise(modelTrainJobEntity.currentEpoch.coalesce(0));

    NumberExpression<Integer> totalEpoch = modelTrainJobEntity.totalEpoch.coalesce(1);

    // per 계산
    NumberExpression<Double> per =
        currentEpoch
            .castToNum(Double.class)
            .divide(totalEpoch.castToNum(Double.class))
            .multiply(100);

    return queryFactory
        .select(
            Projections.constructor(
                ModelTrainMngDto.ProgressPercent.class,
                modelTrainJobEntity.modelId,
                modelTrainJobEntity.jobType,
                modelTrainJobEntity.statusCd,
                totalEpoch.as("totalEpoch"),
                currentEpoch.as("currentEpoch"),
                per.as("per")))
        .from(modelTrainJobEntity)
        .where(modelTrainJobEntity.modelId.eq(id))
        .orderBy(modelTrainJobEntity.attemptNo.desc())
        .fetchFirst();
  }
}
