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

import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.postgres.core.ModelTrainMngCoreService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/** 학습실행 파일 하드링크 */
@Service
@Log4j2
@RequiredArgsConstructor
public class DataSetCountersService {
  private final ModelTrainMngCoreService modelTrainMngCoreService;

  @Value("${train.docker.request_dir}")
  private String requestDir;

  @Value("${train.docker.symbolic_link_dir}")
  private String symbolicDir;

  public String getCount(Long modelId) {
    ModelTrainMngDto.Basic basic = modelTrainMngCoreService.findModelById(modelId);
    List<Long> datasetIds = modelTrainMngCoreService.findModelDatasetMapp(modelId);
    List<String> uids = modelTrainMngCoreService.findDatasetUid(datasetIds);

    StringBuilder allLogs = new StringBuilder();

    try {
      // request 폴더
      for (String uid : uids) {
        Path path = Path.of(requestDir, uid);
        DatasetCounters counters = countTmpAfterBuild(path);
        allLogs.append(counters.prints(uid, "REQUEST")).append(System.lineSeparator());
      }

      // tmp
      Path tmpPath = Path.of(symbolicDir, basic.getRequestPath());

      // 차이나는거
      diffMergedRequestsVsTmp(uids, tmpPath);

      DatasetCounters counters2 = countTmpAfterBuild(tmpPath);
      allLogs
          .append(counters2.prints(basic.getRequestPath(), "TMP"))
          .append(System.lineSeparator());
    } catch (IOException e) {
      log.error(e.getMessage());
    }

    return allLogs.toString();
  }

  private int countTif(Path dir) throws IOException {
    if (!Files.isDirectory(dir)) return 0;

    try (var stream = Files.walk(dir)) {
      return (int)
          stream.filter(Files::isRegularFile).filter(p -> p.toString().endsWith(".tif")).count();
    }

    /*
    대소문자 및 geojson 필요시
    * try (var stream = Files.walk(dir)) {
        return (int)
            stream
                .filter(Files::isRegularFile)
                .filter(p -> {
                    String name = p.getFileName().toString().toLowerCase();
                    return name.endsWith(".tif") || name.endsWith(".geojson");
                })
                .count();
    }
    * */
  }

  public DatasetCounters countTmpAfterBuild(Path path) throws IOException {

    // input1
    int in1Train = countTif(path.resolve("train/input1"));
    int in1Val = countTif(path.resolve("val/input1"));
    int in1Test = countTif(path.resolve("test/input1"));

    // input2
    int in2Train = countTif(path.resolve("train/input2"));
    int in2Val = countTif(path.resolve("val/input2"));
    int in2Test = countTif(path.resolve("test/input2"));

    List<DatasetCounter> input1List = new ArrayList<>();
    List<DatasetCounter> input2List = new ArrayList<>();

    input1List.add(new DatasetCounter(in1Train, in1Test, in1Val));
    input2List.add(new DatasetCounter(in2Train, in2Test, in2Val));

    return new DatasetCounters(input1List, input2List);
  }

  @Getter
  public static class DatasetCounter {
    private int inputTrain = 0;
    private int inputTest = 0;
    private int inputVal = 0;

    public DatasetCounter(int inputTrain, int inputTest, int inputVal) {
      this.inputTrain = inputTrain;
      this.inputTest = inputTest;
      this.inputVal = inputVal;
    }
  }

  @Getter
  public static class DatasetCounters {
    private List<DatasetCounter> input1 = new ArrayList<>();
    private List<DatasetCounter> input2 = new ArrayList<>();

    public DatasetCounters(List<DatasetCounter> input1, List<DatasetCounter> input2) {
      this.input1 = input1;
      this.input2 = input2;
    }

    public String prints(String uuid, String type) {
      int train = 0, test = 0, val = 0;
      int train2 = 0, test2 = 0, val2 = 0;

      for (DatasetCounter datasetCounter : input1) {
        train += datasetCounter.inputTrain;
        test += datasetCounter.inputTest;
        val += datasetCounter.inputVal;
      }

      for (DatasetCounter datasetCounter : input2) {
        train2 += datasetCounter.inputTrain;
        test2 += datasetCounter.inputTest;
        val2 += datasetCounter.inputVal;
      }

      log.info("======== UUID FOLDER COUNT {} : {}", type, uuid);
      log.info("input 1 = train : {} | val : {} | test : {}  ", train, val, test);
      log.info("input 2 = train : {} | val : {} | test : {}  ", train2, val2, test2);
      log.info(
          "*total* = train : {} | val : {} | test : {}  ",
          train + train2,
          val + val2,
          test + test2);

      return String.format(
          "======== UUID FOLDER COUNT %s : %s%n"
              + "input 1 = train : %s | val : %s | test : %s%n"
              + "input 2 = train : %s | val : %s | test : %s%n"
              + "*total* = train : %s | val : %s | test : %s",
          type,
          uuid,
          train,
          val,
          test,
          train2,
          val2,
          test2,
          train + train2,
          val + val2,
          test + test2);
    }
  }

  private Set<String> listTifRelative(Path root) throws IOException {
    if (!Files.isDirectory(root)) return Set.of();

    try (var stream = Files.walk(root)) {
      return stream
          .filter(Files::isRegularFile)
          .filter(p -> p.getFileName().toString().toLowerCase().endsWith(".tif"))
          .map(p -> root.relativize(p).toString().replace("\\", "/"))
          .collect(Collectors.toSet());
    }
  }

  private Set<String> listTifFileNameOnly(Path root) throws IOException {
    if (!Files.isDirectory(root)) return Set.of();

    try (var stream = Files.walk(root)) {
      return stream
          .filter(Files::isRegularFile)
          .filter(p -> p.getFileName().toString().toLowerCase().endsWith(".tif"))
          .map(p -> p.getFileName().toString()) // 파일명만
          .collect(Collectors.toSet());
    }
  }

  public void diffMergedRequestsVsTmp(List<String> uids, Path tmpRoot) throws IOException {

    // 1) 요청 uids 전체를 합친 tif "파일명" 집합
    Set<String> reqAll = new HashSet<>();
    for (String uid : uids) {
      Path reqRoot = Path.of(requestDir, uid);

      // ★합본 tmp는 보통 폴더 구조가 바뀌므로 "상대경로" 비교보다 파일명 비교가 먼저 유용합니다.
      reqAll.addAll(listTifFileNameOnly(reqRoot));
    }

    // 2) tmp tif 파일명 집합
    Set<String> tmpAll = listTifFileNameOnly(tmpRoot);

    Set<String> missing = new HashSet<>(reqAll);
    missing.removeAll(tmpAll);

    Set<String> extra = new HashSet<>(tmpAll);
    extra.removeAll(reqAll);

    log.info("==== MERGED DIFF (filename-based) ====");
    log.info("request(all uids) tif = {}", reqAll.size());
    log.info("tmp tif               = {}", tmpAll.size());
    log.info("missing               = {}", missing.size());
    log.info("extra                 = {}", extra.size());

    log.info("[MISSING] total = {}", missing.size());
    missing.stream().sorted().limit(50).forEach(f -> log.warn("[MISSING] {}", f));
    log.info("[EXTRA] total = {}", extra.size());
    extra.stream().sorted().limit(50).forEach(f -> log.warn("[EXTRA] {}", f));
  }
}
