
package de.ivu.elect.wus.exchange.wahlvorschlag.csv;

import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatus;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.boundary.GebietFactory;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeFactory;
import de.ivu.elect.business.gruppe.entity.GruppeArt;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietFactory;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.kandidatur.boundary.DirektkandidaturFactory;
import de.ivu.elect.business.kandidatur.boundary.ErsatzkandidaturFactory;
import de.ivu.elect.business.kandidatur.boundary.ListenkandidaturFactory;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImportContext;
import de.ivu.elect.business.kandidatur.entity.*;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.boundary.PersonendatenVerknuepfer;
import de.ivu.elect.business.person.entity.VerknuepfungsArt;
import de.ivu.elect.business.person.entity.VerknuepfungsStatus;
import de.ivu.elect.business.staat.boundary.StaatFactory;
import de.ivu.elect.business.staat.entity.Staat;
import de.ivu.elect.business.wahlvorschlag.boundary.DirektkandidatFactory;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeFactory;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;

import java.util.List;
import java.util.Map;

public abstract class AbstractWahlvorschlagCsvExternInternMapper<T extends WahlvorschlagCsvDTO> {

  @Inject
  protected transient StaatFactory staatFactory;

  @Inject
  protected transient GebietFactory gebietFactory;

  @Inject
  protected transient PolitischeGruppeFactory politischeGruppeFactory;

  @Inject
  protected transient GruppeImGebietFactory gruppeImGebietFactory;

  @Inject
  protected transient ListeFactory listeFactory;

  @Inject
  protected transient ListenkandidaturFactory listenkandidaturFactory;

  @Inject
  protected transient DirektkandidatFactory direktkandidatFactory;

  @Inject
  protected transient DirektkandidaturFactory direktkandidaturFactory;

  @Inject
  protected transient ErsatzkandidaturFactory ersatzkandidaturFactory;

  @Inject
  protected transient KandidaturAttributePraesentationFormatter kandidaturAttributePraesentationFormatter;

  @Inject
  protected transient PersonendatenVerknuepfer personendatenVerknuepfer;

  protected AbstractWahlvorschlagCsvExternInternMapper() {
    super();
  }

  public Liste mapToListeOhneListenkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    Liste liste = listeFactory.create();

    fillWahlvorschlag(wahlvorschlagCsv, liste, context);

    return liste;
  }

  protected void fillWahlvorschlag(T wahlvorschlagCsv, Wahlvorschlag wahlvorschlag, WahlvorschlagImportContext context) throws MappingException {
    wahlvorschlag.setGruppeImGebiet(mapToGruppeImGebiet(wahlvorschlagCsv, context));

    wahlvorschlag.setKurzname(wahlvorschlagCsv.getWahlvorschlagKurzname());
    wahlvorschlag.setKennwort(wahlvorschlagCsv.getWahlvorschlagKennwort());
    wahlvorschlag.setZusatzbezeichnung(wahlvorschlagCsv.getWahlvorschlagZusatzbezeichnung());
  }

  protected GruppeImGebiet mapToGruppeImGebiet(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    GruppeImGebiet gruppeImGebiet = gruppeImGebietFactory.create();

    gruppeImGebiet.setGruppe(mapToGruppe(wahlvorschlagCsv, context));
    gruppeImGebiet.setGebiet(mapToGebiet(wahlvorschlagCsv, context));

    fillPositionenWahlvorschlag(wahlvorschlagCsv, gruppeImGebiet);

    if (gruppeImGebiet.getGruppe().getRelativePosition() == null) {
      gruppeImGebiet.getGruppe().setRelativePosition(gruppeImGebiet.getRelativePosition());
    }

    return gruppeImGebiet;
  }

  protected void fillPositionenWahlvorschlag(T wahlvorschlagCsv, GruppeImGebiet gruppeImGebiet) {
    gruppeImGebiet.setVorgegebenePosition(wahlvorschlagCsv.getWahlvorschlagPosition());
    gruppeImGebiet.setEingereichtePosition(wahlvorschlagCsv.getWahlvorschlagPosition());
    gruppeImGebiet.setRelativePosition(wahlvorschlagCsv.getWahlvorschlagPosition());
  }

  protected Gebiet mapToGebiet(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    // wenn es nur ein einzigies Listen/Direktwahlgebiet im System gibt,
    // werden alle Listen/Direktkandidaturen automatisch daran angehängt.
    return createGebiet(mapToGebietsart(wahlvorschlagCsv, context), mapToGebietsnummer(wahlvorschlagCsv));
  }

  protected abstract String mapToGebietsart(T wahlvorschlagCsv, WahlvorschlagImportContext context);

  protected String mapToGebietsnummer(T wahlvorschlagCsv) {
    return wahlvorschlagCsv.getGebietsnummer();
  }

  protected Gebiet createGebiet(String gebietsartName, String gebietsnummer) {
    Gebietsart gebietsart = new Gebietsart(); // nicht zum Persistieren, nur zum späteren Wiederfinden anhand des TKs
    gebietsart.setName(gebietsartName);

    Gebiet gebiet = gebietFactory.create();
    gebiet.setGebietsart(gebietsart);
    gebiet.setNummer(gebietsnummer);

    return gebiet;
  }

  protected PolitischeGruppe mapToGruppe(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    return createPolitischeGruppe(mapToGruppeSchluessel(wahlvorschlagCsv, context.getMandant()), mapToGruppeArt(wahlvorschlagCsv),
        wahlvorschlagCsv.getWahlvorschlagKennwort(),
        wahlvorschlagCsv.getWahlvorschlagKurzname(),
        wahlvorschlagCsv.getWahlvorschlagZusatzbezeichnung());
  }

  protected abstract String mapToGruppeSchluessel(T wahlvorschlagCsv, Mandant mandant);

  protected PolitischeGruppe createPolitischeGruppe(String gruppeSchluessel, GruppeArt gruppeArt, String kennwort, String kurzname, String zusatzbezeichnung) {
    PolitischeGruppe gruppe = createPolitischeGruppeEntity();

    gruppe.setSchluessel(gruppeSchluessel);
    gruppe.setName(kennwort);
    gruppe.setKurzname(kurzname);
    gruppe.setGruppeArt(gruppeArt);
    gruppe.setZusatzbezeichnung(zusatzbezeichnung);

    return gruppe;
  }

  protected PolitischeGruppe createPolitischeGruppeEntity() {
    return politischeGruppeFactory.create();
  }

  protected abstract GruppeArt mapToGruppeArt(T wahlvorschlagCsv) throws MappingException;

  public Listenkandidatur mapToListenkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    return mapToListenkandidatur(wahlvorschlagCsv, context, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung.ERSTELLE_PERSONEN_VERKNUEPFUNGEN);
  }

  protected Listenkandidatur mapToListenkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung dublettenVerarbeitung)
      throws MappingException {
    Listenkandidatur listenkandidatur = listenkandidaturFactory.create();

    fillPositionenListenkandidatur(wahlvorschlagCsv, listenkandidatur);
    Long kandidaturAnzahlNennungen = wahlvorschlagCsv.getKandidaturAnzahlNennungen();
    listenkandidatur.setAnzahlNennungen(kandidaturAnzahlNennungen);

    if (kandidaturAnzahlNennungen != null) { //hasKandidaturDubletteListenposition
      listenkandidatur.setListenkandidaturArt(kandidaturAnzahlNennungen == 0 ? ListenkandidaturArt.ERSATZKANDIDATUR : ListenkandidaturArt.LISTENKANDIDATUR);
    }

    fillKandidatur(wahlvorschlagCsv, listenkandidatur, context, dublettenVerarbeitung);

    return listenkandidatur;
  }

  protected void fillPositionenListenkandidatur(T wahlvorschlagCsv, Listenkandidatur listenkandidatur) {
    listenkandidatur.setRelativePosition(wahlvorschlagCsv.getKandidaturListenposition());
    listenkandidatur.setVorgegebenePosition(wahlvorschlagCsv.getKandidaturListenposition());
  }

  public Direktkandidat mapToDirektkandidat(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    Direktkandidat direktkandidat = direktkandidatFactory.create();

    fillWahlvorschlag(wahlvorschlagCsv, direktkandidat, context);
    direktkandidat.setDirektkandidatur(mapToDirektkandidatur(wahlvorschlagCsv, context, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung.ERSTELLE_PERSONEN_VERKNUEPFUNGEN));

    return direktkandidat;
  }

  protected Direktkandidatur mapToDirektkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung dublettenVerarbeitung)
      throws MappingException {
    Direktkandidatur direktkandidatur = direktkandidaturFactory.create();

    fillKandidatur(wahlvorschlagCsv, direktkandidatur, context, dublettenVerarbeitung);

    return direktkandidatur;
  }

  public Ersatzkandidatur mapToErsatzkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, Map<GruppeImGebietTK, List<Wahlvorschlag>> wahlvorschlaegeProGruppeImGebiet) throws MappingException {
    return mapToErsatzkandidatur(wahlvorschlagCsv, context, wahlvorschlaegeProGruppeImGebiet, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung.ERSTELLE_PERSONEN_VERKNUEPFUNGEN);
  }

  protected Ersatzkandidatur mapToErsatzkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, Map<GruppeImGebietTK, List<Wahlvorschlag>> wahlvorschlaegeProGruppeImGebiet, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung dublettenVerarbeitung)
      throws MappingException {
    Ersatzkandidatur ersatzkandidatur = ersatzkandidaturFactory.create();

    fillKandidatur(wahlvorschlagCsv, ersatzkandidatur, context, dublettenVerarbeitung);

    Kandidatur uebergeordneteKandidatur = null;
    List<Wahlvorschlag> wahlvorschlaege = wahlvorschlaegeProGruppeImGebiet.get(mapToGruppeImGebiet(wahlvorschlagCsv, context).getGruppeImGebietTK());
    if (wahlvorschlaege != null) {
      if (wahlvorschlagCsv.getKandidaturListenposition() == null) {
        // Ersatzkandidatur für Direktkandidatur
        uebergeordneteKandidatur = wahlvorschlaege.stream().filter(Wahlvorschlag::isDirektkandidat).findFirst() //
            .map(wahlvorschlag -> wahlvorschlag.castTo(Direktkandidat.class).getEntity().getDirektkandidatur())
            .orElse(null);
      } else {
        // Ersatzkandidatur für Listenkandidatur
        uebergeordneteKandidatur = wahlvorschlaege.stream().filter(Wahlvorschlag::isListe).findFirst() //
            .flatMap(wahlvorschlag -> wahlvorschlag.castTo(Liste.class).getEntity().getListenkandidatursByListe().stream()
                .filter(listenkandidatur -> wahlvorschlagCsv.getKandidaturListenposition().equals(listenkandidatur.getEingereichtePosition()))
                .findFirst())
            .orElse(null);
      }
    }

    if (uebergeordneteKandidatur == null) {
      throw new MappingException("Die übergeordnete Kandidatur für den folgenden Ersatzkandidatur-Eintrag fehlt in der Datei: " + wahlvorschlagCsv);
    } else {
      uebergeordneteKandidatur.addErsatzkandidaturByUebergeordneteKandidatur(ersatzkandidatur);
      ersatzkandidatur.setUebergeordneteKandidatur(uebergeordneteKandidatur);
    }

    return ersatzkandidatur;
  }

  protected void fillKandidatur(T wahlvorschlagCsv, Kandidatur kandidatur, WahlvorschlagImportContext context, WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung dublettenVerarbeitung)
      throws MappingException {
    kandidatur.setTitel(wahlvorschlagCsv.getKandidaturTitel());
    kandidatur.setFamiliennameBestandteil(wahlvorschlagCsv.getKandidaturNamensbestandteile());
    kandidatur.setFamilienname(wahlvorschlagCsv.getKandidaturFamilienname());
    fillRufnameUndVornamen(wahlvorschlagCsv, kandidatur);

    // NOTE, dass die Namensbestandteile des Familiennamens momentan nicht in den Daten enthalten sind!
    kandidatur.setNamePraesentation(
        kandidaturAttributePraesentationFormatter.formatNamePraesentation(kandidatur.getTitel(), kandidatur.getRufname(), kandidatur.getVornamen(),
        kandidatur.getFamilienname(), kandidatur.getFamiliennameBestandteil(), kandidatur.getKuenstlername()));

    kandidatur.setGeburtsdatum(wahlvorschlagCsv.getKandidaturGeburtsdatum(context.getLocale()));

    kandidatur.setBerufUnformatiert(wahlvorschlagCsv.getKandidaturBeruf());
    kandidatur.setExterneId(wahlvorschlagCsv.getExterneId());

    fillGewaehlteVorperiode(wahlvorschlagCsv, kandidatur);
  }

  protected void fillRufnameUndVornamen(T wahlvorschlagCsv, Kandidatur kandidatur) {
    kandidatur.setRufname(wahlvorschlagCsv.getKandidaturVorname());
    kandidatur.setVornamen(wahlvorschlagCsv.getKandidaturVorname());
  }

  protected enum DublettenVerarbeitungsAnweisung {
    ERSTELLE_PERSONEN_VERKNUEPFUNGEN,
    IGNORIERE_DUBLETTEN
  }

  protected void fillDublette(T wahlvorschlagCsv, Kandidatur kandidatur, WahlvorschlagImportContext context, DublettenVerarbeitungsAnweisung dublettenVerarbeitung) throws MappingException {
    if (dublettenVerarbeitung.equals(WahlvorschlagCsvExternInternMapper.DublettenVerarbeitungsAnweisung.ERSTELLE_PERSONEN_VERKNUEPFUNGEN) && wahlvorschlagCsv.hasKandidaturDublette()) {
      personendatenVerknuepfer.verknuepfeManuell(VerknuepfungsArt.WAHLVORSCHLAG_DUBLETTE, kandidatur, mapToVerknuepfteKandidatur(wahlvorschlagCsv, context), VerknuepfungsStatus.IDENTISCH);
    }
  }

  protected Kandidatur mapToVerknuepfteKandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    Kandidatur result;

    // NOTE, dass die GruppeImGebiet nur gesetzt wird, damit der Importer später den TK zuordnen kann
    GruppeImGebiet gruppeImGebiet = gruppeImGebietFactory.create();
    gruppeImGebiet.setGebiet(mapKandidaturDubletteToGebiet(wahlvorschlagCsv));
    gruppeImGebiet.setGruppe(mapKandidaturDubletteToGruppe(wahlvorschlagCsv, context));

    if (wahlvorschlagCsv.hasKandidaturDubletteListenposition()) {
      Listenkandidatur listenkandidatur = mapToListenkandidatur(wahlvorschlagCsv, context, DublettenVerarbeitungsAnweisung.IGNORIERE_DUBLETTEN);

      listenkandidatur.setRelativePosition(wahlvorschlagCsv.getKandidaturDubletteListenposition());
      listenkandidatur.setVorgegebenePosition(wahlvorschlagCsv.getKandidaturDubletteListenposition());

      // NOTE, dass die Liste nur gesetzt wird, damit der Importer später den TK zuordnen kann
      Liste liste = listeFactory.create();
      liste.setGruppeImGebiet(gruppeImGebiet);
      listenkandidatur.setListe(liste);

      result = listenkandidatur;
    } else {
      result = mapToVerknuepfteDirektkandidatur(wahlvorschlagCsv, context, gruppeImGebiet);
    }

    return result;
  }

  protected Kandidatur mapToVerknuepfteDirektkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, GruppeImGebiet gruppeImGebiet) throws MappingException {
    Direktkandidatur direktkandidatur = mapToDirektkandidatur(wahlvorschlagCsv, context, DublettenVerarbeitungsAnweisung.IGNORIERE_DUBLETTEN);

    // NOTE, dass der Direktkandidat nur gesetzt wird, damit der Importer später den TK zuordnen kann
    Direktkandidat direktkandidat = direktkandidatFactory.create();
    direktkandidat.setGruppeImGebiet(gruppeImGebiet);
    direktkandidatur.setDirektkandidat(direktkandidat);

    return direktkandidatur;
  }

  private Gebiet mapKandidaturDubletteToGebiet(T wahlvorschlagCsv) {
    return createGebiet(wahlvorschlagCsv.getKandidaturDubletteGebietsart(), mapToKandidaturDubletteGebietsnummer(wahlvorschlagCsv));
  }

  protected String mapToKandidaturDubletteGebietsnummer(T wahlvorschlagCsv) {
    return wahlvorschlagCsv.getKandidaturDubletteGebietsnummer();
  }

  protected PolitischeGruppe mapKandidaturDubletteToGruppe(T wahlvorschlagCsv, WahlvorschlagImportContext context) {
    String gruppeSchluessel = wahlvorschlagCsv.hasKandidaturDubletteGruppeSchluessel() ? mapToKandidaturDubletteGruppeSchluessel(wahlvorschlagCsv) : mapToGruppeSchluessel(wahlvorschlagCsv,
        context.getMandant());

    // Fragwürdiger Code: Hier geht es um die Gruppe der Zweitkandidatur, aber es werden anscheinend die Werte der Erstkandidatur verwendet???
    if (wahlvorschlagCsv.isEinzelbewerber()) {
      return createPolitischeGruppe(gruppeSchluessel, GruppeArt.EINZELBEWERBER, wahlvorschlagCsv.getWahlvorschlagKennwort(),
          wahlvorschlagCsv.getWahlvorschlagKurzname(), wahlvorschlagCsv.getWahlvorschlagZusatzbezeichnung());
    }

    return createPolitischeGruppe(gruppeSchluessel, GruppeArt.PARTEI, wahlvorschlagCsv.getWahlvorschlagKennwort(), wahlvorschlagCsv.getWahlvorschlagKurzname(),
        wahlvorschlagCsv.getWahlvorschlagZusatzbezeichnung());
  }

  protected String mapToKandidaturDubletteGruppeSchluessel(T wahlvorschlagCsv) {
    return wahlvorschlagCsv.getKandidaturDubletteGruppeSchluessel();
  }

  protected void fillGewaehlteVorperiode(T wahlvorschlagCsv, Kandidatur kandidatur) {
    if (wahlvorschlagCsv.getGewaehltVorperiode() == null) {
      return;
    }

    if (!kandidatur.hasGewaehltVorperiode()) {
      Gewaehlt gewaehlt = new Gewaehlt(EmptyCustomData.INSTANCE);
      kandidatur.setGewaehltVorperiode(gewaehlt);

      Ergebnis ergebnis = new Ergebnis();
      ergebnis.setErgebnisart(Ergebnisart.VORPERIODE);
      gewaehlt.setErgebnis(ergebnis);
    }

    kandidatur.getGewaehltVorperiode().setGewaehltStatus(GewaehltStatus.valueOf(wahlvorschlagCsv.getGewaehltVorperiode()));
  }

  protected Staat mapToStaatsangehoerigkeit(String customCodeStaatsangehoerigkeit) {
    Staat staat = staatFactory.create();
    staat.setCustomCodeStaatsangehoerigkeit(customCodeStaatsangehoerigkeit);

    return staat;
  }
}
