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

import com.kamco.cd.training.postgres.core.ModelTrainMetricsJobCoreService;
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ResponsePathDto;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class ModelTrainMetricsJobService {

  private final ModelTrainMetricsJobCoreService modelTrainMetricsJobCoreService;

  @Value("${spring.profiles.active}")
  private String profile;

  // 학습 결과가 저장될 호스트 디렉토리
  @Value("${train.docker.response_dir}")
  private String responseDir;

  /** 결과 csv 파일 정보 등록 */
  public void findTrainValidMetricCsvFiles() {

    List<ResponsePathDto> modelIds =
        modelTrainMetricsJobCoreService.getTrainMetricSaveNotYetModelIds();

    if (modelIds.isEmpty()) {
      return;
    }

    for (ResponsePathDto modelInfo : modelIds) {

      createFile(modelInfo);
    }
  }

  /** 단건 결과 csv 파일 정보 등록 */
  public void trainValidMetricCsvFile(Long modelId) {

    ResponsePathDto modelInfo =
        modelTrainMetricsJobCoreService.getTrainMetricSaveNotYetModelId(modelId);

    if (modelInfo == null) {
      return;
    }
    createFile(modelInfo);
  }

  /**
   * 학습 csv 파일 db 등록
   *
   * @param modelInfo
   */
  private void createFile(ResponsePathDto modelInfo) {
    String trainPath = responseDir + "/" + modelInfo.getUuid() + "/metrics/train.csv";
    try (BufferedReader reader =
        Files.newBufferedReader(Paths.get(trainPath), StandardCharsets.UTF_8); ) {

      CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);

      List<Object[]> batchArgs = new ArrayList<>();

      for (CSVRecord record : parser) {

        int epoch = Integer.parseInt(record.get("Epoch"));
        long iteration = Long.parseLong(record.get("Iteration"));
        double Loss = Double.parseDouble(record.get("Loss"));
        double LR = Double.parseDouble(record.get("LR"));
        float time = Float.parseFloat(record.get("Time"));

        batchArgs.add(new Object[] {modelInfo.getModelId(), epoch, iteration, Loss, LR, time});
      }

      modelTrainMetricsJobCoreService.insertModelMetricsTrain(batchArgs);

    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    String validationPath = responseDir + "/" + modelInfo.getUuid() + "/metrics/val.csv";
    try (BufferedReader reader =
        Files.newBufferedReader(Paths.get(validationPath), StandardCharsets.UTF_8); ) {

      CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);

      List<Object[]> batchArgs = new ArrayList<>();

      for (CSVRecord record : parser) {

        int epoch = Integer.parseInt(record.get("Epoch"));

        Float aAcc = parseFloatSafe(record.get("aAcc"));
        Float mFscore = parseFloatSafe(record.get("mFscore"));
        Float mPrecision = parseFloatSafe(record.get("mPrecision"));
        Float mRecall = parseFloatSafe(record.get("mRecall"));
        Float mIoU = parseFloatSafe(record.get("mIoU"));
        Float mAcc = parseFloatSafe(record.get("mAcc"));

        Float changed_fscore = parseFloatSafe(record.get("changed_fscore"));
        Float changed_precision = parseFloatSafe(record.get("changed_precision"));
        Float changed_recall = parseFloatSafe(record.get("changed_recall"));

        Float unchanged_fscore = parseFloatSafe(record.get("unchanged_fscore"));
        Float unchanged_precision = parseFloatSafe(record.get("unchanged_precision"));
        Float unchanged_recall = parseFloatSafe(record.get("unchanged_recall"));

        batchArgs.add(
            new Object[] {
              modelInfo.getModelId(),
              epoch,
              aAcc,
              mFscore,
              mPrecision,
              mRecall,
              mIoU,
              mAcc,
              changed_fscore,
              changed_precision,
              changed_recall,
              unchanged_fscore,
              unchanged_precision,
              unchanged_recall
            });
      }

      modelTrainMetricsJobCoreService.insertModelMetricsValidation(batchArgs);

    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    Path responsePath = Paths.get(responseDir + "/" + modelInfo.getUuid());
    Integer epoch = null;
    boolean exists;
    Pattern pattern = Pattern.compile("best_changed_fscore_epoch_(\\d+)\\.pth");

    try (Stream<Path> s = Files.list(responsePath)) {
      epoch =
          s.filter(Files::isRegularFile)
              .map(
                  p -> {
                    Matcher matcher = pattern.matcher(p.getFileName().toString());
                    if (matcher.matches()) {
                      return Integer.parseInt(matcher.group(1)); // ← 숫자 부분 추출
                    }
                    return null;
                  })
              .filter(Objects::nonNull)
              .findFirst()
              .orElse(null);

    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    // best_changed_fscore_epoch_숫자.pth -> 숫자 값 가지고 와서 베스트 에폭에 업데이트 하기
    modelTrainMetricsJobCoreService.updateModelSelectedBestEpoch(modelInfo.getModelId(), epoch);

    modelTrainMetricsJobCoreService.updateModelMetricsTrainSaveYn(modelInfo.getModelId(), "step1");
  }

  private Float parseFloatSafe(String value) {
    try {
      if (value == null) return null;

      value = value.trim();

      if (value.isEmpty()) return null;

      if (value.equalsIgnoreCase("nan")) return null;

      float f = Float.parseFloat(value);

      return Float.isNaN(f) ? null : f;

    } catch (Exception e) {
      return null;
    }
  }
}
