package com.kamco.cd.kamcoback.geojson.service;

import com.kamco.cd.kamcoback.geojson.config.GeoJsonMonitorConfig;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataGeomRepository;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataRepository;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/** GeoJSON 파일 모니터링 서비스 지정된 폴더를 주기적으로 모니터링하여 압축파일을 자동으로 처리합니다. */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeoJsonFileMonitorService {

  private final GeoJsonMonitorConfig config;
  private final ArchiveExtractorService archiveExtractorService;
  private final GeoJsonDataService geoJsonDataService;
  private final GeometryConversionService geometryConversionService;
  private final MapSheetLearnDataRepository learnDataRepository;
  private final MapSheetLearnDataGeomRepository geomRepository;

  /** 애플리케이션 시작 시 필요한 디렉토리들을 미리 생성 */
  @PostConstruct
  public void initializeDirectories() {
    try {
      log.info("GeoJSON 모니터링 시스템 초기화 중...");
      log.info(
          "설정된 경로 - Watch: {}, Processed: {}, Error: {}, Temp: {}",
          config.getWatchDirectory(),
          config.getProcessedDirectory(),
          config.getErrorDirectory(),
          config.getTempDirectory());

      ensureDirectoriesExist();
      log.info("GeoJSON 모니터링 시스템 초기화 완료");
    } catch (Exception e) {
      log.warn("GeoJSON 모니터링 시스템 초기화 실패 - 스케줄러 실행 시 재시도됩니다", e);
      // 초기화 실패해도 애플리케이션은 시작되도록 함 (RuntimeException 던지지 않음)
    }
  }

  /** 스케줄러를 통한 파일 모니터링 설정된 cron 표현식에 따라 주기적으로 실행 */
  //    @Scheduled(cron = "#{@geoJsonMonitorConfig.cronExpression}")
  public void monitorFiles() {
    log.debug("파일 모니터링 시작");

    try {
      // 모니터링 폴더 존재 확인 및 생성
      ensureDirectoriesExist();

      // 압축파일 검색 및 처리
      processArchiveFiles();

      // 미처리된 Geometry 변환 작업 수행
      processUnprocessedGeometryData();

    } catch (RuntimeException e) {
      log.error("파일 모니터링 중 치명적 오류 발생 - 이번 주기 건너뜀", e);
    } catch (Exception e) {
      log.error("파일 모니터링 중 오류 발생", e);
    }

    log.debug("파일 모니터링 완료");
  }

  /** 필요한 디렉토리들이 존재하는지 확인하고 생성 */
  private void ensureDirectoriesExist() {
    boolean hasError = false;
    try {
      createDirectoryIfNotExists(config.getWatchDirectory());
    } catch (IOException e) {
      log.error("Watch 디렉토리 생성 실패: {} - {}", config.getWatchDirectory(), e.getMessage());
      hasError = true;
    }

    try {
      createDirectoryIfNotExists(config.getProcessedDirectory());
    } catch (IOException e) {
      log.error("Processed 디렉토리 생성 실패: {} - {}", config.getProcessedDirectory(), e.getMessage());
      hasError = true;
    }

    try {
      createDirectoryIfNotExists(config.getErrorDirectory());
    } catch (IOException e) {
      log.error("Error 디렉토리 생성 실패: {} - {}", config.getErrorDirectory(), e.getMessage());
      hasError = true;
    }

    try {
      createDirectoryIfNotExists(config.getTempDirectory());
    } catch (IOException e) {
      log.error("Temp 디렉토리 생성 실패: {} - {}", config.getTempDirectory(), e.getMessage());
      hasError = true;
    }

    if (hasError) {
      log.warn("일부 디렉토리 생성에 실패했습니다. 해당 기능은 제한될 수 있습니다.");
      log.info("수동으로 다음 디렉토리들을 생성해주세요:");
      log.info("  - {}", config.getWatchDirectory());
      log.info("  - {}", config.getProcessedDirectory());
      log.info("  - {}", config.getErrorDirectory());
      log.info("  - {}", config.getTempDirectory());
    } else {
      log.info("모든 필요한 디렉토리가 준비되었습니다.");
    }
  }

  /** 디렉토리가 존재하지 않으면 생성 */
  private void createDirectoryIfNotExists(String directory) throws IOException {
    if (directory == null || directory.trim().isEmpty()) {
      throw new IllegalArgumentException("디렉토리 경로가 비어있습니다.");
    }

    Path path = Paths.get(directory);

    if (!Files.exists(path)) {
      try {
        Files.createDirectories(path);
        log.info("디렉토리 생성 완료: {}", directory);

        // 디렉토리 권한 설정 (Unix/Linux 환경에서)
        try {
          if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
            // rwxrwxr-x 권한 설정
            java.nio.file.attribute.PosixFilePermissions.asFileAttribute(
                java.nio.file.attribute.PosixFilePermissions.fromString("rwxrwxr-x"));
          }
        } catch (Exception permissionException) {
          log.debug("권한 설정 실패 (무시됨): {}", permissionException.getMessage());
        }

      } catch (IOException e) {
        log.error("디렉토리 생성 실패: {} - {}", directory, e.getMessage());
        throw new IOException("디렉토리를 생성할 수 없습니다: " + directory, e);
      }
    } else if (!Files.isDirectory(path)) {
      throw new IOException("지정된 경로가 디렉토리가 아닙니다: " + directory);
    } else if (!Files.isWritable(path)) {
      log.warn("디렉토리에 쓰기 권한이 없습니다: {}", directory);
    } else {
      log.debug("디렉토리가 이미 존재합니다: {}", directory);
    }
  }

  /** 모니터링 폴더에서 압축파일들을 찾아서 처리 */
  private void processArchiveFiles() {
    Path watchDir = Paths.get(config.getWatchDirectory());

    // 디렉토리 존재 확인
    if (!Files.exists(watchDir)) {
      log.debug("Watch 디렉토리가 존재하지 않습니다: {}", watchDir);
      return;
    }

    if (!Files.isDirectory(watchDir)) {
      log.warn("Watch 경로가 디렉토리가 아닙니다: {}", watchDir);
      return;
    }

    if (!Files.isReadable(watchDir)) {
      log.warn("Watch 디렉토리에 읽기 권한이 없습니다: {}", watchDir);
      return;
    }

    try (Stream<Path> files = Files.list(watchDir)) {
      files
          .filter(Files::isRegularFile)
          .filter(archiveExtractorService::isSupportedArchive)
          .filter(archiveExtractorService::isFileSizeValid)
          .forEach(this::processArchiveFile);

    } catch (IOException e) {
      log.error("파일 목록 조회 실패: {}", watchDir, e);
    }
  }

  /** 개별 압축파일 처리 */
  private void processArchiveFile(Path archiveFile) {
    String fileName = archiveFile.getFileName().toString();
    log.info("압축파일 처리 시작: {}", fileName);

    try {
      // 1. 압축파일에서 GeoJSON 파일들 추출
      Map<String, String> geoJsonContents =
          archiveExtractorService.extractGeoJsonFiles(archiveFile);

      if (geoJsonContents.isEmpty()) {
        log.warn("압축파일에서 GeoJSON 파일을 찾을 수 없습니다: {}", fileName);
        moveFileToError(archiveFile, "GeoJSON 파일 없음");
        return;
      }

      // 2. 처리 가능한 파일 수인지 확인
      if (!geoJsonDataService.isProcessable(geoJsonContents)) {
        log.warn("처리할 수 없는 파일입니다: {}", fileName);
        moveFileToError(archiveFile, "처리 불가능한 파일");
        return;
      }

      // 3. GeoJSON 데이터를 데이터베이스에 저장
      List<Long> savedLearnDataIds =
          geoJsonDataService.processGeoJsonFiles(geoJsonContents, fileName);

      if (savedLearnDataIds.isEmpty()) {
        log.warn("저장된 학습 데이터가 없습니다: {}", fileName);
        moveFileToError(archiveFile, "데이터 저장 실패");
        return;
      }

      // 4. Geometry 데이터로 변환
      List<Long> geometryIds = geometryConversionService.convertToGeometryData(savedLearnDataIds);

      // 5. 처리 완료된 파일을 처리된 폴더로 이동
      moveFileToProcessed(archiveFile);

      log.info(
          "압축파일 처리 완료: {} (학습 데이터: {}개, Geometry: {}개)",
          fileName,
          savedLearnDataIds.size(),
          geometryIds.size());

    } catch (Exception e) {
      log.error("압축파일 처리 실패: {}", fileName, e);
      try {
        moveFileToError(archiveFile, "처리 중 오류 발생: " + e.getMessage());
      } catch (IOException moveError) {
        log.error("오류 파일 이동 실패: {}", fileName, moveError);
      }
    }
  }

  /** 미처리된 Geometry 변환 작업 수행 */
  private void processUnprocessedGeometryData() {
    try {
      List<Long> processedIds = geometryConversionService.processUnprocessedLearnData();
      if (!processedIds.isEmpty()) {
        log.info("미처리 Geometry 변환 완료: {}개", processedIds.size());
      }
    } catch (Exception e) {
      log.error("미처리 Geometry 변환 작업 실패", e);
    }
  }

  /** 처리 완료된 파일을 processed 폴더로 이동 */
  private void moveFileToProcessed(Path sourceFile) throws IOException {
    String fileName = sourceFile.getFileName().toString();
    String timestampedFileName = addTimestamp(fileName);
    Path targetPath = Paths.get(config.getProcessedDirectory(), timestampedFileName);

    Files.move(sourceFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
    log.info("파일을 처리된 폴더로 이동: {} -> {}", fileName, timestampedFileName);
  }

  /** 오류가 발생한 파일을 error 폴더로 이동 */
  private void moveFileToError(Path sourceFile, String errorReason) throws IOException {
    String fileName = sourceFile.getFileName().toString();
    String errorFileName = addTimestamp(fileName) + ".error";
    Path targetPath = Paths.get(config.getErrorDirectory(), errorFileName);

    Files.move(sourceFile, targetPath, StandardCopyOption.REPLACE_EXISTING);

    // 오류 정보를 별도 파일로 저장
    String errorInfoFileName = errorFileName + ".info";
    Path errorInfoPath = Paths.get(config.getErrorDirectory(), errorInfoFileName);
    String errorInfo =
        String.format(
            "파일: %s%n오류 시간: %s%n오류 원인: %s%n", fileName, java.time.Instant.now(), errorReason);
    Files.write(errorInfoPath, errorInfo.getBytes());

    log.warn("파일을 오류 폴더로 이동: {} (원인: {})", fileName, errorReason);
  }

  /** 파일명에 타임스탬프 추가 */
  private String addTimestamp(String fileName) {
    int lastDotIndex = fileName.lastIndexOf('.');
    String name = (lastDotIndex > 0) ? fileName.substring(0, lastDotIndex) : fileName;
    String extension = (lastDotIndex > 0) ? fileName.substring(lastDotIndex) : "";

    return String.format("%s_%d%s", name, System.currentTimeMillis(), extension);
  }

  /** 수동으로 특정 파일 처리 (테스트/관리 목적) */
  public void processFileManually(String filePath) {
    Path archiveFile = Paths.get(filePath);

    if (!Files.exists(archiveFile)) {
      log.error("파일이 존재하지 않습니다: {}", filePath);
      return;
    }

    if (!archiveExtractorService.isSupportedArchive(archiveFile)) {
      log.error("지원하지 않는 압축파일 형식입니다: {}", filePath);
      return;
    }

    log.info("수동 파일 처리 시작: {}", filePath);
    processArchiveFile(archiveFile);
  }

  /** 디렉토리 초기화를 수동으로 실행 (API에서 호출 가능) */
  public void initializeDirectoriesManually() {
    log.info("디렉토리 수동 초기화 시작");
    try {
      ensureDirectoriesExist();
      log.info("디렉토리 수동 초기화 완료");
    } catch (Exception e) {
      log.error("디렉토리 수동 초기화 실패", e);
      throw new RuntimeException("디렉토리 초기화 실패", e);
    }
  }

  /** 모니터링 상태 정보 반환 */
  public Map<String, Object> getMonitorStatus() {
    return Map.of(
        "watchDirectory", config.getWatchDirectory(),
        "processedDirectory", config.getProcessedDirectory(),
        "errorDirectory", config.getErrorDirectory(),
        "cronExpression", config.getCronExpression(),
        "supportedExtensions", config.getSupportedExtensions(),
        "maxFileSize", config.getMaxFileSize(),
        "maxFileSizeMB", config.getMaxFileSize() / 1024 / 1024);
  }

  /** 시스템 통계 정보 조회 */
  public Map<String, Object> getSystemStats() {
    Map<String, Object> stats = new HashMap<>();

    try {
      // 데이터베이스 통계
      long totalLearnData = learnDataRepository.count();
      long totalGeomData = geomRepository.count();
      long pendingAnalysis = learnDataRepository.countByAnalState("PENDING");

      stats.put(
          "database",
          Map.of(
              "totalLearnData", totalLearnData,
              "totalGeomData", totalGeomData,
              "pendingAnalysis", pendingAnalysis));

      // 파일 시스템 통계
      stats.put("fileSystem", getFileSystemStats());

      // 모니터링 설정
      stats.put(
          "monitoring",
          Map.of(
              "isActive", true,
              "cronExpression", "0/30 * * * * *",
              "watchDirectory", config.getWatchDirectory(),
              "processedDirectory", config.getProcessedDirectory(),
              "errorDirectory", config.getErrorDirectory()));

    } catch (Exception e) {
      log.error("통계 정보 조회 실패", e);
      stats.put("error", e.getMessage());
    }

    return stats;
  }

  /** 파일 시스템 통계 조회 */
  private Map<String, Object> getFileSystemStats() {
    Map<String, Object> fileStats = new HashMap<>();

    try {
      // 각 디렉토리의 파일 수 계산
      Path watchDir = Paths.get(config.getWatchDirectory());
      Path processedDir = Paths.get(config.getProcessedDirectory());
      Path errorDir = Paths.get(config.getErrorDirectory());

      fileStats.put("watchDirectoryCount", countFilesInDirectory(watchDir));
      fileStats.put("processedDirectoryCount", countFilesInDirectory(processedDir));
      fileStats.put("errorDirectoryCount", countFilesInDirectory(errorDir));

    } catch (Exception e) {
      log.warn("파일 시스템 통계 조회 실패: {}", e.getMessage());
      fileStats.put("error", e.getMessage());
    }

    return fileStats;
  }

  /** 디렉토리 내 파일 개수 계산 */
  private long countFilesInDirectory(Path directory) {
    if (!Files.exists(directory) || !Files.isDirectory(directory)) {
      return 0;
    }

    try (Stream<Path> files = Files.list(directory)) {
      return files.filter(Files::isRegularFile).count();
    } catch (IOException e) {
      log.warn("디렉토리 파일 계산 실패: {}", directory, e);
      return 0;
    }
  }
}
