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

import com.kamco.cd.training.common.enums.HyperParamSelectType;
import com.kamco.cd.training.common.enums.ModelType;
import com.kamco.cd.training.common.enums.TrainStatusType;
import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.common.utils.UserUtil;
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetReq;
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectDataSet;
import com.kamco.cd.training.model.dto.ModelConfigDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.TrainingDataset;
import com.kamco.cd.training.postgres.entity.ModelConfigEntity;
import com.kamco.cd.training.postgres.entity.ModelDatasetEntity;
import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import com.kamco.cd.training.postgres.entity.ModelMasterEntity;
import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository;
import com.kamco.cd.training.postgres.repository.hyperparam.HyperParamRepository;
import com.kamco.cd.training.postgres.repository.model.ModelConfigRepository;
import com.kamco.cd.training.postgres.repository.model.ModelDatasetMappRepository;
import com.kamco.cd.training.postgres.repository.model.ModelDatasetRepository;
import com.kamco.cd.training.postgres.repository.model.ModelMngRepository;
import java.time.ZonedDateTime;
import java.util.List;
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 ModelTrainMngCoreService {
  private final ModelMngRepository modelMngRepository;
  private final ModelDatasetRepository modelDatasetRepository;
  private final ModelDatasetMappRepository modelDatasetMapRepository;
  private final ModelConfigRepository modelConfigRepository;
  private final HyperParamRepository hyperParamRepository;
  private final DatasetRepository datasetRepository;
  private final UserUtil userUtil;

  /**
   * 모델 목록 조회
   *
   * @param searchReq 검색 조건
   * @return 페이징 처리된 모델 목록
   */
  public Page<Basic> findByModelList(ModelTrainMngDto.SearchReq searchReq) {
    Page<ModelMasterEntity> entityPage = modelMngRepository.findByModels(searchReq);
    return entityPage.map(ModelMasterEntity::toDto);
  }

  /**
   * 학습모델 삭제
   *
   * @param uuid
   */
  public void deleteModel(UUID uuid) {
    ModelMasterEntity entity =
        modelMngRepository
            .findByUuid(uuid)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
    entity.setDelYn(true);
    entity.setUpdatedDttm(ZonedDateTime.now());
    entity.setUpdatedUid(userUtil.getId());
  }

  /**
   * 모델학습 저장
   *
   * @param addReq
   * @return
   */
  public Long saveModel(ModelTrainMngDto.AddReq addReq) {
    ModelMasterEntity entity = new ModelMasterEntity();
    ModelHyperParamEntity hyperParamEntity = new ModelHyperParamEntity();

    // 최적화 파라미터는 HPs_0001 사용
    if (HyperParamSelectType.OPTIMIZED.getId().equals(addReq.getHyperParamType())) {
      hyperParamEntity = hyperParamRepository.findByHyperVer("HPs_0001").orElse(null);

    } else {
      hyperParamEntity =
          hyperParamRepository.findHyperParamByUuid(addReq.getHyperUuid()).orElse(null);
    }

    if (hyperParamEntity == null || hyperParamEntity.getHyperVer() == null) {
      throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND);
    }

    String modelVer =
        String.join(
            ".", addReq.getModelNo(), hyperParamEntity.getHyperVer(), entity.getUuid().toString());
    entity.setModelVer(modelVer);
    entity.setHyperParamId(hyperParamEntity.getId());
    entity.setModelNo(addReq.getModelNo());
    entity.setTrainType(addReq.getTrainType()); // 일반, 전이
    entity.setBeforeModelId(addReq.getBeforeModelId());

    if (addReq.getIsStart()) {
      entity.setModelStep((short) 1);
      entity.setStatusCd(TrainStatusType.IN_PROGRESS.getId());
      entity.setStrtDttm(ZonedDateTime.now());
      entity.setStep1StrtDttm(ZonedDateTime.now());
      entity.setStep1State(TrainStatusType.IN_PROGRESS.getId());
    } else {
      entity.setStatusCd(TrainStatusType.READY.getId());
    }

    entity.setCreatedUid(userUtil.getId());
    ModelMasterEntity resultEntity = modelMngRepository.save(entity);
    return resultEntity.getId();
  }

  /**
   * data set 저장
   *
   * @param modelId 저장한 모델 학습 id
   * @param addReq 요청 파라미터
   */
  public void saveModelDataset(Long modelId, ModelTrainMngDto.AddReq addReq) {
    TrainingDataset dataset = addReq.getTrainingDataset();
    ModelMasterEntity modelMasterEntity = new ModelMasterEntity();
    ModelDatasetEntity datasetEntity = new ModelDatasetEntity();

    modelMasterEntity.setId(modelId);
    datasetEntity.setModel(modelMasterEntity);

    if (addReq.getModelNo().equals(ModelType.G1.getId())) {
      datasetEntity.setBuildingCnt(dataset.getSummary().getBuildingCnt());
      datasetEntity.setContainerCnt(dataset.getSummary().getContainerCnt());
    } else if (addReq.getModelNo().equals(ModelType.G2.getId())) {
      datasetEntity.setWasteCnt(dataset.getSummary().getWasteCnt());
    } else if (addReq.getModelNo().equals(ModelType.G3.getId())) {
      datasetEntity.setLandCoverCnt(dataset.getSummary().getLandCoverCnt());
    }

    datasetEntity.setCreatedUid(userUtil.getId());

    // data set 저장
    modelDatasetRepository.save(datasetEntity);
  }

  /**
   * 모델 데이터셋 mapping 테이블 저장
   *
   * @param modelId 모델학습 id
   * @param datasetList 선택한 data set
   */
  public void saveModelDatasetMap(Long modelId, List<Long> datasetList) {

    for (Long datasetId : datasetList) {
      ModelDatasetMappEntity mapEntity = new ModelDatasetMappEntity();
      mapEntity.setModelUid(modelId);
      mapEntity.setDatasetUid(datasetId);
      modelDatasetMapRepository.save(mapEntity);
    }
  }

  /**
   * 모델학습 config 저장
   *
   * @param modelId 모델학습 id
   * @param req 요청 파라미터
   * @return
   */
  public void saveModelConfig(Long modelId, ModelTrainMngDto.ModelConfig req) {
    ModelMasterEntity modelMasterEntity = new ModelMasterEntity();
    ModelConfigEntity entity = new ModelConfigEntity();
    modelMasterEntity.setId(modelId);
    entity.setModel(modelMasterEntity);
    entity.setEpochCount(req.getEpochCnt());
    entity.setTrainPercent(req.getTrainingCnt());
    entity.setValidationPercent(req.getValidationCnt());
    entity.setTestPercent(req.getTestCnt());
    entity.setMemo(req.getMemo());

    modelConfigRepository.save(entity);
  }

  /**
   * 데이터셋 매핑 생성
   *
   * @param modelUid 모델 UID
   * @param datasetIds 데이터셋 ID 목록
   */
  public void createDatasetMappings(Long modelUid, List<Long> datasetIds) {
    for (Long datasetId : datasetIds) {
      ModelDatasetMappEntity mapping = new ModelDatasetMappEntity();
      mapping.setModelUid(modelUid);
      mapping.setDatasetUid(datasetId);
      mapping.setDatasetType("TRAIN");
      modelDatasetMapRepository.save(mapping);
    }
  }

  /**
   * UUID로 모델 조회
   *
   * @param uuid UUID
   * @return 모델 Entity
   */
  public ModelMasterEntity findByUuid(UUID uuid) {
    try {
      return modelMngRepository
          .findByUuid(uuid)
          .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
    } catch (IllegalArgumentException e) {
      throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid);
    }
  }

  /**
   * 모델학습 아이디로 config정보 조회
   *
   * @param uuid
   * @return
   */
  public ModelConfigDto.Basic findModelConfigByModelId(UUID uuid) {
    ModelMasterEntity modelEntity = findByUuid(uuid);
    return modelConfigRepository
        .findModelConfigByModelId(modelEntity.getId())
        .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
  }

  /**
   * 데이터셋 G1 목록
   *
   * @param req
   * @return
   */
  public List<SelectDataSet> getDatasetSelectG1List(DatasetReq req) {
    return datasetRepository.getDatasetSelectG1List(req);
  }

  /**
   * 데이터셋 G2, G3 목록
   *
   * @param req
   * @return
   */
  public List<SelectDataSet> getDatasetSelectG2G3List(DatasetReq req) {
    return datasetRepository.getDatasetSelectG2G3List(req);
  }
}
