
package de.ivu.elect.exchange.gebietsbaum.csv;

import static org.apache.commons.lang3.StringUtils.isBlank;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.control.Gebietsbaum;
import de.ivu.elect.business.gebietsbaum.control.GebietsbaumExchangeContext;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.exchange.gebietsbaum.GebietDTO;
import de.ivu.elect.exchange.gebietsbaum.GebietDTOMapper;
import de.ivu.elect.exchange.gebietsbaum.GebietsSchluessel;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;

public abstract class AbstractGebietsbaumVonGebietDTOImportMapper<T extends GebietDTO> extends AbstractGebietsbaumImportMapper {

  @Inject
  private transient GebietDTOMapper gebietDTOMapper;

  protected AbstractGebietsbaumVonGebietDTOImportMapper() {
    super();
  }

  @Override
  protected boolean isAcceptContextWurzelgebietNichtVorhanden(GebietsbaumExchangeContext context) {
    return true;
  }

  @Override
  public void addProperties(FormBuilder formBuilder, GebietsbaumExchangeContext context, @Nullable Datei datei) {
    super.addProperties(formBuilder, context, datei);

    gebietDTOMapper.addProperties(formBuilder, context);
  }

  protected List<Gebiet> mapGebietsbaum(List< ? extends T> gebiete, GebietsbaumExchangeContext context) throws MappingException {
    List<Gebiet> result = mapGebieteOrderedByListPosition(gebiete, context);

    erstelleBaumHierarchie(gebiete, result);

    return result;
  }

  private List<Gebiet> mapGebieteOrderedByListPosition(List< ? extends T> gebiete, GebietsbaumExchangeContext context) {
    List<Gebiet> result = new ArrayList<>();

    for (int i = 0; i < gebiete.size(); i++) {
      Gebiet gebiet = createGebiet(context, gebiete.get(i));

      // Reihenfolge wie in der Datei erhalten
      gebiet.setPosition((long) i);

      result.add(gebiet);
    }

    return result;
  }

  public Gebiet createGebiet(GebietsbaumExchangeContext context, GebietDTO dto) {
    Gebietsart gebietsart = dto.getGebietsart(dto.getGebietsartName());
    Gebiet gebiet = createGebietOrBezirk(context, dto);

    setBesondereBezirkeFlags(dto, gebiet);

    gebiet.setWahl(context.getWahl());
    gebiet.setWahltermin(context.getWahltermin());

    gebiet.setBearbeitenRolle(createRolle(dto.getRolleBearbeiten()));
    gebiet.setEinsehenRolle(createRolle(dto.getRolleEinsehen()));

    gebiet.setExterneId(dto.getExterneId());
    gebiet.setNummer(dto.getNummer());
    gebiet.setNummerBezirk(dto.getNummerBezirk());
    gebiet.setName(dto.getName());

    gebiet.setGebietsart(gebietsart);

    // FIXME ELECT-2958: Vielleicht müssen die Daten irgendwo anders ebenfalls gesetzt werden?

    Anschrift anschrift = gebiet.getAnschrift();

    anschrift.setStrasse(dto.getAnschriftStrasse());
    anschrift.setHausnummer(dto.getAnschriftHausnummer());
    anschrift.setHausnummerzusatz(dto.getAnschriftHausnummerzusatz());

    anschrift.setOrt(dto.getAnschriftOrt());
    anschrift.setOrtsteil(dto.getAnschriftOrtsteil());
    anschrift.setPlz(dto.getAnschriftPlz());

    anschrift.setZustellanweisung(dto.getAnschriftZustellanweisung());

    Personendaten ansprechpartner = gebiet.getAnsprechpartner();
    ansprechpartner.setAnrede(dto.getAnsprechpartnerAnrede());
    ansprechpartner.setRufname(dto.getAnsprechpartnerVorname());
    ansprechpartner.setFamilienname(dto.getAnsprechpartnerFamilienname());
    ansprechpartner.setBerufUnformatiert(dto.getAnsprechpartnerBerufUnformatiert());
    ansprechpartner.setTelefonnummerFestnetz(dto.getAnsprechpartnerTelefonnummerFestnetz());
    ansprechpartner.setTelefonnummerMobil(dto.getAnsprechpartnerTelefonnummerMobil());
    ansprechpartner.setBemerkung(dto.getBemerkung());

    dto.accept(gebiet);

    return gebiet;
  }

  @Nullable
  private Rolle createRolle(@Nullable String rollenName) {
    if (rollenName == null || isBlank(rollenName)) {
      return null;
    }

    Rolle rolle = new Rolle();
    rolle.setName(rollenName.trim());

    return rolle;
  }

  @Nullable
  public Gebiet createUebergeordnetesGebiet(GebietsbaumExchangeContext context, GebietDTO dto) {
    if (isBlank(dto.getUebergeordnetesGebietNummer()) && (dto.getUebergeordnetesGebietGebietsart() == null || isBlank(dto.getUebergeordnetesGebietGebietsart().getName()))) {
      return null;
    }

    Gebiet gebiet = gebietFactory.create();

    gebiet.setWahl(context.getWahl());
    gebiet.setWahltermin(context.getWahltermin());

    gebiet.setNummer(dto.getUebergeordnetesGebietNummer());
    gebiet.setGebietsart(dto.getUebergeordnetesGebietGebietsart());

    return gebiet;
  }

  private void setBesondereBezirkeFlags(GebietDTO dto, Gebiet gebiet) {
    gebiet.setSonderstimmbezirk(dto.getSonderstimmbezirk());

    gebiet.setAuswahlbezirk(dto.getAuswahlbezirk2());
    gebiet.setUmfragebezirk(dto.getUmfragebezirk2());

    gebiet.setWahlbeteiligungsbezirk(dto.getWahlbeteiligungsbezirk2());

    gebiet.setBeweglicherWahlvorstand(dto.getBeweglicherWahlvorstand2());
    gebiet.setBriefwahlInUrneErfassen(dto.getBriefwahlInUrneErfassen2());
  }

  /**
   * Verknüpft die Gebiete bzw. setzt die übergeordneten Gebiete
   */
  protected void erstelleBaumHierarchie(List< ? extends GebietDTO> gebiete, List<Gebiet> mappedGebieteInRichtigerReihenfolge) {
    Map<GebietsSchluessel, Gebiet> schluesselToGebietMap = erstelleSchluesselToGebietMap(mappedGebieteInRichtigerReihenfolge);

    for (GebietDTO dto : gebiete) {
      setzeUebergeordnetesGebiet(schluesselToGebietMap, dto);
    }
  }

  private Map<GebietsSchluessel, Gebiet> erstelleSchluesselToGebietMap(List<Gebiet> mappedGebieteInRichtigerReihenfolge) {
    Map<GebietsSchluessel, Gebiet> schluesselToGebietMap = new HashMap<>();

    for (Gebiet gebiet : mappedGebieteInRichtigerReihenfolge) {
      schluesselToGebietMap.put(createGebietsSchluessel(gebiet), gebiet);
    }

    return schluesselToGebietMap;
  }

  private void setzeUebergeordnetesGebiet(Map<GebietsSchluessel, Gebiet> schluesselToGebietMap, GebietDTO dto) {
    if (dto.getUebergeordnetesGebietNummer() != null) {
      GebietsSchluessel gebietsSchluessel = createGebietsSchluessel(dto);
      Gebiet gebiet = schluesselToGebietMap.get(gebietsSchluessel);

      if (gebiet == null) {
        throw new IllegalStateException(
            "Gebiet nicht gefunden: " + gebietsSchluessel + ", key vorhanden: " + schluesselToGebietMap.containsKey(gebietsSchluessel) + ", habe: " + new ArrayList<>(schluesselToGebietMap.keySet()));
      }

      Gebiet uebergeordnetesGebiet = schluesselToGebietMap.get(createGebietsSchluesselUebergeordnetesGebiet(dto));
      gebiet.setUebergeordnetesGebiet(uebergeordnetesGebiet);
    }
  }

  private GebietsSchluessel createGebietsSchluesselUebergeordnetesGebiet(GebietDTO dto) {
    return new GebietsSchluessel(dto.getUebergeordnetesGebietNummer(), dto.getGebietsart(dto.getUebergeordnetesGebietGebietsartName()).getName());
  }

  private GebietsSchluessel createGebietsSchluessel(Gebiet gebiet) {
    return new GebietsSchluessel(gebiet.getNummer(), gebiet.getGebietsart().getName());
  }

  private GebietsSchluessel createGebietsSchluessel(GebietDTO dto) {
    return new GebietsSchluessel(dto.getNummer(), dto.getGebietsart(dto.getGebietsartName()).getName());
  }

  protected Gebiet createGebietOrBezirk(GebietsbaumExchangeContext context, GebietDTO dto) {
    return gebietDTOMapper.createGebietOrBezirk(context, dto);
  }

  @Override
  public Gebietsbaum deserialize(InputStream is, GebietsbaumExchangeContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    return new Gebietsbaum(wahlFactory.create(), mapGebietsbaum(deserializeDtos(is, fileKonfiguration), context));
  }

  protected abstract List< ? extends T> deserializeDtos(InputStream is, FileKonfiguration fileKonfiguration);

  @Override
  public Violations validateInputFormat(InputStream is, GebietsbaumExchangeContext context, FileKonfiguration fileKonfiguration) {
    return new Violations();
  }
}
