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

import com.kamco.cd.training.filemanager.dto.FileManagerDto;
import com.kamco.cd.training.postgres.entity.DatasetEntity;
import com.kamco.cd.training.postgres.entity.ModelMasterEntity;
import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository;
import com.kamco.cd.training.postgres.repository.model.ModelMngRepository;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class FileManagerService {

  private static final String BASE_DATA_PATH = "/data";
  private static final long MAX_PATH_LENGTH = 500;

  @Value("${train.docker.base_path}")
  private String basePath;

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

  @Value("${train.docker.response_dir}")
  private String responseDir;

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

  private final ModelMngRepository modelMngRepository;
  private final DatasetRepository datasetRepository;

  /**
   * 디렉토리 내 파일 목록 조회
   *
   * @param request 조회 요청 정보
   * @return 파일 목록 응답
   */
  public FileManagerDto.ListFilesRes listFiles(FileManagerDto.ListFilesReq request) {
    String targetPath =
        request.getDirectoryPath() != null ? request.getDirectoryPath() : BASE_DATA_PATH;

    boolean recursive = request.getRecursive() != null && request.getRecursive();

    // 경로 검증은 수행하되, 예외를 throw하지 않고 빈 데이터 반환
    try {
      validatePath(targetPath);
    } catch (IllegalArgumentException e) {
      log.debug("유효하지 않은 경로: {}", targetPath);
      return FileManagerDto.ListFilesRes.builder()
          .directoryPath(targetPath)
          .files(new ArrayList<>())
          .totalCount(0)
          .totalSize(0L)
          .build();
    }

    Path directory = Paths.get(targetPath);
    if (!Files.exists(directory)) {
      log.debug("디렉토리가 존재하지 않습니다: {}", targetPath);
      return FileManagerDto.ListFilesRes.builder()
          .directoryPath(targetPath)
          .files(new ArrayList<>())
          .totalCount(0)
          .totalSize(0L)
          .build();
    }

    if (!Files.isDirectory(directory)) {
      log.debug("디렉토리 경로가 아닙니다: {}", targetPath);
      return FileManagerDto.ListFilesRes.builder()
          .directoryPath(targetPath)
          .files(new ArrayList<>())
          .totalCount(0)
          .totalSize(0L)
          .build();
    }

    List<FileManagerDto.FileInfo> files = new ArrayList<>();
    long totalSize = 0;

    try {
      if (recursive) {
        // 재귀적으로 모든 하위 파일 조회
        Files.walkFileTree(
            directory,
            new SimpleFileVisitor<>() {
              @Override
              public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                files.add(createFileInfo(file));
                return FileVisitResult.CONTINUE;
              }

              @Override
              public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                if (!dir.equals(directory)) {
                  files.add(createFileInfo(dir));
                }
                return FileVisitResult.CONTINUE;
              }
            });
      } else {
        // 현재 디렉토리의 파일만 조회
        try (Stream<Path> stream = Files.list(directory)) {
          stream.forEach(path -> files.add(createFileInfo(path)));
        }
      }

      // 총 파일 크기 계산
      for (FileManagerDto.FileInfo file : files) {
        if (file.getIsFile() && file.getFileSize() != null) {
          totalSize += file.getFileSize();
        }
      }

    } catch (IOException e) {
      log.debug("파일 목록 조회 중 오류 발생: {}", targetPath, e);
      return FileManagerDto.ListFilesRes.builder()
          .directoryPath(targetPath)
          .files(new ArrayList<>())
          .totalCount(0)
          .totalSize(0L)
          .build();
    }

    return FileManagerDto.ListFilesRes.builder()
        .directoryPath(targetPath)
        .files(files)
        .totalCount(files.size())
        .totalSize(totalSize)
        .build();
  }

  /**
   * 파일 또는 디렉토리 삭제
   *
   * @param request 삭제 요청 정보
   * @return 삭제 결과
   */
  public FileManagerDto.DeleteFileRes deleteFiles(FileManagerDto.DeleteFileReq request) {
    List<String> deletedFiles = new ArrayList<>();
    List<String> failedFiles = new ArrayList<>();

    boolean recursive = request.getRecursive() != null && request.getRecursive();

    for (String filePath : request.getFilePaths()) {
      try {
        try {
          validatePath(filePath);
        } catch (IllegalArgumentException e) {
          log.debug("유효하지 않은 경로: {}", filePath);
          failedFiles.add(filePath + " (유효하지 않은 경로)");
          continue;
        }

        Path path = Paths.get(filePath);

        if (!Files.exists(path)) {
          log.debug("삭제하려는 파일이 존재하지 않습니다: {}", filePath);
          failedFiles.add(filePath + " (파일이 존재하지 않음)");
          continue;
        }

        if (Files.isDirectory(path)) {
          if (recursive) {
            // 디렉토리 및 하위 파일 모두 삭제
            deleteDirectoryRecursively(path);
            deletedFiles.add(filePath);
          } else {
            // 빈 디렉토리만 삭제
            if (isDirectoryEmpty(path)) {
              Files.delete(path);
              deletedFiles.add(filePath);
            } else {
              failedFiles.add(filePath + " (디렉토리가 비어있지 않음)");
            }
          }
        } else {
          // 파일 삭제
          Files.delete(path);
          deletedFiles.add(filePath);
        }

        log.info("파일 삭제 성공: {}", filePath);

      } catch (Exception e) {
        log.debug("파일 삭제 실패: {}", filePath, e);
        failedFiles.add(filePath + " (" + e.getMessage() + ")");
      }
    }

    String message =
        String.format("%d개 파일 삭제 성공, %d개 파일 삭제 실패", deletedFiles.size(), failedFiles.size());

    return FileManagerDto.DeleteFileRes.builder()
        .deletedFiles(deletedFiles)
        .failedFiles(failedFiles)
        .successCount(deletedFiles.size())
        .failureCount(failedFiles.size())
        .message(message)
        .build();
  }

  /** FileInfo 객체 생성 */
  private FileManagerDto.FileInfo createFileInfo(Path path) {
    try {
      BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

      return FileManagerDto.FileInfo.builder()
          .fileName(path.getFileName().toString())
          .filePath(path.toString())
          .fileSize(attrs.isRegularFile() ? attrs.size() : null)
          .isFile(attrs.isRegularFile())
          .isDirectory(attrs.isDirectory())
          .lastModified(
              LocalDateTime.ofInstant(
                  Instant.ofEpochMilli(attrs.lastModifiedTime().toMillis()),
                  ZoneId.systemDefault()))
          .readable(Files.isReadable(path))
          .writable(Files.isWritable(path))
          .build();
    } catch (IOException e) {
      log.warn("파일 정보 조회 실패: {}", path, e);
      return FileManagerDto.FileInfo.builder()
          .fileName(path.getFileName().toString())
          .filePath(path.toString())
          .build();
    }
  }

  /** 디렉토리 재귀 삭제 */
  private void deleteDirectoryRecursively(Path directory) throws IOException {
    Files.walkFileTree(
        directory,
        new SimpleFileVisitor<>() {
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
              throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
          }

          @Override
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
          }
        });
  }

  /** 디렉토리가 비어있는지 확인 */
  private boolean isDirectoryEmpty(Path directory) throws IOException {
    try (Stream<Path> stream = Files.list(directory)) {
      return stream.findFirst().isEmpty();
    }
  }

  /** 경로 검증 (보안) */
  private void validatePath(String path) {
    if (path == null || path.trim().isEmpty()) {
      throw new IllegalArgumentException("경로가 비어있습니다");
    }

    if (path.length() > MAX_PATH_LENGTH) {
      throw new IllegalArgumentException("경로가 너무 깁니다");
    }

    // 경로 순회 공격 방지 - 상대경로 패턴만 제한
    if (path.contains("..")) {
      throw new IllegalArgumentException("상대 경로(..)는 사용할 수 없습니다");
    }
  }

  /**
   * 모델 파일 경로 조회
   *
   * @param modelUuid 모델 UUID
   * @return 모델 파일 경로 및 파일 목록
   */
  public FileManagerDto.ModelFilePathRes getModelFilePath(UUID modelUuid) {
    // tb_model_master 테이블에서 모델 조회
    ModelMasterEntity model = modelMngRepository.findByUuid(modelUuid).orElse(null);

    // 모델이 존재하지 않으면 빈 데이터 반환
    if (model == null) {
      return FileManagerDto.ModelFilePathRes.builder()
          .modelUuid(modelUuid.toString())
          .requestPath(null)
          .responsePath(null)
          .requestFiles(new ArrayList<>())
          .responseFiles(new ArrayList<>())
          .build();
    }

    // request_path: tb_model_master.request_path 컬럼 값 사용
    // request_path 컬럼에는 'AE366F3076504FACBF12106986202AB5' 형태의 값이 저장되어 있음
    String requestPathFromDb = model.getRequestPath();
    String requestPath;
    if (requestPathFromDb != null && !requestPathFromDb.isEmpty()) {
      // DB에 저장된 값이 있으면 symbolic_link_dir + request_path 조합
      requestPath = symbolicLinkDir + "/" + requestPathFromDb;
    } else {
      // 없으면 기본값: symbolic_link_dir + model_uuid
      requestPath = symbolicLinkDir + "/" + modelUuid;
    }

    // response_path: response_dir + model_uuid (UUID 사용)
    String responsePath = responseDir + "/" + modelUuid;

    // 파일 목록 조회
    List<FileManagerDto.FileInfo> requestFiles = getFilesInDirectory(requestPath);
    List<FileManagerDto.FileInfo> responseFiles = getFilesInDirectory(responsePath);

    return FileManagerDto.ModelFilePathRes.builder()
        .modelUuid(modelUuid.toString())
        .requestPath(requestPath)
        .responsePath(responsePath)
        .requestFiles(requestFiles)
        .responseFiles(responseFiles)
        .build();
  }

  /**
   * 데이터셋 파일 경로 조회
   *
   * @param datasetUuid 데이터셋 UUID
   * @return 데이터셋 파일 경로 및 파일 목록
   */
  public FileManagerDto.DatasetFilePathRes getDatasetFilePath(UUID datasetUuid) {
    // tb_dataset 테이블에서 데이터셋 조회
    DatasetEntity dataset = datasetRepository.findByUuid(datasetUuid).orElse(null);

    // 데이터셋이 존재하지 않으면 빈 데이터 반환
    if (dataset == null) {
      return FileManagerDto.DatasetFilePathRes.builder()
          .datasetUuid(datasetUuid.toString())
          .datasetPath(null)
          .files(new ArrayList<>())
          .build();
    }

    // dataset_path 컬럼이 있으면 사용, 없으면 request_dir + uuid 사용
    String datasetPath = dataset.getDatasetPath();
    if (datasetPath == null || datasetPath.isEmpty()) {
      datasetPath = requestDir + "/" + datasetUuid;
    }

    // 파일 목록 조회
    List<FileManagerDto.FileInfo> files = getFilesInDirectory(datasetPath);

    return FileManagerDto.DatasetFilePathRes.builder()
        .datasetUuid(datasetUuid.toString())
        .datasetPath(datasetPath)
        .files(files)
        .build();
  }

  /**
   * 디렉토리 용량 체크
   *
   * @param directoryPath 디렉토리 경로
   * @return 디렉토리 용량 정보
   */
  public FileManagerDto.DirectoryCapacityRes checkDirectoryCapacity(String directoryPath) {
    // 경로 검증은 수행하되, 예외를 throw하지 않고 빈 데이터 반환
    try {
      validatePath(directoryPath);
    } catch (IllegalArgumentException e) {
      log.debug("유효하지 않은 경로: {}", directoryPath);
      return FileManagerDto.DirectoryCapacityRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .totalSize(0L)
          .totalSizeFormatted("0 B")
          .build();
    }

    Path directory = Paths.get(directoryPath);
    if (!Files.exists(directory)) {
      log.debug("디렉토리가 존재하지 않습니다: {}", directoryPath);
      return FileManagerDto.DirectoryCapacityRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .totalSize(0L)
          .totalSizeFormatted("0 B")
          .build();
    }

    if (!Files.isDirectory(directory)) {
      log.debug("디렉토리 경로가 아닙니다: {}", directoryPath);
      return FileManagerDto.DirectoryCapacityRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .totalSize(0L)
          .totalSizeFormatted("0 B")
          .build();
    }

    final long[] totalSize = {0};
    final int[] fileCount = {0};
    final int[] directoryCount = {0};

    try {
      Files.walkFileTree(
          directory,
          new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
              totalSize[0] += attrs.size();
              fileCount[0]++;
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
              if (!dir.equals(directory)) {
                directoryCount[0]++;
              }
              return FileVisitResult.CONTINUE;
            }
          });
    } catch (IOException e) {
      log.debug("디렉토리 용량 체크 중 오류 발생: {}", directoryPath, e);
      return FileManagerDto.DirectoryCapacityRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .totalSize(0L)
          .totalSizeFormatted("0 B")
          .build();
    }

    return FileManagerDto.DirectoryCapacityRes.builder()
        .directoryPath(directoryPath)
        .fileCount(fileCount[0])
        .directoryCount(directoryCount[0])
        .totalSize(totalSize[0])
        .totalSizeFormatted(formatFileSize(totalSize[0]))
        .build();
  }

  /**
   * 모델별 학습 실행 상태 조회
   *
   * @return 모델별 학습 실행 상태 목록
   */
  public FileManagerDto.AllModelsExecutionStatusRes getModelsExecutionStatus() {
    // G1 ~ G4 모델 조회
    List<String> modelNumbers = Arrays.asList("G1", "G2", "G3", "G4");
    List<FileManagerDto.ModelExecutionStatusRes> modelStatuses = new ArrayList<>();
    int runningCount = 0;

    for (String modelNo : modelNumbers) {
      // model_no로 가장 최근 모델 조회 (del_yn = false)
      List<ModelMasterEntity> models =
          modelMngRepository.findByModelNoAndDelYnOrderByCreatedDttmDesc(modelNo, false);

      if (models.isEmpty()) {
        // 모델이 없으면 대기 상태
        modelStatuses.add(
            FileManagerDto.ModelExecutionStatusRes.builder()
                .modelNo(modelNo)
                .statusMessage(modelNo + " 모델은 대기 중입니다.")
                .step1State(null)
                .step2State(null)
                .currentStep(null)
                .build());
        continue;
      }

      ModelMasterEntity model = models.getFirst();
      String step1State = model.getStep1State();
      String step2State = model.getStep2State();

      String statusMessage;
      Integer currentStep = null;

      // step1, step2 상태 확인
      boolean step1Running = "IN_PROGRESS".equals(step1State);
      boolean step2Running = "IN_PROGRESS".equals(step2State);

      if (step1Running) {
        statusMessage = modelNo + " 모델에 학습(1단계)을 진행중입니다.";
        currentStep = 1;
        runningCount++;
      } else if (step2Running) {
        statusMessage = modelNo + " 모델에 테스트(2단계)를 진행중입니다.";
        currentStep = 2;
        runningCount++;
      } else if ("COMPLETED".equals(step1State) && "COMPLETED".equals(step2State)) {
        statusMessage = modelNo + " 모델은 학습이 완료되었습니다.";
      } else if ("COMPLETED".equals(step1State)) {
        statusMessage = modelNo + " 모델은 1단계 학습이 완료되었습니다.";
      } else {
        statusMessage = modelNo + " 모델은 대기 중입니다.";
      }

      modelStatuses.add(
          FileManagerDto.ModelExecutionStatusRes.builder()
              .modelNo(modelNo)
              .statusMessage(statusMessage)
              .step1State(step1State)
              .step2State(step2State)
              .currentStep(currentStep)
              .build());
    }

    return FileManagerDto.AllModelsExecutionStatusRes.builder()
        .modelStatuses(modelStatuses)
        .runningCount(runningCount)
        .build();
  }

  /** 디렉토리 내 파일 목록 조회 (내부 사용) */
  private List<FileManagerDto.FileInfo> getFilesInDirectory(String directoryPath) {
    List<FileManagerDto.FileInfo> files = new ArrayList<>();

    Path directory = Paths.get(directoryPath);
    if (!Files.exists(directory)) {
      log.debug("디렉토리가 존재하지 않습니다: {}", directoryPath);
      return files;
    }

    if (!Files.isDirectory(directory)) {
      log.debug("디렉토리 경로가 아닙니다: {}", directoryPath);
      return files;
    }

    try (Stream<Path> stream = Files.list(directory)) {
      stream.forEach(path -> files.add(createFileInfo(path)));
    } catch (IOException e) {
      log.debug("파일 목록 조회 중 오류 발생: {}", directoryPath, e);
    }

    return files;
  }

  /** 파일 크기 포맷팅 (읽기 쉬운 형식) */
  private String formatFileSize(long size) {
    if (size < 1024) {
      return size + " B";
    } else if (size < 1024 * 1024) {
      return String.format("%.2f KB", size / 1024.0);
    } else if (size < 1024 * 1024 * 1024) {
      return String.format("%.2f MB", size / (1024.0 * 1024.0));
    } else {
      return String.format("%.2f GB", size / (1024.0 * 1024.0 * 1024.0));
    }
  }

  /**
   * 저장공간 정보 조회 (남은 공간 포함) 고정 경로: train.docker.base_path 설정값 사용
   *
   * @return 저장공간 정보 (사용량, 남은 공간, 디스크 용량)
   */
  public FileManagerDto.StorageSpaceRes getStorageSpaceInfo() {
    // 설정값에서 경로 가져오기 (train.docker.base_path)

    log.info("basePath = {}", basePath);
    String directoryPath = basePath;

    Path directory = Paths.get(directoryPath);
    if (!Files.exists(directory)) {
      log.info("디렉토리가 존재하지 않습니다: {}", directoryPath);
      return FileManagerDto.StorageSpaceRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .usedSize(0L)
          .usedSizeFormatted("0 B")
          .totalDiskSpace(0L)
          .totalDiskSpaceFormatted("0 B")
          .freeSpace(0L)
          .freeSpaceFormatted("0 B")
          .usableSpace(0L)
          .usableSpaceFormatted("0 B")
          .usagePercentage(0.0)
          .build();
    }

    if (!Files.isDirectory(directory)) {
      log.info("디렉토리 경로가 아닙니다: {}", directoryPath);
      return FileManagerDto.StorageSpaceRes.builder()
          .directoryPath(directoryPath)
          .fileCount(0)
          .directoryCount(0)
          .usedSize(0L)
          .usedSizeFormatted("0 B")
          .totalDiskSpace(0L)
          .totalDiskSpaceFormatted("0 B")
          .freeSpace(0L)
          .freeSpaceFormatted("0 B")
          .usableSpace(0L)
          .usableSpaceFormatted("0 B")
          .usagePercentage(0.0)
          .build();
    }

    // 디렉토리 사용량 계산 — du -sb 사용 (walkFileTree 대비 수배 빠름)
    // fileCount/directoryCount는 du가 제공하지 않으므로 -1(미집계) 반환
    long usedSize = 0;

    log.info("[StorageSpace] du 시작 - path={}", directoryPath);
    try {
      ProcessBuilder duPb = new ProcessBuilder("du", "-sb", directoryPath);
      duPb.redirectErrorStream(true);
      Process duProcess = duPb.start();
      try (java.io.BufferedReader br =
          new java.io.BufferedReader(new java.io.InputStreamReader(duProcess.getInputStream()))) {
        String line = br.readLine();
        if (line != null) {
          // du -sb 출력 형식: "12345678\t/path/to/dir"
          String[] parts = line.split("\t", 2);
          usedSize = Long.parseLong(parts[0].trim());
        }
      }
      int exitCode = duProcess.waitFor();
      log.info(
          "[StorageSpace] du 완료 - exitCode={}, usedSize={} bytes ({})",
          exitCode,
          usedSize,
          formatFileSize(usedSize));
    } catch (Exception e) {
      log.error("[StorageSpace] du 실행 실패: {}", directoryPath, e);
      return FileManagerDto.StorageSpaceRes.builder()
          .directoryPath(directoryPath)
          .fileCount(-1)
          .directoryCount(-1)
          .usedSize(0L)
          .usedSizeFormatted("0 B")
          .totalDiskSpace(0L)
          .totalDiskSpaceFormatted("0 B")
          .freeSpace(0L)
          .freeSpaceFormatted("0 B")
          .usableSpace(0L)
          .usableSpaceFormatted("0 B")
          .usagePercentage(0.0)
          .build();
    }

    // 디스크 공간 정보 조회 (FileStore 사용) — basePath가 속한 파티션/NFS 마운트 전체 기준
    long totalDiskSpace = 0;
    long freeSpace = 0;
    long usableSpace = 0;
    double usagePercentage = 0.0;

    try {
      java.nio.file.FileStore fileStore = Files.getFileStore(directory);
      totalDiskSpace = fileStore.getTotalSpace();
      freeSpace = fileStore.getUnallocatedSpace(); // OS 미할당 공간 (root 예약 블록 포함 가능)
      usableSpace = fileStore.getUsableSpace(); // 실제 프로세스가 사용 가능한 공간
      log.info(
          "[StorageSpace] FileStore - name={}, type={}, total={} bytes ({}), unallocated={} bytes ({}), usable={} bytes ({})",
          fileStore.name(),
          fileStore.type(),
          totalDiskSpace,
          formatFileSize(totalDiskSpace),
          freeSpace,
          formatFileSize(freeSpace),
          usableSpace,
          formatFileSize(usableSpace));

      // 사용률은 usableSpace 기준으로 계산 (더 정확한 실사용 가능 공간 반영)
      if (totalDiskSpace > 0) {
        long usedDiskSpace = totalDiskSpace - usableSpace;
        usagePercentage = (usedDiskSpace * 100.0) / totalDiskSpace;
      }
      log.info("[StorageSpace] usagePercentage={}%", Math.round(usagePercentage * 100.0) / 100.0);
    } catch (IOException e) {
      log.warn("[StorageSpace] 디스크 공간 정보 조회 실패: {}", directoryPath, e);
      // 디스크 정보 조회 실패 시에도 디렉토리 용량은 반환
    }

    return FileManagerDto.StorageSpaceRes.builder()
        .directoryPath(directoryPath)
        .fileCount(-1)
        .directoryCount(-1)
        .usedSize(usedSize)
        .usedSizeFormatted(formatFileSize(usedSize))
        .totalDiskSpace(totalDiskSpace)
        .totalDiskSpaceFormatted(formatFileSize(totalDiskSpace))
        .freeSpace(freeSpace)
        .freeSpaceFormatted(formatFileSize(freeSpace))
        .usableSpace(usableSpace)
        .usableSpaceFormatted(formatFileSize(usableSpace))
        .usagePercentage(Math.round(usagePercentage * 100.0) / 100.0)
        .build();
  }
}
