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

import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.postgres.entity.ModelTrainJobEntity;
import com.kamco.cd.training.postgres.repository.train.ModelTrainJobRepository;
import com.kamco.cd.training.train.dto.ModelTrainJobDto;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Log4j2
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ModelTrainJobCoreService {

  private final ModelTrainJobRepository modelTrainJobRepository;

  public int findMaxAttemptNo(Long modelId) {
    return modelTrainJobRepository.findMaxAttemptNo(modelId);
  }

  public Optional<ModelTrainJobDto> findLatestByModelId(Long modelId) {
    return modelTrainJobRepository.findLatestByModelId(modelId).map(ModelTrainJobEntity::toDto);
  }

  public Optional<ModelTrainJobDto> findById(Long jobId) {
    return modelTrainJobRepository.findById(jobId).map(ModelTrainJobEntity::toDto);
  }

  /** QUEUED Job 생성 */
  @Transactional
  public Long createQueuedJob(
      Long modelId, int attemptNo, Map<String, Object> paramsJson, ZonedDateTime queuedDttm) {

    ModelTrainJobEntity job = new ModelTrainJobEntity();
    job.setModelId(modelId);
    job.setAttemptNo(attemptNo);
    job.setStatusCd("QUEUED");
    job.setParamsJson(paramsJson);
    job.setQueuedDttm(queuedDttm != null ? queuedDttm : ZonedDateTime.now());

    modelTrainJobRepository.save(job);
    modelTrainJobRepository.flush();
    return job.getId();
  }

  /** 실행 시작 처리 */
  @Transactional
  public void markRunning(
      Long jobId,
      String containerName,
      String logPath,
      String lockedBy,
      Integer totalEpoch,
      String jobType) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd("RUNNING");
    job.setContainerName(containerName);
    job.setLogPath(logPath);
    job.setStartedDttm(ZonedDateTime.now());
    job.setLockedDttm(ZonedDateTime.now());
    job.setLockedBy(lockedBy);
    job.setJobType(jobType);

    if (totalEpoch != null) {
      job.setTotalEpoch(totalEpoch);
    }
  }

  /** 실행 시작 처리 수정 */
  @Transactional
  public void updateJobStatus(Long jobId, String jobStatus) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd(jobStatus);
  }

  /**
   * 성공 처리
   *
   * @param jobId
   * @param exitCode
   */
  @Transactional
  public void markSuccess(Long jobId, int exitCode) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd("SUCCESS");
    job.setExitCode(exitCode);
    job.setFinishedDttm(ZonedDateTime.now());
  }

  /**
   * 실패 처리
   *
   * @param jobId
   * @param exitCode
   * @param errorMessage
   */
  @Transactional
  public void markFailed(Long jobId, Integer exitCode, String errorMessage) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd("FAILED");
    job.setExitCode(exitCode);
    job.setErrorMessage(errorMessage);
    job.setFinishedDttm(ZonedDateTime.now());

    log.info("[TRAIN JOB FAIL] jobId={}, modelId={}", jobId, errorMessage);
  }

  /**
   * 중단됨 처리
   *
   * @param jobId
   * @param exitCode
   * @param errorMessage
   */
  @Transactional
  public void markPaused(Long jobId, Integer exitCode, String errorMessage) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd("STOPPED");
    job.setExitCode(exitCode);
    job.setErrorMessage(errorMessage);
    job.setFinishedDttm(ZonedDateTime.now());

    log.info("[TRAIN JOB FAIL] jobId={}, modelId={}", jobId, errorMessage);
  }

  /** 취소 처리 */
  @Transactional
  public void markCanceled(Long jobId) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findById(jobId)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setStatusCd("STOPPED");
    job.setFinishedDttm(ZonedDateTime.now());
  }

  @Transactional
  public void updateEpoch(String containerName, Integer epoch) {
    ModelTrainJobEntity job =
        modelTrainJobRepository
            .findByContainerName(containerName)
            .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));

    job.setCurrentEpoch(epoch);

    if (Objects.equals(job.getTotalEpoch(), epoch)) {}
  }

  public void insertModelTestTrainingRun(Long modelId, Long jobId, int epoch) {
    modelTrainJobRepository.insertModelTestTrainingRun(modelId, jobId, epoch);
  }

  /**
   * 실행중인 학습이 있는지 조회
   *
   * @return
   */
  public List<ModelTrainJobDto> findRunningJobs() {
    List<ModelTrainJobEntity> entity = modelTrainJobRepository.findRunningJobs();

    if (entity == null || entity.isEmpty()) {
      return Collections.emptyList();
    }

    return entity.stream().map(ModelTrainJobEntity::toDto).toList();
  }
}
