package de.ivu.elect.business.veranstaltung.boundary;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

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

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSearchHelper;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.business.veranstaltung.control.TeilnahmeExchangeContext;
import de.ivu.elect.business.veranstaltung.control.VeranstaltungCreate;
import de.ivu.elect.business.veranstaltung.control.VeranstaltungExchangeContext;
import de.ivu.elect.business.veranstaltung.control.VeranstaltungImportExchange;
import de.ivu.elect.business.veranstaltung.entity.Teilnahme;
import de.ivu.elect.business.veranstaltung.entity.Veranstaltung;
import de.ivu.elect.business.veranstaltung.entity.VeranstaltungMeta;
import de.ivu.elect.business.veranstaltung.entity.VeranstaltungTK;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;

@Stateless
public class VeranstaltungImporter extends
    AbstractEntityImporter<Veranstaltung, VeranstaltungTK, VeranstaltungExchangeContext, DateiImportBerichtCustomData>
{

  @Inject
  private transient GebietSearchHelper gebietSearchHelper;

  @Inject
  private transient VeranstaltungDAO veranstaltungDAO;

  @Inject
  private transient VeranstaltungMeta veranstaltungMeta;

  @Inject
  private transient VeranstaltungCreate veranstaltungCreate;

  @Inject
  private transient VeranstaltungImportExchange veranstaltungImportExchange;

  @Inject
  private transient TeilnahmeImporter teilnahmeImporter;

  @Override
  public Meta getMetaForEntityInternal() {
    return veranstaltungMeta;
  }

  @Override
  protected ImportExchange<List<Veranstaltung>, VeranstaltungExchangeContext> getExchange() {
    return veranstaltungImportExchange;
  }

  // NOTE: Override, um Public zu machen
  @Override
  public void importiereImportObject(List<Veranstaltung> inDatei, VeranstaltungExchangeContext context,
      ImportOptions importOptions, DateiImportBerichtCustomData customDateiImportBerichtCustomData,
      StringBuilder bericht) {
    importiereImportObject(getSeparator(inDatei, context), context, importOptions, customDateiImportBerichtCustomData,
        bericht);
  }

  private class VerzoegerteVerknuepfung {

    private Veranstaltung veranstaltung;
    private Veranstaltung verknuepfteVeranstaltung;

    private void assertBeideVeranstaltungenPersistent() {
      if (!veranstaltung.isPersistent()) {
        throw new IllegalStateException("Eine Veranstaltung aus der Datei ist nicht persistiert: " + veranstaltung);
      }
      if (!verknuepfteVeranstaltung.isPersistent()) {
        throw new IllegalStateException(
            "Eine Veranstaltung aus der Datei ist nicht persistiert: " + verknuepfteVeranstaltung);
      }
    }
  }

  @Override
  protected void persistiere(Collection<Veranstaltung> neueEntities, VeranstaltungExchangeContext context,
      DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    List<VerzoegerteVerknuepfung> verzoegerteVerknuepfungList = new ArrayList<>();

    for (Veranstaltung veranstaltung : neueEntities) {
      veranstaltung.setWahl(context.getWahl());
      veranstaltung.setWahltermin(context.getWahltermin());
      if (veranstaltung.getGebiet() == null) {
        veranstaltung.setGebiet(context.getGebiet());
      } else {
        veranstaltung.setGebiet(gebietSearchHelper.attachGebiet(context.getWahltermin(), context.getWahl(), veranstaltung.getGebiet()));
      }

      if (veranstaltung.getVerknuepfteVeranstaltung() != null && !veranstaltung.getVerknuepfteVeranstaltung()
          .isPersistent()) {
        // HINT: die Verknüpfte Veranstaltung war noch nicht an der Reihe; wir verknüpfen die Veranstaltungen später.
        verzoegerteVerknuepfungList.add(createVerzoegerteVerknuepfung(veranstaltung));
        veranstaltung.setVerknuepfteVeranstaltung(null);
      }

      veranstaltungCreate.save(veranstaltung);

      legeTeilnahmenAnFallsNoetig(veranstaltung, veranstaltung.getTeilnahmesByVeranstaltung(), context, bericht);
    }

    for (VerzoegerteVerknuepfung verknuepfung : verzoegerteVerknuepfungList) {
      // NOTE, dass wir davon ausgehen, dass die Importdatei nur Verknüpfungen innerhalb der Importdatei referenziert.
      //   Das Wahltermin-Import-Format unterstützt nichts anderes.
      verknuepfung.assertBeideVeranstaltungenPersistent();
      verknuepfung.veranstaltung.setVerknuepfteVeranstaltung(verknuepfung.verknuepfteVeranstaltung);
    }
  }

  private void legeTeilnahmenAnFallsNoetig(Veranstaltung veranstaltung, Set<Teilnahme> teilnahmeSet,
      VeranstaltungExchangeContext context, StringBuilder bericht) {
    TeilnahmeExchangeContext teilnahmeContext = createTeilnahmeExchangeContext(context);
    teilnahmeImporter.anlegenWennNichtVorhanden(veranstaltung, teilnahmeSet, teilnahmeContext,
        new DateiImportBerichtCustomData());
  }

  private TeilnahmeExchangeContext createTeilnahmeExchangeContext(VeranstaltungExchangeContext context) {
    TeilnahmeExchangeContext teilnahmeContext = new TeilnahmeExchangeContext(context.getWahltermin(), context.getWahl(),
        context.getGebiet());
    teilnahmeContext.setImportInstanceTracker(context.getImportInstanceTracker());
    teilnahmeContext.setTeilnahmePersonendatenSindBereitsPersistent(context.isTeilnahmePersonendatenSindBereitsPersistent());
    return teilnahmeContext;
  }

  private VerzoegerteVerknuepfung createVerzoegerteVerknuepfung(Veranstaltung veranstaltung) {
    VerzoegerteVerknuepfung verknuepfung = new VerzoegerteVerknuepfung();
    verknuepfung.veranstaltung = veranstaltung;
    verknuepfung.verknuepfteVeranstaltung = veranstaltung.getVerknuepfteVeranstaltung();
    return verknuepfung;
  }

  @Override
  protected void aktualisiere(Veranstaltung entityInDatei, Veranstaltung entityInDatenbank,
      VeranstaltungExchangeContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData,
      StringBuilder bericht) {
    entityInDatenbank.setBezeichnung(entityInDatei.getBezeichnung());
    entityInDatenbank.setBeginn(entityInDatei.getBeginn());
    entityInDatenbank.setEnde(entityInDatei.getEnde());

    // NOTE, dass ich hier zunächst darauf verzichte, Verknüpfungen bestehender Veranstaltungen zu aktualisieren, da
    //   die vorhandenen Importformate das ohnehin nicht hergeben.

    // NOTE, dass Teilnehmer momentan nicht mit aktualisiert werden können
  }

  @Override
  protected void loescheAusDatenbank(Collection<Veranstaltung> entitiesInDb, VeranstaltungExchangeContext context,
      DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException(
        "Momentan nicht unterstützt, cf. VeranstaltungImportDescription#getDefaultImportOptions");
  }

  @Override
  protected List<Veranstaltung> getAllEntitiesInDB(VeranstaltungExchangeContext veranstaltungExchangeContext) {
    if (veranstaltungExchangeContext.hasWahl()) {
      return veranstaltungDAO
          .findByWahlUndGebiet(veranstaltungExchangeContext.getWahl(), veranstaltungExchangeContext.getGebiet());
    }

    return veranstaltungDAO.findByWahlterminUndGebiet(veranstaltungExchangeContext.getWahltermin(),
        veranstaltungExchangeContext.getGebiet());
  }

  @Override
  protected Function<Veranstaltung, VeranstaltungTK> getEntityToEntityTKFunction() {
    return this::createEntityTK;
  }

  private VeranstaltungTK createEntityTK(Veranstaltung veranstaltung) {
    if (veranstaltung.getExterneId() == null) {
      // HINT: Ich nehme hier identityHashCode, da der garantiert eindeutig ist, solange die Instanz lebt --
      //   und nach dem Import sind die IDs ohnehin wieder weg (sollen nicht persistiert werden, da es keine "echten"
      //   Daten sind).
      String eindeutigeId = Integer.toHexString(System.identityHashCode(veranstaltung));

      // NOTE: Würden wir die externeId im TK auf <null> lassen, würde der Importer alle Veranstaltungen ohne
      //   externeId als identisch ansehen. So werden Veranstaltungen von Importformaten, die ohne externeId
      //   daherkommen, eben doppelt importiert.
      String externeId = getClass().getName() + ":temporäreId:" + eindeutigeId;

      GebietTK gebietTK = veranstaltung.getGebiet() == null ? null : veranstaltung.getGebiet().getGebietTK();
      return new VeranstaltungTK(externeId, gebietTK);
    }

    return veranstaltung.getVeranstaltungTK();
  }

  @Override
  public void validiere(Violations violations, List<Veranstaltung> inDatei, VeranstaltungExchangeContext context,
      ImportOptions importOptions) {
    validiereVeranstaltungen(violations, inDatei, context);
    // validiereEntitiesNichtMehrfachInDatei(violations, inDatei, context); elect: Egal, es gibt ohnehin keine Unique Constraints
  }

  private void validiereVeranstaltungen(Violations violations, List<Veranstaltung> inDateiList,
      VeranstaltungExchangeContext context) {
    for (Veranstaltung inDatei : inDateiList) {
      validateEntity(violations, inDatei, inDatei.getBezeichnung());
      //ELECT-8917  teilnahmeImporter.validateTeilnahmenFuerVeranstaltung(violations, inDatei, inDatei.getTeilnahmesByVeranstaltung(),createTeilnahmeExchangeContext(context));
    }
  }

  @Override
  public void erstelleBericht(List<Veranstaltung> veranstaltungs,
      VeranstaltungExchangeContext veranstaltungExchangeContext, ImportOptions importOptions, StringBuilder bericht,
      boolean vorab) {
    // nichts zu tun
  }
}
