package com.kamco.cd.training.postgres.core;

import com.kamco.cd.training.common.dto.HyperClsParam;
import com.kamco.cd.training.common.enums.ModelType;
import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.common.utils.UserUtil;
import com.kamco.cd.training.hyperparam.dto.HyperClsParamDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.SearchReq;
import com.kamco.cd.training.postgres.entity.ModelHyperClsParamEntity;
import com.kamco.cd.training.postgres.repository.hyperparam.HyperClsParamRepository;
import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class HyperClsParamCoreService {

  private final HyperClsParamRepository hyperClsParamRepository;
  private final UserUtil userUtil;

  /**
   * 하이퍼파라미터 등록
   *
   * @param createReq ModelTrainMngDto.HyperParamDto
   * @return 등록된 버전명
   */
  public HyperClsParamDto.Basic createHyperParam(HyperClsParam createReq) {
    String firstVersion = getFirstHyperParamVersion(createReq.getModel());

    ModelHyperClsParamEntity entity = new ModelHyperClsParamEntity();
    entity.setHyperVer(firstVersion);
    applyHyperParam(entity, createReq);

    // user
    entity.setCreatedUid(userUtil.getId());

    ModelHyperClsParamEntity resultEntity = hyperClsParamRepository.save(entity);
    HyperClsParamDto.Basic basic = new HyperClsParamDto.Basic();
    basic.setUuid(resultEntity.getUuid());
    basic.setHyperVer(resultEntity.getHyperVer());
    return basic;
  }

  /**
   * 하이퍼파라미터 수정
   *
   * @param uuid uuid
   * @param createReq 등록 요청
   * @return ver
   */
  public String updateHyperParam(UUID uuid, HyperClsParam createReq) {
    ModelHyperClsParamEntity entity =
        hyperClsParamRepository
            .findHyperParamByUuid(uuid)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    if (entity.getIsDefault()) {
      throw new CustomApiException("UNPROCESSABLE_ENTITY_UPDATE", HttpStatus.UNPROCESSABLE_ENTITY);
    }
    applyHyperParam(entity, createReq);

    // user
    entity.setUpdatedUid(userUtil.getId());
    entity.setUpdatedDttm(ZonedDateTime.now());

    return entity.getHyperVer();
  }

  /**
   * 하이퍼파라미터 삭제
   *
   * @param uuid
   */
  public void deleteHyperParam(UUID uuid) {
    ModelHyperClsParamEntity entity =
        hyperClsParamRepository
            .findHyperParamByUuid(uuid)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    // 디폴트면 삭제불가
    if (entity.getIsDefault()) {
      throw new CustomApiException("UNPROCESSABLE_ENTITY", HttpStatus.UNPROCESSABLE_ENTITY);
    }

    entity.setDelYn(true);
    entity.setUpdatedUid(userUtil.getId());
    entity.setUpdatedDttm(ZonedDateTime.now());
  }

  /**
   * 하이퍼파라미터 최적화 설정값 조회
   *
   * @return
   */
  public HyperClsParamDto.Basic getInitHyperParam(ModelType model) {
    ModelHyperClsParamEntity entity =
        hyperClsParamRepository.getHyperParamByType(model).stream()
            .filter(e -> e.getIsDefault() == Boolean.TRUE)
            .findFirst()
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
    return entity.toDto();
  }

  /**
   * 하이퍼파라미터 상세 조회
   *
   * @return
   */
  public HyperClsParamDto.Basic getHyperParam(UUID uuid) {
    ModelHyperClsParamEntity entity =
        hyperClsParamRepository
            .findHyperParamByUuid(uuid)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
    return entity.toDto();
  }

  /**
   * 하이퍼파라미터 목록 조회
   *
   * @param model
   * @param req
   * @return
   */
  public Page<HyperParamDto.List> findByHyperVerList(ModelType model, SearchReq req) {
    return hyperClsParamRepository.findByHyperVerList(model, req);
  }

  /**
   * 하이퍼파라미터 버전 조회
   *
   * @param model 모델 타입
   * @return ver
   */
  public String getFirstHyperParamVersion(ModelType model) {
    return hyperClsParamRepository
        .findHyperParamVerByModelType(model)
        .map(ModelHyperClsParamEntity::getHyperVer)
        .map(ver -> increase(ver, model))
        .orElse(model.name() + "_000001");
  }

  /**
   * 하이퍼 파라미터의 버전을 증가시킨다.
   *
   * @param hyperVer 현재 버전
   * @param modelType 모델 타입
   * @return 증가된 버전
   */
  private String increase(String hyperVer, ModelType modelType) {
    String prefix = modelType.name() + "_";
    int num = Integer.parseInt(hyperVer.substring(prefix.length()));
    return prefix + String.format("%06d", num + 1);
  }

  private void applyHyperParam(ModelHyperClsParamEntity entity, HyperClsParam src) {

    ModelType model = src.getModel();

    entity.setInputSize(src.getInputSize());

    // Model
    entity.setModelType(model);
    entity.setInputSize(src.getInputSize());
    entity.setBatchSize(src.getBatchSize());
    entity.setInputSize(src.getInputSize());

    // Argument
    entity.setScale(src.getScale());
    entity.setTranslate(src.getTranslate());
    entity.setFliplr(src.getFliplr());
    entity.setFlipud(src.getFlipud());
    entity.setErasing(src.getErasing());
    entity.setMixup(src.getMixup());
    entity.setHsvH(src.getHsvH());
    entity.setHsvS(src.getHsvS());
    entity.setHsvV(src.getHsvV());
    entity.setDropout(src.getDropout());
    entity.setLabelSmoothing(src.getLabelSmoothing());

    // memo
    entity.setMemo(src.getMemo());
    entity.setIsDefault(src.getIsDefault());
  }
}
