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

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

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

import de.ivu.elect.business.ElectSystemProperties;
import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.dokumente.boundary.DokumentVorlageDAO;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.control.StimmergebnisDatenCreator;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.business.mandant.entity.MandantPK;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.boundary.WahlEdit;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.boundary.WasWahlImporter;
import de.ivu.elect.business.wahl.entity.WahlImportContext;
import de.ivu.elect.business.wahl.entity.WahlPK;
import de.ivu.elect.business.wahl.entity.WahlaufteilenResultContainer;
import de.ivu.elect.jee.exchange.ImportOptions;

@Stateless
public class WahlMandantenAufteilenSynchron {

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

  private static final long ANZAHL_MANDANTEN_PRO_TRANSAKTION = 10L;

  @Inject
  private transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  private transient StimmergebnisDatenCreator stimmergebnisDatenCreator;

  @Inject
  private transient DokumentVorlageDAO dokumentVorlageDAO;

  @Inject
  private transient MandantDAO mandantDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient WahlAufteiler wahlAufteiler;

  @Inject
  private transient WahlAufteilerExportImportMapper wahlAufteilerExportImportMapper;

  @Inject
  private transient WasWahlImporter wasWahlImporter;

  @Inject
  private transient ErgebnisDAO ergebnisDAO;

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient WahlEdit wahlEdit;

  @Inject
  private transient WahlMandantenAufteilenFilter wahlMandantenAufteilenFilter;

  @Inject
  private transient WahlMandantenAufteilenStatusErmittler wahlMandantenAufteilenStatusErmittler;

  public Map<MandantPK, WahlaufteilenResultContainer> aufteilen(WahlPK wahlPK, Collection<MandantPK> mandantPKS) {

    LOG.info("Aufteilen: " + mandantPKS + ", wahlPK=" + wahlPK);
    var wahl = wahldurchfuehrungDAO.findByPrimaryKey(wahlPK);

    Map<MandantPK, WahlaufteilenResultContainer> result = new HashMap<>();

    for (var mandantPK : mandantPKS) {
      var wahlaufteilenStatusContainer = wahlMandantenAufteilenStatusErmittler.bestimmeStatusFuerMandant(wahlPK, mandantPK);
      if (wahlaufteilenStatusContainer.getWahlaufteilenStatus().isAufteilenMoeglich()) {
        var mandant = mandantDAO.findByPrimaryKey(mandantPK);

        var stimmergebnisDaten = stimmergebnisDatenCreator.createErgebnisDatenExporte(wahl.bestimmeZuBerechnendesErgebnis(ergebnisDAO));
        var dokumentvorlagen = dokumentVorlageDAO.findByWahlOrderByArtAndName(wahl);

        // ELECTKOM-446 List<WahlParameter> wahlParameters = wahlParameterDAO.findByWahl(wahl);

        LOG.info("Aufteilen für Mandant: " + mandant);

        var wurzelGebiete = wahlaufteilenStatusContainer.getWurzelgebiete().stream().map(pk -> wahldurchfuehrungsgebietDAO.findByPrimaryKey(pk))
            .collect(Collectors.toList());

        var wahldurchfuehrungsgebiet = bestimmeGemeinsamesObergebiet(wurzelGebiete);

        // Neues Wurzelgebiet
        var wahlAufgeteilt = wahlAufteiler.erzeugeStimmergebnisMappedFuerAufgeteiltesGebiet(stimmergebnisDaten, wahldurchfuehrungsgebiet,
            wahlMandantenAufteilenFilter.getFilter(wahldurchfuehrungsgebiet, wurzelGebiete));

        var stimmergebnisDatenFuerMandant = wahlAufgeteilt.getStimmergebnisDaten();
        // Objekte von der Datenbank lösen
        var wahlImportObject = wahlAufteilerExportImportMapper
            .copyTo(new WahlExportObject(stimmergebnisDatenFuerMandant, dokumentvorlagen, wahlAufgeteilt.getWahlParameter()));
        // Importieren
        var wahlImportContext = new WahlImportContext(mandant);
        var customDateiImportBericht = new DateiImportBerichtCustomData();
        wasWahlImporter
            .importieren(wahlImportObject, ImportOptions.NEUE_DATEN_ANLEGEN_BESTEHENDE_DATEN_UNVERAENDERT_LASSEN, customDateiImportBericht, wahlImportContext);
        var wahlFuerMandant = wahldurchfuehrungDAO.findByPrimaryKey(wahlImportContext.getWahlPK());
        wahlFuerMandant.setBerichtetAn(wahl);
        result.put(mandantPK, new WahlaufteilenResultContainer(true, customDateiImportBericht));

        LOG.info("Aufteilen für Mandant: " + mandant + " - done");
      } else {
        result.put(mandantPK, new WahlaufteilenResultContainer(false, new DateiImportBerichtCustomData()));
      }
    }
    LOG.info("Aufteilen: " + mandantPKS + ", wahlPK=" + wahlPK + " - done");
    return result;
  }

  @Nullable
  private Wahldurchfuehrungsgebiet bestimmeGemeinsamesObergebiet(List<Wahldurchfuehrungsgebiet> gebiete) {
    if (gebiete.isEmpty()) {
      return null;
    }
    return bestimmeGemeinsamesObergebiet(gebiete, gebiete.get(0));
  }

  @Nullable
  private Wahldurchfuehrungsgebiet bestimmeGemeinsamesObergebiet(List<Wahldurchfuehrungsgebiet> gebiete, Gebiet gebiet) {
    if (gebiet == null) {
      return null;
    }
    if (gebiete.stream().allMatch(gebiet::isInGebietshierarchieUebergeordnetesGebietVon)) {
      return gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
    }
    return bestimmeGemeinsamesObergebiet(gebiete, gebiet.getUebergeordnetesGebiet());
  }

  public long bestimmeAnzahlMandantenProTransaktion() {
    try {
      var l = Long.parseLong(System.getProperty(ElectSystemProperties.ELECT_WAHL_AUFTEILEN_MANDANTEN_PRO_TRANSAKTION));
      if (l > 0) {
        return l;
      }
    } catch (Exception e) {
      LOG.warn("Fehler beim Bestimmen der Anzahl der Mandanten pro Transaktion: " + e.getMessage());
    }
    return ANZAHL_MANDANTEN_PRO_TRANSAKTION;
  }

  public void loeschen(WahlPK wahlPK, Collection<MandantPK> mandantPKS) {
    LOG.info("Löschen: " + mandantPKS + ", wahlPK=" + wahlPK);
    var wahl = wahldurchfuehrungDAO.findByPrimaryKey(wahlPK);
    for (var mandantPK : mandantPKS) {
      var mandant = mandantDAO.findByPrimaryKey(mandantPK);
      for (var wahlZuloeschen : wahlDAO.findByBerichtetAnAndMandant(wahl, mandant)) {
        LOG.info("Lösche Wahl: " + wahlZuloeschen);
        wahlEdit.delete(wahlZuloeschen);
      }
    }
    LOG.info("Löschen: " + mandantPKS + ", wahlPK=" + wahlPK + " - done");
  }
}
