package com.kamco.cd.kamcoback.common.utils;

import static java.lang.String.CASE_INSENSITIVE_ORDER;

import io.swagger.v3.oas.annotations.media.Schema;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
import org.apache.commons.io.FilenameUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.gce.geotiff.GeoTiffReader;

public class FIleChecker {

  public static boolean isValidFile(String pathStr) {

    Path path = Paths.get(pathStr);

    if (!Files.exists(path)) {
      return false;
    }

    if (!Files.isRegularFile(path)) {
      return false;
    }

    if (!Files.isReadable(path)) {
      return false;
    }

    try {
      if (Files.size(path) <= 0) {
        return false;
      }
    } catch (IOException e) {
      return false;
    }

    return true;
  }

  public static boolean verifyFileIntegrity(Path path, String expectedHash)
      throws IOException, NoSuchAlgorithmException {

    // 1. 알고리즘 선택 (SHA-256 권장, MD5는 보안상 비추천)
    MessageDigest digest = MessageDigest.getInstance("SHA-256");

    try (InputStream fis = Files.newInputStream(path)) {
      byte[] buffer = new byte[8192]; // 8KB 버퍼
      int bytesRead;
      while ((bytesRead = fis.read(buffer)) != -1) {
        digest.update(buffer, 0, bytesRead);
      }
    }

    // 3. 계산된 바이트 배열을 16진수 문자열로 변환
    StringBuilder sb = new StringBuilder();
    for (byte b : digest.digest()) {
      sb.append(String.format("%02x", b));
    }
    String actualHash = sb.toString();

    return actualHash.equalsIgnoreCase(expectedHash);
  }

  public static boolean checkTfw(String filePath) {

    File file = new File(filePath);

    if (!file.exists()) {
      return false;
    }

    // 1. 파일의 모든 라인을 읽어옴
    List<Double> lines = new ArrayList<>();
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
      String line;
      while ((line = br.readLine()) != null) {
        if (!line.trim().isEmpty()) { // 빈 줄 제외
          lines.add(Double.parseDouble(line.trim()));
        }
      }
    } catch (IOException ignored) {
      return false;
    }

    // 2. 6줄이 맞는지 확인
    if (lines.size() < 6) {
      // System.out.println("유효하지 않은 TFW 파일입니다. (데이터 부족)");
      return false;
    }

    return true;
  }

  public static boolean checkGeoTiff(String filePath) {

    File file = new File(filePath);

    if (!file.exists()) {
      return false;
    }

    GeoTiffReader reader = null;
    try {
      // 1. 파일 포맷 및 헤더 확인
      reader = new GeoTiffReader(file);

      // 2. 실제 데이터 로딩 (여기서 파일 깨짐 여부 확인됨)
      // null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름
      GridCoverage2D coverage = reader.read(null);

      if (coverage == null) return false;

      // 3. GIS 필수 정보(좌표계)가 있는지 확인
      // if (coverage.getCoordinateReferenceSystem() == null) {
      // GeoTIFF가 아니라 일반 TIFF일 수도 있음(이미지는 정상이지만, 좌표계(CRS) 정보가 없습니다.)
      // }

      return true;

    } catch (Exception e) {
      System.err.println("손상된 TIF 파일입니다: " + e.getMessage());
      return false;
    } finally {
      // 리소스 해제 (필수)
      if (reader != null) reader.dispose();
    }
  }

  public static Boolean cmmndGdalInfo(String filePath) {

    File file = new File(filePath);

    if (!file.exists()) {
      System.err.println("파일이 존재하지 않습니다: " + filePath);
      return false;
    }

    boolean hasDriver = false;

    // 운영체제 감지
    String osName = System.getProperty("os.name").toLowerCase();
    boolean isWindows = osName.contains("win");
    boolean isMac = osName.contains("mac");
    boolean isUnix = osName.contains("nix") || osName.contains("nux") || osName.contains("aix");

    // gdalinfo 경로 찾기 (일반적인 설치 경로 우선 확인)
    String gdalinfoPath = findGdalinfoPath();
    if (gdalinfoPath == null) {
      System.err.println("gdalinfo 명령어를 찾을 수 없습니다. GDAL이 설치되어 있는지 확인하세요.");
      System.err.println("macOS: brew install gdal");
      System.err.println("Ubuntu/Debian: sudo apt-get install gdal-bin");
      System.err.println("CentOS/RHEL: sudo yum install gdal");
      return false;
    }

    List<String> command = new ArrayList<>();

    if (isWindows) {
      // 윈도우용
      command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행
      command.add("/c"); // 명령어를 수행하고 종료한다는 옵션
      command.add("gdalinfo");
      command.add(filePath);
      command.add("|");
      command.add("findstr");
      command.add("/i");
      command.add("Geo");
    } else if (isMac || isUnix) {
      // 리눅스, 맥용
      command.add("sh");
      command.add("-c");
      command.add(gdalinfoPath + " \"" + filePath + "\" | grep -i Geo");
    } else {
      System.err.println("지원하지 않는 운영체제: " + osName);
      return false;
    }

    ProcessBuilder processBuilder = new ProcessBuilder(command);
    processBuilder.redirectErrorStream(true);

    Process process = null;
    BufferedReader reader = null;
    try {
      System.out.println("gdalinfo 명령어 실행 시작: " + filePath);
      process = processBuilder.start();

      reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

      String line;
      while ((line = reader.readLine()) != null) {
        System.out.println("gdalinfo 출력: " + line);
        if (line.contains("Driver: GTiff/GeoTIFF")) {
          hasDriver = true;
          break;
        }
      }

      int exitCode = process.waitFor();
      System.out.println("gdalinfo 종료 코드: " + exitCode);

      // 프로세스가 정상 종료되지 않았고 Driver를 찾지 못한 경우
      if (exitCode != 0 && !hasDriver) {
        System.err.println("gdalinfo 명령 실행 실패. Exit code: " + exitCode);
      }

    } catch (IOException e) {
      System.err.println("gdalinfo 실행 중 I/O 오류 발생: " + e.getMessage());
      e.printStackTrace();
      return false;
    } catch (InterruptedException e) {
      System.err.println("gdalinfo 실행 중 인터럽트 발생: " + e.getMessage());
      Thread.currentThread().interrupt();
      return false;
    } catch (Exception e) {
      System.err.println("gdalinfo 실행 중 예상치 못한 오류 발생: " + e.getMessage());
      e.printStackTrace();
      return false;
    } finally {
      // 리소스 정리
      if (reader != null) {
        try {
          reader.close();
        } catch (IOException e) {
          System.err.println("BufferedReader 종료 중 오류: " + e.getMessage());
        }
      }
      if (process != null) {
        process.destroy();
      }
    }

    return hasDriver;
  }

  @Schema(name = "File Basic", description = "파일 기본 정보")
  @Getter
  public static class Basic {

    private final String fileNm;
    private final String parentFolderNm;
    private final String parentPath;
    private final String fullPath;
    private final String extension;
    private final long fileSize;
    private final String lastModified;

    public Basic(
        String fileNm,
        String parentFolderNm,
        String parentPath,
        String fullPath,
        String extension,
        long fileSize,
        String lastModified) {
      this.fileNm = fileNm;
      this.parentFolderNm = parentFolderNm;
      this.parentPath = parentPath;
      this.fullPath = fullPath;
      this.extension = extension;
      this.fileSize = fileSize;
      this.lastModified = lastModified;
    }
  }

  public static List<Basic> getFilesFromAllDepth(
      String dir,
      String targetFileNm,
      String extension,
      int maxDepth,
      String sortType,
      int startPos,
      int endPos) {

    Path startPath = Paths.get(dir);
    String dirPath = dir;

    int limit = endPos - startPos + 1;

    Set<String> targetExtensions = createExtensionSet(extension);

    List<Basic> fileList = new ArrayList<>();
    SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // int fileTotCnt = 0;
    // long fileTotSize = 0;

    Predicate<Path> isTarget =
        p -> {
          if (targetFileNm == null
              || targetFileNm.trim().isEmpty()
              || targetFileNm.trim().equals("*")) {
            return true; // 전체 파일 허용
          }
          return p.getFileName().toString().contains(targetFileNm);
        };

    try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {

      fileList =
          stream
              .filter(Files::isRegularFile)
              .filter(
                  p ->
                      extension == null
                          || extension.equals("")
                          || extension.equals("*")
                          || targetExtensions.contains(extractExtension(p)))
              .sorted(getFileComparator(sortType))
              .filter(isTarget)
              .skip(startPos)
              .limit(limit)
              .map(
                  path -> {
                    // int depth = path.getNameCount();

                    String fileNm = path.getFileName().toString();
                    String ext = FilenameUtils.getExtension(fileNm);
                    String parentFolderNm = path.getParent().getFileName().toString();
                    String parentPath = path.getParent().toString();
                    String fullPath = path.toAbsolutePath().toString();

                    File file = new File(fullPath);
                    long fileSize = file.length();
                    String lastModified = dttmFormat.format(new Date(file.lastModified()));

                    return new Basic(
                        fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
                  })
              .collect(Collectors.toList());

    } catch (IOException e) {
      System.err.println("파일 I/O 오류 발생: " + e.getMessage());
    }

    return fileList;
  }

  public static Long getFileTotSize(List<FIleChecker.Basic> files) {

    Long fileTotSize = 0L;
    if (files != null || files.size() > 0) {
      fileTotSize = files.stream().mapToLong(FIleChecker.Basic::getFileSize).sum();
    }

    return fileTotSize;
  }

  public static Set<String> createExtensionSet(String extensionString) {
    if (extensionString == null || extensionString.isBlank()) {
      return Set.of();
    }

    // "java, class" -> ["java", " class"] -> [".java", ".class"]
    return Arrays.stream(extensionString.split(","))
        .map(ext -> ext.trim())
        .filter(ext -> !ext.isEmpty())
        .map(ext -> "." + ext.toLowerCase())
        .collect(Collectors.toSet());
  }

  public static String extractExtension(Path path) {
    String filename = path.getFileName().toString();
    int lastDotIndex = filename.lastIndexOf('.');

    // 확장자가 없거나 파일명이 .으로 끝나는 경우
    if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
      return ""; // 빈 문자열 반환
    }

    // 확장자 추출 및 소문자 변환
    return filename.substring(lastDotIndex).toLowerCase();
  }

  public static Comparator<Path> getFileComparator(String sortType) {

    // 파일 이름 비교 기본 Comparator (대소문자 무시)
    Comparator<Path> nameComparator =
        Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER);

    Comparator<Path> dateComparator =
        Comparator.comparing(
            path -> {
              try {
                return Files.getLastModifiedTime(path);
              } catch (IOException e) {
                return FileTime.fromMillis(0);
              }
            });

    if ("name desc".equalsIgnoreCase(sortType)) {
      return nameComparator.reversed();
    } else if ("date".equalsIgnoreCase(sortType)) {
      return dateComparator;
    } else if ("date desc".equalsIgnoreCase(sortType)) {
      return dateComparator.reversed();
    } else {
      return nameComparator;
    }
  }

  /**
   * gdalinfo 실행 파일 경로를 찾습니다.
   *
   * @return gdalinfo 경로 (찾지 못하면 null)
   */
  private static String findGdalinfoPath() {
    // 일반적인 설치 경로 확인
    String[] possiblePaths = {
      "/usr/local/bin/gdalinfo", // Homebrew (macOS)
      "/opt/homebrew/bin/gdalinfo", // Homebrew (Apple Silicon macOS)
      "/usr/bin/gdalinfo", // Linux
      "gdalinfo" // PATH에 있는 경우
    };

    for (String path : possiblePaths) {
      if (isCommandAvailable(path)) {
        return path;
      }
    }

    return null;
  }

  /**
   * 명령어가 사용 가능한지 확인합니다.
   *
   * @param command 명령어 경로
   * @return 사용 가능 여부
   */
  private static boolean isCommandAvailable(String command) {
    try {
      ProcessBuilder pb = new ProcessBuilder(command, "--version");
      pb.redirectErrorStream(true);
      Process process = pb.start();

      // 프로세스 완료 대기 (최대 5초)
      boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);

      if (!finished) {
        process.destroy();
        return false;
      }

      // 종료 코드가 0이면 정상 (일부 명령어는 --version에서 다른 코드 반환할 수 있음)
      return process.exitValue() == 0 || process.exitValue() == 1;
    } catch (Exception e) {
      return false;
    }
  }
}
