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

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import javax.annotation.Nullable;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;

import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.jee.entity.SimpleDAO;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.FileTypeResolver;
import de.ivu.elect.business.exchange.entity.DateienImportierenBackgroundServiceContext;
import de.ivu.elect.business.exchange.entity.DateienImportierenBackgroundServiceImportContext;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlOderWahltermin;
import de.ivu.elect.business.wahl.entity.WahlPK;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.business.wahl.entity.WahlterminPK;
import de.ivu.elect.jee.cdi.BeanManagerUtil;
import de.ivu.elect.jee.exception.ElectCommonException;


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

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class DateienImportierenBackgroundService extends AbstractDateiExchangeBackgroundService {

  protected static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

  private static final Logger LOG = LoggerFactory.getLogger(DateienImportierenBackgroundService.class);
  public static final String DATEI_IMPORT_BACKGROUND_LOG_FILE_EXTENSION = "datei.import.background.log.file.extension";
  public static final String DATEI_IMPORT_BACKGROUND_ERROR_FILE_EXTENSION = "datei.import.background.error.file.extension";

  @Inject
  private transient FileTypeResolver fileTypeResolver;

  @Inject
  private transient MandantDAO mandantDAO;

  @Inject
  private transient SimpleDAO simpleDAO;

  @Inject
  private transient DateienImportierenBackgroundServiceDatenCreator dateienImportierenBackgroundServiceDatenCreator;

  public DateienImportierenBackgroundService() {
    super();
  }

  public boolean importiere(WahlPK wahlPK, DateienImportierenBackgroundServiceContext context) {
    Wahl wahl = findeWahl(wahlPK);

    if (wahl == null) {
      LOG.warn(getMessage("datei.import.background.wahl.nicht.gefunden", wahlPK));

      return false;
    }

    return importiere(wahl, context);
  }

  public boolean importiere(WahlterminPK wahlterminPK, DateienImportierenBackgroundServiceContext context) {
    Wahltermin wahltermin = findeWahltermin(wahlterminPK);

    if (wahltermin == null) {
      LOG.warn(getMessage("datei.import.background.wahltermin.nicht.gefunden", wahlterminPK));

      return false;
    }

    return importiere(wahltermin, context);
  }

  protected boolean importiere(WahlOderWahltermin wahlOderWahltermin, DateienImportierenBackgroundServiceContext context) {
    try {
      File importVerzeichnis = pruefeVerzeichnis(wahlOderWahltermin, context.getImportVerzeichnis());

      File erfolgVerzeichnis = pruefeVerzeichnis(wahlOderWahltermin, context.getSuccessVerzeichnis());
      File fehlerVerzeichnis = pruefeVerzeichnis(wahlOderWahltermin, context.getErrorVerzeichnis());

      File naechsteDatei = context.getBestimmeNaechsteDateiInVerzeichnis().apply(importVerzeichnis);

      if (naechsteDatei != null) {
        LOG.info(getMessage("datei.import.background.import.file", naechsteDatei));

        try {
          boolean flush = false;

          DateiImportBerichtCustomData dateiImportBerichtCustomData = null;

          LOG.info(getMessage("datei.import.background.import.create.context", naechsteDatei));
          for (DateienImportierenBackgroundServiceImportContext importContext : context.getImportContexts()) {
            if (flush) {
              LOG.info(getMessage("datei.import.background.import.flush", naechsteDatei));
              // Zwischen zwei Importen flush-Aufrufen, damit die Daten des ersten Importers im zweiten nutzbar sind
              wahlterminDAO.flush();
            }

            LOG.info(getMessage("datei.import.background.import.data", importContext));
            importiereDatei(wahlOderWahltermin, naechsteDatei, importContext);
            LOG.info(getMessage("datei.import.background.import.data.done", importContext));

            dateiImportBerichtCustomData = importContext.getDateiImportBerichtCustomData();

            flush = true;
          }

          LOG.info(getMessage("datei.import.background.move.successful.file", naechsteDatei, erfolgVerzeichnis));

          verschiebeDateiErfolg(naechsteDatei, erfolgVerzeichnis, dateiImportBerichtCustomData);

          // ELECTMUC-2062
          if (dateiImportBerichtCustomData!=null && dateiImportBerichtCustomData.areWarnungenVorhanden()) {
            systemLogWriter.writeWarnLogMessageFuerMandant(wahlOderWahltermin, getMessage("datei.import.background.warning.syslog", wahlOderWahltermin.getName(), naechsteDatei.getName()), context.getQuelle());
          }
        } catch (Exception ex) {
          systemLogWriter.writeErrorLogMessageFuerMandant(wahlOderWahltermin, getMessage("datei.import.background.error.syslog",
              wahlOderWahltermin.getName(), naechsteDatei.getName(), ex.getLocalizedMessage()), ex, context.getQuelle());

          LOG.info(getMessage("datei.import.background.move.error.file", naechsteDatei, fehlerVerzeichnis));

          verschiebeDateiFehler(naechsteDatei, fehlerVerzeichnis, ex);
        }

        return true; // Weitere (fehlerfreie) Datei möglicher Weise vorhanden
      }
    } catch (Exception ex) {
      systemLogWriter.writeErrorLogMessageFuerMandant(wahlOderWahltermin, wahlOderWahltermin.getName() + " - " + ex.getLocalizedMessage(), ex, context.getQuelle());
    }

    return false;
  }

  private DateienImportFormatResolver bestimmeFormatResolver(Iterable<? extends DateienImportFormatResolver> formatResolvers,
                                                             File naechsteDatei, WahlOderWahltermin wahlOderWahltermin) {
    Set<DateienImportFormatResolver> result = new HashSet<>();

    FileType dateiFileType = bestimmeFileTypeDatei(naechsteDatei);

    for (DateienImportFormatResolver formatResolver : formatResolvers) {
      if (formatResolver.isAccept(wahlOderWahltermin.getWahlterminIfWahltermin(), wahlOderWahltermin.getWahlIfWahl())) {
        if (dateiFileType.equals(formatResolver.getFileType())) {
          result.add(formatResolver);
        }
      }
    }

    if (result.isEmpty()) {
      throw new DateienExchangeBackgroundServiceException("datei.importieren.background.kein.unterstuetztes.format",
          naechsteDatei.getName(), getMessage(dateiFileType));
    }

    if (result.size() > 1) {
      throw new DateienExchangeBackgroundServiceException("datei.importieren.background.mehr.als.ein.unterstuetztes.format",
          naechsteDatei.getName(), getMessage(dateiFileType),
          String.join(", ", result.stream().map(DateienImportExportFormatResolver::getFormatName).map(this::getMessage).collect(toList())));
    }

    return result.iterator().next();
  }

  private FileType bestimmeFileTypeDatei(File naechsteDatei) {
    Optional<FileType> fileTypeOptional = fileTypeResolver.getFileType(naechsteDatei);

    if (fileTypeOptional.isPresent()) {
      return fileTypeOptional.get();
    }

    throw new DateienExchangeBackgroundServiceException("datei.importieren.background.datei.typ.unbekannt", naechsteDatei.getName());
  }

  protected File verschiebeDateiErfolg(File datei, File verzeichnis, @Nullable DateiImportBerichtCustomData dateiImportBerichtCustomData)
      throws IOException {
    File result = verschiebeDatei(datei, verzeichnis);

    schreibeImportBericht(datei, verzeichnis, dateiImportBerichtCustomData);
    return result;
  }

  protected void schreibeImportBericht(File datei, File verzeichnis, @Nullable DateiImportBerichtCustomData dateiImportBerichtCustomData) throws IOException {
    String importBericht = "";

    if (dateiImportBerichtCustomData != null) {
      importBericht = dateiImportBerichtCustomData.toString(messages);
    }

    FileUtils.writeStringToFile(
        new File(
            verzeichnis.getAbsolutePath() + File.separator + datei.getName() + getMessage(DATEI_IMPORT_BACKGROUND_LOG_FILE_EXTENSION)),
        importBericht, DEFAULT_ENCODING);
  }

  private void verschiebeDateiFehler(File datei, File verzeichnis, Throwable cause) throws IOException {
    verschiebeDatei(datei, verzeichnis);

    schreibeErrorDatei(datei, verzeichnis, cause);
  }

  protected void schreibeErrorDatei(File datei, File verzeichnis, Throwable cause) throws IOException {
    FileUtils.writeStringToFile(
        new File(
            verzeichnis.getAbsolutePath() + File.separator + datei.getName() + getMessage(DATEI_IMPORT_BACKGROUND_ERROR_FILE_EXTENSION)),
        getMessage(cause), DEFAULT_ENCODING);
  }

  private File verschiebeDatei(File naechsteDatei, File targetVerzeichnis) throws IOException {
    File target = new File(targetVerzeichnis.getAbsolutePath() + File.separator + naechsteDatei.getName());
    FileUtils.moveFile(naechsteDatei, target);
    return target;
  }

  private void importiereDatei(WahlOderWahltermin wahlOderWahltermin, File naechsteDatei,
                               DateienImportierenBackgroundServiceImportContext importContext) throws Exception {
    DateienImportFormatResolver formatResolver = bestimmeFormatResolver(importContext.getFormatResolvers(), naechsteDatei, wahlOderWahltermin);

    formatResolver.validierungVorImport(wahlOderWahltermin, naechsteDatei);

    AbstractImporter importer = BeanManagerUtil.getBeanByClass(importContext.getImporterClass());

    dateienImportierenBackgroundServiceDatenCreator.importiere(importer, naechsteDatei.getAbsolutePath(),
        bestimmeFileFormat(formatResolver), importContext.getDateiImportArt(),
            importContext.getMandantPK().ladeMandant(simpleDAO.getEntityManager()), importContext.getWahlOderWahlterminFuerDateiFct().apply(wahlOderWahltermin),
        importContext.getContextFct().apply(wahlOderWahltermin, naechsteDatei), importContext.getImportOptions(),
        formatResolver.getDateiImportHandler(), importContext.getDateiImportBerichtCustomData());

    formatResolver.postImport(wahlOderWahltermin, naechsteDatei);
  }

  private FileFormat bestimmeFileFormat(DateienImportExportFormatResolver formatResolver) {
    return new FileFormat(formatResolver.getFormatName(), formatResolver.getFileType());
  }

  protected String getMessage(Throwable throwable) {
    if (throwable == null) {
      return "";
    }

    if (throwable instanceof ElectCommonException) {
      ElectCommonException electCommonException = (ElectCommonException) throwable;
      return electCommonException.getLocalizedMessage();
    } else {

      StringBuilder sb = new StringBuilder();
      sb.append(getMessage("datei.import.background.error.reason"));
      sb.append(throwable.getMessage());
      sb.append("\n");

      StringWriter writer = new StringWriter();
      PrintWriter printWriter = new PrintWriter(writer, true);
      throwable.printStackTrace(printWriter);
      sb.append(writer);
      sb.append("\n");
      sb.append(getMessage(throwable.getCause()));

      return sb.toString();
    }
  }
}
