
package de.ivu.elect.business.dokumente.control;

import de.ivu.elect.business.datei.boundary.DateiDAO;
import de.ivu.elect.business.datei.boundary.DateiImportDAO;
import de.ivu.elect.business.datei.control.FileExchange;
import de.ivu.elect.business.datei.control.ImportFileProperties;
import de.ivu.elect.business.datei.entity.*;
import de.ivu.elect.business.dokumente.boundary.DokumentVorlageDAO;
import de.ivu.elect.business.dokumente.entity.*;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.entity.DateiImportArt;
import de.ivu.elect.business.wahl.boundary.WahlterminDAO;
import de.ivu.elect.business.wahl.control.WahlAttacher;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.model.file.UploadedFile;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.validation.constraints.Null;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class DokumentVorlageCreate extends AbstractDokumentVorlageCreate {

  public static final Charset ISO_885911 = StandardCharsets.ISO_8859_1;

  public static final Charset ISO_88591 = StandardCharsets.ISO_8859_1;

  public static final Charset ZIP_CHARSET = StandardCharsets.ISO_8859_1; // ELECT-3376

  @Inject
  private transient FileTypeResolver fileTypeResolver;

  @Inject
  private transient FileExchange fileExchange;

  @Inject
  private transient WahlAttacher wahlAttacher;

  @Inject
  private transient WahlterminDAO wahlterminDAO;

  @Inject
  private transient DokumentVorlageDAO dokumentVorlageDAO;

  @Inject
  private transient DateiImportDAO dateiImportDAO;

  @Inject
  private transient DateiDAO dateiDAO;

  public DokumentVorlageCreate() {
    super();
  }

  public void save(DokumentVorlage entity, UploadedFile uploadedFile, Wahl wahl, Wahltermin wahltermin) {
    try {
      save(entity, uploadedFile.getInputStream(), uploadedFile.getFileName(), wahl, wahltermin);
    } catch (IOException ex) {
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }

  public void save(DokumentVorlage dokumentVorlage, InputStream inputStream, String fileName, @Nullable Wahl wahl, Wahltermin wahltermin) throws IOException {
    FileType fileType = fileTypeResolver.getFileType(fileName).orElse(FileType.ODT);

    ImportFileProperties importFileProperties = new ImportFileProperties(fileName, new FileFormat("Dokumentvorlage", fileType), DateiImportArt.DOKUMENT_VORLAGE);

    DateiImport dateiImport = createDateiImport(inputStream, wahl, wahltermin, importFileProperties);

    dokumentVorlage.setDateiImport(dateiImport);
    dokumentVorlage.setWahl(wahlAttacher.attach(wahl));

    if (wahltermin == null && wahl != null) {
      wahltermin = wahl.getWahltermin(); // in WAS ist nicht unbedingt ein Wahltermin ausgewählt
    }

    dokumentVorlage.setWahltermin(wahlterminDAO.attach(wahltermin));

    super.save(dokumentVorlage);
  }

  private DateiImport createDateiImport(InputStream inputStream, @Nullable Wahl wahl, Wahltermin wahltermin, ImportFileProperties importFileProperties) throws IOException {
    if (wahl == null) {
      return fileExchange.createAndPersistDateiImportAndSpeichereDateiAufServer(inputStream, importFileProperties, wahltermin.getMandant(), wahltermin, new DateiImportBerichtCustomData(), ServerFileType.DOKUMENTVORLAGE);
    }

    return fileExchange.createAndPersistDateiImportAndSpeichereDateiAufServer(inputStream, importFileProperties, wahl.getMandant(), wahl, new DateiImportBerichtCustomData(), ServerFileType.DOKUMENTVORLAGE);
  }

  public List<DokumentArchiveUploadResultEntry> saveArchive(UploadedFile archive, Wahl wahl, Wahltermin wahltermin, List<DokumentVorlageArt> dokumentVorlageArtenRelevant, Function<DokumentVorlageAnlageArt, DokumentArchiveUploadResultStatus> updateStrategie, DokumentVorlageAnlageArt dokumentVorlageAnlageArt) {
    return saveArchive(archive, wahl, wahltermin, dokumentVorlageArtenRelevant, updateStrategie, dokumentVorlageAnlageArt, Collections.emptySet());
  }

  public List<DokumentArchiveUploadResultEntry> saveArchive(UploadedFile archive, @Nullable Wahl wahl, Wahltermin wahltermin,
                                                            List<DokumentVorlageArt> dokumentVorlageArtenRelevant, Function<DokumentVorlageAnlageArt, DokumentArchiveUploadResultStatus> updateStrategie, DokumentVorlageAnlageArt dokumentVorlageAnlageArt, Set<DokumentVorlage> ueberzaehligeDokumentvorlagen) {
    return readArchiveSaveEntries(archive, wahl, wahltermin, dokumentVorlageArtenRelevant, updateStrategie, true, dokumentVorlageAnlageArt,
        ueberzaehligeDokumentvorlagen);
  }

  public List<DokumentArchiveUploadResultEntry> readArchive(UploadedFile archive, Wahl wahl, Wahltermin wahltermin, List<DokumentVorlageArt> dokumentVorlageArtenRelevant, Function<DokumentVorlageAnlageArt, DokumentArchiveUploadResultStatus> updateStrategie, DokumentVorlageAnlageArt dokumentVorlageAnlageArt) {
    return readArchiveSaveEntries(archive, wahl, wahltermin, dokumentVorlageArtenRelevant, updateStrategie, false, dokumentVorlageAnlageArt, new HashSet<>());
  }

  private List<DokumentArchiveUploadResultEntry> readArchiveSaveEntries(UploadedFile archive, @Nullable Wahl wahl, Wahltermin wahltermin,
                                                                        List<DokumentVorlageArt> dokumentVorlageArtenRelevant,
                                                                        Function<DokumentVorlageAnlageArt, DokumentArchiveUploadResultStatus> updateStrategie,
                                                                        boolean dokumentVorlageAnlegen, DokumentVorlageAnlageArt dokumentVorlageAnlageArt,
                                                                        Set<DokumentVorlage> ueberzaehligeDokumentvorlagen) {
    List<DokumentVorlage> dokumentVorlagenInDb;

    if (wahl == null) {
      dokumentVorlagenInDb = new ArrayList<>(dao.findByWahlterminOrderByArtAndName(wahltermin));
    } else {
      dokumentVorlagenInDb = new ArrayList<>(dao.findByWahlOrderByArtAndName(wahl));
    }

    List<DokumentArchiveUploadResultEntry> result = new ArrayList<>();

    String dokumentVorlageVersionsnummer = versucheVersionsnummerZuBestimmen(archive.getFileName());

    try (ZipInputStream zis = new ZipInputStream(archive.getInputStream(), ZIP_CHARSET)) {
      ZipEntry zipEntry;

      while ((zipEntry = zis.getNextEntry()) != null) {
        if (!zipEntry.isDirectory()) {
          result.add(handleZipEntry(zipEntry, zis, dokumentVorlageVersionsnummer, dokumentVorlagenInDb, dokumentVorlageArtenRelevant, updateStrategie,
              dokumentVorlageAnlegen, dokumentVorlageAnlageArt, wahl, wahltermin, ueberzaehligeDokumentvorlagen));
        }
      }
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

    result.sort(new DokumentArchiveUploadResultEntryByZipEntryFileNameComparator());

    return result;
  }

  protected static String versucheVersionsnummerZuBestimmen(String fileName) {
    if (StringUtils.isEmpty(fileName)) {
      return "";
    }

    Pattern pattern = Pattern.compile(".*-([\\d]*\\.[\\d]*\\.[\\d]*)-.*");
    Matcher matcher = pattern.matcher(fileName);

    if (matcher.matches()) {
      return matcher.group(1);
    }

    return "";
  }

  private DokumentArchiveUploadResultEntry handleZipEntry(ZipEntry zipEntry, ZipInputStream zis, String dokumentVorlageVersionsnummer, List<DokumentVorlage> dokumentVorlagenInDb,
                                                          List<DokumentVorlageArt> dokumentVorlageArtenRelevant, Function<DokumentVorlageAnlageArt, DokumentArchiveUploadResultStatus> updateStrategie,
                                                          boolean dokumentVorlageAnlegen, DokumentVorlageAnlageArt dokumentVorlageAnlageArt, @Nullable Wahl wahl, Wahltermin wahltermin,
                                                          Set<DokumentVorlage> ueberzaehligeDokumentvorlagen) throws IOException {
    String zipEntryPathName = zipEntry.getName();
    String zipEntryFileNameAsDateiOriginalDateiname = extractDateiOriginalDateiname(zipEntry);

    DokumentVorlageArt dokumentVorlageArt = DokumentVorlageArt.getDokumentVorlageArt(zipEntryFileNameAsDateiOriginalDateiname);

    if (dokumentVorlageArt == null || !dokumentVorlageArtenRelevant.contains(dokumentVorlageArt)) {
      return new DokumentArchiveUploadResultEntry(zipEntryPathName, zipEntryFileNameAsDateiOriginalDateiname, "-", null, dokumentVorlageVersionsnummer,
          DokumentArchiveUploadResultStatus.NICHT_ERKANNT);
    }

    String dokumentVorlageName = DokumentVorlageArt.getDokumentVorlageName(zipEntryFileNameAsDateiOriginalDateiname);
    // FIXME ELECT-7034, bei mehreren gleichnamigen Dateien (in verschiedenen ZIP-Archiv-Verzeichnissen) wird hier die jeweils vorherige Datei gefunden und
    // anschließend aktualisiert == überschrieben!!!
    DokumentVorlage dokumentVorlageInDb = findeVorhandeneDokumentVorlage(dokumentVorlagenInDb, dokumentVorlageArt, dokumentVorlageName);

    DokumentArchiveUploadResultStatus status;

    if (dokumentVorlageInDb == null) {
      status = DokumentArchiveUploadResultStatus.NEU_ANGELEGT;
    } else {
      ueberzaehligeDokumentvorlagen.remove(dokumentVorlageInDb);
      status = updateStrategie.apply(dokumentVorlageInDb.getAnlageArt());
    }

    if (dokumentVorlageAnlegen) {
      if (status.isUploadErfolgt()) {
        DokumentVorlage dokumentVorlage = new DokumentVorlage();

        if (dokumentVorlageInDb != null) {
          dokumentVorlage.setBeschreibung(dokumentVorlageInDb.getBeschreibung());

          loescheDokumentvorlage(dokumentVorlageInDb);

          dokumentVorlagenInDb.remove(dokumentVorlageInDb);
        }

        dokumentVorlage.setDokumentVorlageArt(dokumentVorlageArt);
        dokumentVorlage.setAnlageArt(dokumentVorlageAnlageArt);
        dokumentVorlage.setName(dokumentVorlageName);
        dokumentVorlage.setVersionsnummer(dokumentVorlageVersionsnummer);

        save(dokumentVorlage, zis, zipEntryFileNameAsDateiOriginalDateiname, wahl, wahltermin);

        dokumentVorlagenInDb.add(dokumentVorlage);
      }
    }

    return new DokumentArchiveUploadResultEntry(zipEntryPathName, zipEntryFileNameAsDateiOriginalDateiname, dokumentVorlageName, dokumentVorlageArt, dokumentVorlageVersionsnummer, status);
  }

  public void loescheDokumentvorlage(DokumentVorlage dokumentVorlageInDb) {
    DateiImport dateiImportInDb = dokumentVorlageInDb.getDateiImport();
    Datei dateiInDb = dateiImportInDb.getDatei();
    dokumentVorlageDAO.remove(dokumentVorlageInDb);
    dateiImportDAO.remove(dateiImportInDb);
    dateiDAO.remove(dateiInDb);
  }

  /**
   * ELECT-3376
   */
  public static String extractDateiOriginalDateiname(ZipEntry zipEntry) {
    String dateiNameVorlaeufigIso = FilenameUtils.getName(zipEntry.getName());
    int anzahlUmlauteIso = zaehleUmlaute(dateiNameVorlaeufigIso);

    byte[] bytes = dateiNameVorlaeufigIso.getBytes(ZIP_CHARSET);

    String dateiNameVorlaeufigUTF8 = new String(bytes, StandardCharsets.UTF_8);
    int anzahlUmlauteUF8 = zaehleUmlaute(dateiNameVorlaeufigUTF8);

    String dateiName = dateiNameVorlaeufigIso;

    if (anzahlUmlauteUF8 > anzahlUmlauteIso) {
      dateiName = dateiNameVorlaeufigUTF8;
    }

    return dateiName;
  }

  private static int zaehleUmlaute(String string) {
    int anzahlUmlaute = 0;
    String umlaute = "üäöÜÄÖß";

    for (char c : umlaute.toCharArray()) {
      if (string.indexOf(c) > -1) {
        anzahlUmlaute++;
      }
    }

    return anzahlUmlaute;
  }

  @Nullable
  private DokumentVorlage findeVorhandeneDokumentVorlage(List<DokumentVorlage> dokumentVorlagenInDb, DokumentVorlageArt dokumentVorlageArt, String dokumentVorlageName) {
    return dokumentVorlagenInDb.stream()
      .filter(dokumentVorlage -> dokumentVorlageArt.equals(dokumentVorlage.getDokumentVorlageArt()) && dokumentVorlageName.equals(dokumentVorlage.getName()))
      .findFirst().orElse(null);

  }
}
