package com.kamco.cd.training.dataset.service;

import com.kamco.cd.training.common.enums.LearnDataRegister;
import com.kamco.cd.training.dataset.dto.DatasetDto.AddDeliveriesReq;
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetMngRegDto;
import com.kamco.cd.training.postgres.core.DatasetCoreService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
@Log4j2
@RequiredArgsConstructor
public class DatasetAsyncService {

  private final DatasetService datasetService;
  private final DatasetCoreService datasetCoreService;

  private static final String LOG_PREFIX = "[납품 데이터셋]";

  /**
   * 납품 데이터셋 등록 비동기 업로드 처리 1) 데이터셋 구조/파일 검증 2) UID 생성 및 마스터 데이터 저장 3) 상태를 UPLOADING으로 변경 4) 실제
   * 데이터(train/val/test) 등록 5) 완료 시 COMPLETED 상태로 변경 6) 실패 시 상태를 UPLOAD_FAILED로 변경 후 데이터 정리(삭제)
   *
   * @param req
   */
  @Async("datasetExecutor")
  public void insertDeliveriesDatasetAsync(AddDeliveriesReq req) {

    long startTime = System.currentTimeMillis();

    log.info("{} 업로드 시작 ==========", LOG_PREFIX);
    log.info(
        "{} filePath={}, targetYyyy={}, compareYyyy={}, roundNo={}",
        LOG_PREFIX,
        req.getFilePath(),
        req.getTargetYyyy(),
        req.getCompareYyyy(),
        req.getRoundNo());

    Long datasetUid = null;

    try {

      // ===== 1. UID 생성 =====
      // String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();

      // 26-05-08: UID를 생성하지 않고 folder name 을 uid 로 저장하기  -> req.getFilePath()
      Path selectedPath = Paths.get(req.getFilePath());
      String uid = selectedPath.getFileName().toString();
      log.info("{} 생성된 UID: {}", LOG_PREFIX, uid);

      // ===== 2. 마스터 데이터 생성 =====
      String title = req.getTitle();

      if (title == null || title.isBlank()) {
        Integer compareYyyy = req.getCompareYyyy();
        Integer targetYyyy = req.getTargetYyyy();

        if (compareYyyy != null && targetYyyy != null) {
          title = compareYyyy + "-" + targetYyyy;
        } else {
          title = null;
        }
      }

      DatasetMngRegDto datasetMngRegDto = new DatasetMngRegDto();
      datasetMngRegDto.setUid(uid);
      datasetMngRegDto.setDataType("DELIVER");
      datasetMngRegDto.setCompareYyyy(req.getCompareYyyy() == null ? 0 : req.getCompareYyyy());
      datasetMngRegDto.setTargetYyyy(req.getTargetYyyy() == null ? 0 : req.getTargetYyyy());
      datasetMngRegDto.setRoundNo(req.getRoundNo());
      datasetMngRegDto.setTitle(title);
      datasetMngRegDto.setMemo(req.getMemo());
      datasetMngRegDto.setDatasetPath(req.getFilePath());
      datasetMngRegDto.setTotalSize(getDirectorySize(req.getFilePath())); // 선택한 폴더 안의 모든 파일 용량 합계

      // 마스터 저장
      datasetUid = datasetCoreService.insertDatasetMngData(datasetMngRegDto);

      log.info("{} 마스터 저장 완료. datasetUid={}", LOG_PREFIX, datasetUid);

      // ===== 3. 상태 변경 (업로드중) =====
      datasetCoreService.updateDatasetUploadStatus(datasetUid, LearnDataRegister.UPLOADING);
      log.info("{} 상태 변경 → UPLOADING. datasetUid={}", LOG_PREFIX, datasetUid);

      // ===== 4. 데이터 등록 =====
      long insertStart = System.currentTimeMillis();

      // 납품 데이터 obj 등록
      datasetService.insertDeliveriesDataset(req, datasetUid);

      log.info(
          "{} 데이터 등록 완료. datasetUid={}, 소요시간={} ms",
          LOG_PREFIX,
          datasetUid,
          System.currentTimeMillis() - insertStart);

      // ===== 5. 상태 변경 (완료) =====
      datasetCoreService.updateDatasetUploadStatus(datasetUid, LearnDataRegister.COMPLETED);
      log.info("{} 상태 변경 → COMPLETED. datasetUid={}", LOG_PREFIX, datasetUid);

      log.info(
          "{} 업로드 완료. 총 소요시간={} ms ==========", LOG_PREFIX, System.currentTimeMillis() - startTime);

    } catch (Exception e) {

      log.error(
          "{} 업로드 실패. datasetUid={}, filePath={}", LOG_PREFIX, datasetUid, req.getFilePath(), e);

      if (datasetUid != null) {
        try {
          // ===== 실패 처리 =====
          datasetCoreService.updateDatasetUploadStatus(datasetUid, LearnDataRegister.UPLOAD_FAILED);
          log.error("{} 상태 변경 → 업로드 실패. datasetUid={}", LOG_PREFIX, datasetUid);

          // 실패 시 데이터 정리
          datasetCoreService.deleteAllDatasetObj(datasetUid);
          log.error("{} 데이터 정리 완료. datasetUid={}", LOG_PREFIX, datasetUid);

        } catch (Exception ex) {
          log.error("{} 실패 후 정리 작업 중 오류. datasetUid={}", LOG_PREFIX, datasetUid, ex);
        }
      }
    }
  }

  private Long getDirectorySize(String filePath) {
    Path selectedPath = Paths.get(filePath);

    try (Stream<Path> paths = Files.walk(selectedPath)) {
      return paths
          .filter(Files::isRegularFile)
          .mapToLong(
              path -> {
                try {
                  return Files.size(path);
                } catch (IOException e) {
                  return 0L;
                }
              })
          .sum();
    } catch (IOException e) {
      return 0L;
    }
  }
}
