package de.ivu.elect.business.system.control.files.cleaner;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.datei.boundary.DateiDAO;
import de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.util.FileUtil;


import static java.util.stream.Collectors.toList;

public class DateiInDatenbankGeloeschtFileCleaner extends AbstractCleaner<List<File>> implements FileCleaner<List<File>> {

  private static final Logger LOG = LoggerFactory.getLogger(DateiInDatenbankGeloeschtFileCleaner.class);

  private static final long DATEIEN_PRO_DURCHLAUF = 1000L;

  public static final Set<String> DENYLIST = new HashSet<>();

  private static final long MIN_LENGTH_FILE_SIZE_OHNE_MINIMALE_ZEIT = 1024L * 1024L * 100L; // 100 Mb

  private static final long MINIMALE_ZEIT_GROSSE_DATEIEN = 10L * 1000L; // 10 Sekunden

  @Inject
  private transient DateiDAO dateiDAO;

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient Messages messages;

  @Override
  protected Logger getLog() {
    return LOG;
  }

  @Override
  public void bereinige(File verzeichnis, FileCleanerBucket<List<File>> bucket) throws Exception {
    for (var file : bucket.getBucket()) {
      bereinige(verzeichnis, file);
    }
  }

  @Override
  public List<FileCleanerBucket<List<File>>> getBuckets(File verzeichnis) throws Exception {
    var zuBereinigendeDateien = getZuBereinigendeDateien(verzeichnis);
    if (zuBereinigendeDateien.isEmpty()) {
      return Collections.emptyList();
    }
    return Collections.singletonList(new FileCleanerBucket<>(zuBereinigendeDateien));
  }

  private void bereinige(File verzeichnis, File file) {
    try {
      verschiebeInRecycleBin(file, verzeichnis);
    } catch (Exception e) {
      // ELECT-8678
      DENYLIST.add(file.getName());
      if (isFileNotFoundException(e)) {
        LOG.info("Datei bereits gelöscht: {}", file);
      } else {
        systemLogWriter.writeWarnLogMessageFuerSystemMandant(messages.getMessageForMessageKey("datei.cleanup.move.recycle.failed", file.getName()), e,
            SystemLogQuelle.FILE_CLEANUP);
      }
    }

  }

  private boolean isFileNotFoundException(Throwable e) {
    if (e == null) {
      return false;
    }
    if (e instanceof FileNotFoundException) {
      return true;
    }
    return isFileNotFoundException(e.getCause());

  }

  private List<File> getZuBereinigendeDateien(File verzeichnis) throws IOException {
    getLog().info("Lade bekannte Dateinamen");
    Set<String> knownFileNames = new HashSet<>(dateiDAO.findByAllDateinamen());
    getLog().info("Lade bekannte Dateinamen - done, habe: {}", knownFileNames.size());

    var minimaleZeit = konfigurationLoader.resolveLongValueOptional(ElectKonfigurationWerte.ELECT_COMMON_DATEI_CLEANUP_MS_BEFORE_RECYLE);
    var now = System.currentTimeMillis();
    LOG.info("Minimale Zeit: {}", minimaleZeit);

    return FileUtil.listFilesInDirectoryFast(verzeichnis, true).stream()
        // nicht notwendig .filter(File::isFile)
        .filter(f -> !DENYLIST.contains(f.getName())) // ELECT-8678
        .filter(f -> !knownFileNames.contains(f.getName()))
        .filter(f -> !isInRecycleBin(f))
        .filter(f -> isAusreichendAlt(f, minimaleZeit, now))
        .limit(DATEIEN_PRO_DURCHLAUF)
        .collect(toList());
  }

  private boolean isAusreichendAlt(File f, Long minimaleZeit, long now) {
    if (minimaleZeit == null || minimaleZeit <= 0L) {
      return true;
    }

    var result = now - f.lastModified() >= minimaleZeit;

    if (!result && f.length() > MIN_LENGTH_FILE_SIZE_OHNE_MINIMALE_ZEIT) {
      return now - f.lastModified() >= MINIMALE_ZEIT_GROSSE_DATEIEN;
    }

    return result;
  }
}
