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

import com.kamco.cd.training.common.dto.HyperParam;
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.HyperParamDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.Basic;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.SearchReq;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import com.kamco.cd.training.postgres.repository.hyperparam.HyperParamRepository;
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 HyperParamCoreService {

  private final HyperParamRepository hyperParamRepository;
  private final UserUtil userUtil;

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

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

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

    ModelHyperParamEntity resultEntity = hyperParamRepository.save(entity);
    Basic basic = new Basic();
    basic.setUuid(resultEntity.getUuid());
    basic.setHyperVer(resultEntity.getHyperVer());
    return basic;
  }

  /**
   * 하이퍼파라미터 수정
   *
   * @param uuid uuid
   * @param createReq 등록 요청
   * @return ver
   */
  public String updateHyperParam(UUID uuid, HyperParam createReq) {
    ModelHyperParamEntity entity =
        hyperParamRepository
            .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) {
    ModelHyperParamEntity entity =
        hyperParamRepository
            .findHyperParamByUuid(uuid)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    //    if (entity.getHyperVer().equals("HPs_0001")) {
    //      throw new CustomApiException("UNPROCESSABLE_ENTITY", HttpStatus.UNPROCESSABLE_ENTITY);
    //    }

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

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

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

  /**
   * 하이퍼파라미터 상세 조회
   *
   * @return
   */
  public HyperParamDto.Basic getHyperParam(UUID uuid) {
    ModelHyperParamEntity entity =
        hyperParamRepository
            .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 hyperParamRepository.findByHyperVerList(model, req);
  }

  /**
   * 하이퍼파라미터 버전 조회
   *
   * @param model 모델 타입
   * @return ver
   */
  public String getFirstHyperParamVersion(ModelType model) {
    return hyperParamRepository
        .findHyperParamVerByModelType(model)
        .map(ModelHyperParamEntity::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(ModelHyperParamEntity entity, HyperParam src) {

    ModelType model = src.getModel();

    // 하드코딩 모델별로 다른경우 250212 bbn 하드코딩
    if (model == ModelType.G3) {
      entity.setCropSize("512,512");
    } else {
      entity.setCropSize("256,256");
    }
    entity.setCropSize(src.getCropSize());

    // Important
    entity.setModelType(model); // 20250212 modeltype추가
    entity.setBackbone(src.getBackbone());
    entity.setInputSize(src.getInputSize());
    entity.setBatchSize(src.getBatchSize());

    // Data
    entity.setTrainNumWorkers(src.getTrainNumWorkers());
    entity.setValNumWorkers(src.getValNumWorkers());
    entity.setTestNumWorkers(src.getTestNumWorkers());
    entity.setTrainShuffle(src.getTrainShuffle());
    entity.setTrainPersistent(src.getTrainPersistent());
    entity.setValPersistent(src.getValPersistent());

    // Model Architecture
    entity.setDropPathRate(src.getDropPathRate());
    entity.setFrozenStages(src.getFrozenStages());
    entity.setNeckPolicy(src.getNeckPolicy());
    entity.setClassWeight(src.getClassWeight());
    entity.setDecoderChannels(src.getDecoderChannels());

    // Loss & Optimization
    entity.setLearningRate(src.getLearningRate());
    entity.setWeightDecay(src.getWeightDecay());
    entity.setLayerDecayRate(src.getLayerDecayRate());
    entity.setDdpFindUnusedParams(src.getDdpFindUnusedParams());
    entity.setIgnoreIndex(src.getIgnoreIndex());
    entity.setNumLayers(src.getNumLayers());

    // Evaluation
    entity.setMetrics(src.getMetrics());
    entity.setSaveBest(src.getSaveBest());
    entity.setSaveBestRule(src.getSaveBestRule());
    entity.setValInterval(src.getValInterval());
    entity.setLogInterval(src.getLogInterval());
    entity.setVisInterval(src.getVisInterval());

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