package nl.sidnlabs.entrada.file;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.FileSystemUtils;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Component("local")
public class LocalFileManagerImpl implements FileManager {

  private static final String LOCAL_SCHEME = "file://";

  @Override
  public String schema() {
    return LOCAL_SCHEME;
  }

  @Override
  public boolean exists(String file) {
    File f = new File(file);
    return f.exists();
  }

  @Override
  public List<String> files(String dir, String... filter) {

    File fDir = new File(dir);
    if (!fDir.isDirectory()) {
      log.error("{} is not a valid directory", dir);
      return Collections.emptyList();
    }

    return Arrays
        .stream(fDir.listFiles())
        .filter(File::isFile)
        .map(File::getAbsolutePath)
        .filter(f -> checkFilter(f, Arrays.asList(filter)))
        .collect(Collectors.toList());
  }

  private boolean checkFilter(String file, List<String> filters) {
    if (filters.isEmpty()) {
      return true;
    }
    return filters.stream().anyMatch(f -> StringUtils.endsWith(file, f));
  }

  @Override
  public Optional<InputStream> open(String location) {
    log.info("Open local file: " + location);

    File f = FileUtils.getFile(location);
    try {
      return Optional.of(FileUtils.openInputStream(f));
    } catch (IOException e) {
      log.error("Cannot open file: {}", f, e);
    }

    return Optional.empty();
  }

  @Override
  public boolean upload(String src, String dst, boolean archive) {
    log.info("Upload work location: {} to target location: {}", src, dst);

    try {
      FileUtils.copyDirectory(new File(src), new File(dst), true);
      return true;
    } catch (Exception ex) {
      log.error("Cannot upload directory: {} to {}", src, dst, ex);
    }

    return false;
  }

  @Override
  public boolean rmdir(String location) {
    log.info("Delete local directory: " + location);

    File f = new File(location);
    if (f.exists() && f.isDirectory()) {
      return FileSystemUtils.deleteRecursively(f);
    }

    return false;
  }

  @Override
  public boolean delete(String location) {
    log.info("Delete local file: " + location);

    File f = new File(location);
    if (f.exists() && f.isFile()) {
      try {
        Files.delete(Paths.get(location));
        return true;
      } catch (IOException e) {
        log.error("Cannot delete file: {}", location, e);
      }
    }

    return false;
  }


  @Override
  public boolean move(String src, String dst, boolean archive) {
    log.info("Move local file: {} to: {} ", src, dst);
    Path dstPath = Paths.get(dst);
    // make sure the directory exists
    mkdir(dstPath.getParent().toString());

    try {
      Files.move(Paths.get(src), dstPath, StandardCopyOption.REPLACE_EXISTING);
      return true;
    } catch (IOException e) {
      log.error("Error while moving file {} to {}", src, dst, e);
    }
    return false;
  }

  @Override
  public boolean supported(String location) {
    return StringUtils.startsWith(location, "/");
  }

  @Override
  public boolean isLocal() {
    return true;
  }

  @Override
  public boolean mkdir(String path) {
    log.info("Create directory: {} ", path);
    File f = new File(path);

    if (!f.exists() && !Files.isSymbolicLink(f.toPath())) {
      return f.mkdirs();
    }

    // dir already exists
    return true;
  }

  @Override
  public boolean chown(String path, String owner, String group) {
    throw new UnsupportedOperationException();
  }

  @Override
  public List<String> expired(String location, int maxAge, String... filter) {
    long max = System.currentTimeMillis() - (maxAge * 60 * 60 * 1000);

    try (Stream<Path> stream = Files.walk(Paths.get(location))) {
      return stream
          .filter(Files::isRegularFile)
          .filter(p -> isTooOld(p, max))
          .map(Path::toString)
          .filter(p -> checkFilter(p, Arrays.asList(filter)))
          .collect(Collectors.toList());
    } catch (Exception e) {
      log.error("Cannot get list of expired files", e);
      return Collections.emptyList();
    }
  }

  private boolean isTooOld(Path p, long max) {
    FileTime ft;
    try {
      ft = Files.getLastModifiedTime(p);
    } catch (IOException e) {
      log.error("Cannot get LastModifiedTime", e);
      return false;
    }
    return ft.toMillis() < max;
  }

  @Override
  public void close() {
    // do nothing
  }

}