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

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Transient;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.boundary.WurzelgebietNotUniqueEvent;
import de.ivu.elect.business.gebietsbaum.control.EntityMitWurzelgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsarteigenschaft;
import de.ivu.elect.business.mandant.entity.EntityMitMandant;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.wahl.boundary.AbstractWahlDAO;
import de.ivu.elect.business.wahl.boundary.WahlartCreateTemplate;
import de.ivu.elect.jee.cdi.BeanManagerUtil;
import de.ivu.elect.jee.faces.FacesMessageUtil;
import de.ivu.elect.util.FileUtil;


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

public abstract class AbstractWahl implements WahlBase, WahlOderWahltermin {

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

  private static final long serialVersionUID = 1L;

  private transient Boolean wahlMitWahlgebieten;

  private transient Boolean wahlMitListengebieten;

  private transient Boolean wahlMitDirektkandidatengebieten;

  private transient Boolean wahlMitReferendengebieten;

  private transient Boolean wahlMitListenkandidatenStimmen;

  protected AbstractWahl() {
    super();
  }

  public boolean hasFarbe() {
    return !isBlank(getFarbe());
  }

  @Override
  @Nullable
  public Date getWahldatum() {
    if (getWahltermin() != null) {
      return getWahltermin().getWahldatum();
    }

    return null;
  }

  @Override
  @Nullable
  public String getWahlterminNummer() {
    if (getWahltermin() != null) {
      return getWahltermin().getNummer();
    }

    return null;
  }

  @Override
  public int getWahljahr() {
    if (getWahltermin() != null) {
      return getWahltermin().getWahljahr();
    }

    return 0;
  }

  @Override
  @Nullable
  public Date getStichwahlWahldatum() {
    if (getWahltermin() != null) {
      return getWahltermin().getStichwahlWahldatum();
    }

    return null;
  }

  @Override
  @Nullable
  public Gebiet getWurzelgebiet(EntityMitWurzelgebietDAO entityMitWurzelgebietDAO) {
    try {
      return entityMitWurzelgebietDAO.getWahlFct().apply((Wahl) this);
    } catch (NoResultException nre) {
      return null;
    } catch (NonUniqueResultException e) {
      try {
        BeanManagerUtil.getBeanByClass(WurzelgebietNotUniqueEvent.class).fire(getPk());
      } catch (Exception ex) {
        LOG.warn("Fehler bei Wurzelgebietreperaturaufruf: " + ex.getMessage(), ex);
        FacesMessageUtil.addErrorMessage("mehr.als.ein.wurzelgebiet.vorhanden");
      }
      return null;
    }
  }

  public List<Gebietsart> getGebietsartenDerWahlgebiete() {
    return getWahlart().getGebietsartenDerWahlgebiete();
  }

  public List<Gebietsart> getGebietsartenDerDirektkandidatengebiete() {
    return getWahlart().getGebietsartenDerDirektkandidatengebiete();
  }

  public List<Gebietsart> getGebietsartenDerListengebiete() {
    return getWahlart().getGebietsartenDerListengebiete();
  }

  public List<Gebietsart> getGebietsartenDerReferendengebiete() {
    return getWahlart().getGebietsartenDerReferendengebiete();
  }

  public List<Gebietsart> getGebietsartenDerListenkandidatenStimmen() {
    return getWahlart().getGebietsartenMitListenkandidatenStimmen();
  }

  public boolean isBegehren() {
    return getWahlart().isBegehren();
  }

  public boolean isReferendum() {
    return getWahlart().isReferendum();
  }

  public boolean isReferendumMitQuorum() {
    return isReferendum() && hasQuorumProzenthuerde();
  }

  public boolean hasQuorumProzenthuerde() {
    return getQuorumProzenthuerde() != null;
  }

  /**
   * @return Prozenthürde == 5 -> Prozenthürde dezimal = 0,05
   */
  @Nullable
  public BigDecimal getQuorumProzenthuerdeDezimal() {
    if (getQuorumProzenthuerde() == null) {
      return null;
    }

    int onePercentScale = 2;

    return getQuorumProzenthuerde().divide(new BigDecimal(100), onePercentScale, RoundingMode.HALF_UP);
  }

  public boolean isWahlMitQuorum() {
    return getWahlart() != null && getWahlart().isWahlMitQuorum();
  }

  public boolean isWahlMitProzenthuerde() {
    return getWahlart() != null && getWahlart().isWahlMitProzenthuerde();
  }

  public boolean isWahlMitAnzahlSitze() {
    return !isWahlMitReferendengebieten();
  }

  public boolean isWahlMitAnzahlStimmenProWaehlerPerWahlgebiet() {
    return getWahlart() != null && getWahlart().isWahlMitAnzahlStimmenProWaehlerPerWahlgebiet();
  }

  public boolean isWahlMitWahlgebieten() {
    if (wahlMitWahlgebieten == null) {
      wahlMitWahlgebieten = !getGebietsartenDerWahlgebiete().isEmpty();
    }

    return wahlMitWahlgebieten;
  }

  public boolean isWahlMitListengebieten() {
    if (wahlMitListengebieten == null) {
      wahlMitListengebieten = !getGebietsartenDerListengebiete().isEmpty();
    }

    return wahlMitListengebieten;
  }

  public boolean isWahlMitReferendengebieten() {
    if (wahlMitReferendengebieten == null) {
      wahlMitReferendengebieten = !getGebietsartenDerReferendengebiete().isEmpty();
    }

    return wahlMitReferendengebieten;
  }

  public boolean isWahlMitUnterstuetzerAufEintragungslisten() {
    return getWahlart().isWahlMitUnterstuetzerAufEintragungslisten();
  }

  public boolean isWahlMitKandidaturen() {
    return !isWahlMitReferendengebieten();
  }

  public boolean isWahlMitDirektkandidatengebieten() {
    if (wahlMitDirektkandidatengebieten == null) {
      wahlMitDirektkandidatengebieten = !getGebietsartenDerDirektkandidatengebiete().isEmpty();
    }

    return wahlMitDirektkandidatengebieten;
  }

  public boolean isWahlMitListenkandidatenStimmen() {
    if (wahlMitListenkandidatenStimmen == null) {
      wahlMitListenkandidatenStimmen = !getGebietsartenDerListenkandidatenStimmen().isEmpty();
    }

    return wahlMitListenkandidatenStimmen;
  }

  /**
   * Arbeitshypothese: Gesamtstimmen sind nur sinnvoll, wenn es Direktstimmen gibt und auch Listenkandidaten Stimmen haben.
   *
   * @return Werden bei dieser Wahl Gesamtstimmen (Direktstimmen + Listenstimmen) angezeigt
   */
  public boolean isWahlMitGesamtstimmenauswertung() {
    return isWahlMitDirektkandidatengebieten() && isWahlMitListenkandidatenStimmen();
  }

  public boolean isWahlart(WahlartCreateTemplate wahlartTemplate) {
    return getWahlart().equalsWahlart(wahlartTemplate);
  }

  public <W extends WahlartCreateTemplate> W getWahlartTemplate(W[] wahlartTemplates) {
    return getWahlart().getCustomWahlartCreateTemplate(Arrays.asList(wahlartTemplates));
  }

  public <W extends WahlartCreateTemplate> W getWahlartTemplate(Collection<W> wahlartTemplates) {
    return getWahlart().getCustomWahlartCreateTemplate(wahlartTemplates);
  }

  public boolean isWahlMitMehrfachnennungen() {
    return getWahlart().getMaxAnzahlNennungen() > 1L;
  }

  public boolean isWahlMitErsatzkandidaturen() {
    return getWahlart().isWahlMitErsatzkandidaturen();
  }

  public boolean isWahlMitLeereListenErlaubt() {
    return getWahlart().getLeereListenErlaubt();
  }

  public boolean isWahlMitListenverbindungenErlaubt() {
    return getWahlart().getListenverbindungenErlaubt();
  }

  public Date bestimmeStichtagVormWahltag(@Nullable Long anzahlTageVormWahltag) {
    if (anzahlTageVormWahltag == null) {
      return null;
    }

    return getWahltermin().calculateStichtagVormWahltag(anzahlTageVormWahltag.intValue());
  }

  @Transient
  private transient Boolean wahlMitMehrerenListenGebietsarten = null;

  // Spezialfall Europawahl Destatis
  @Deprecated // diese Methode sagt nicht aus, ob in der aktuellen Wahl wirklich meherere Listenwahlgebiete vorkommen(!)
  public boolean isWahlMitMehrerenListenGebietsarten() {
    if (wahlMitMehrerenListenGebietsarten == null) {
      wahlMitMehrerenListenGebietsarten = bestimmeWahlMitMehrerenListenGebietsarten();
    }

    return wahlMitMehrerenListenGebietsarten;
  }

  private Boolean bestimmeWahlMitMehrerenListenGebietsarten() {
    return getWahlart().getGebietsarteigenschaften().stream().filter(Gebietsarteigenschaft::getListenwahlgebiet).count() > 1L;
  }

  @Override
  public String getEinsehenRolleMessageKey() {
    return WahlConstants.MESSAGE_KEY_EINSEHEN_ID_EINZAHL;
  }

  @Override
  public boolean isInstanceOfWahl() {
    return true;
  }

  @Override
  public boolean isInstanceOfWahltermin() {
    return false;
  }

  public String getDatumWahlartId() {

    var mandantPrefix = "";
    if (!getMandant().getArt().isOhneMandant()) {
      mandantPrefix = getMandant().getNummer() + "_";
    }

    // WASDE-2314 wahltermin nummer beachten, wenn nicht "0"
    var wahlnummerPrefix = "";
    if (StringUtils.isNotBlank(getWahlterminNummer()) && !"0".equals(getWahlterminNummer())) {
      wahlnummerPrefix = getWahlterminNummer() + "_";
    }

    return FileUtil.normalizeFileName(
        mandantPrefix +
            wahlnummerPrefix +
            new SimpleDateFormat("yyyyMMdd", Locale.GERMANY).format(getWahldatum()) +
            getWahlart().getName()
    );
  }

  @Deprecated // nur für WES verwenden, beachtet wahlterminnummer nicht
  public String getWahlterminNummerDatumWahlartOhneMandantId() {
    return FileUtil.normalizeZipEntryName(
        getWahlterminNummer() + "_" + new SimpleDateFormat("yyyyMMdd", Locale.GERMANY).format(getWahldatum()) + "_" + getWahlart().getName());
  }

  public List<Wahl> getWeitereWahlenAmWahltermin() {
    return getWahltermin().getWahlenSortiertNachPosition().stream().filter(w -> !this.equals(w)).collect(Collectors.toList());
  }

  @Transient
  private transient Map<String, Object> additionalData;

  public void addAdditionalData(String key, Object data) {
    if (additionalData == null) {
      additionalData = new HashMap<>();
    }
    additionalData.put(key, data);
  }

  public Object getAdditionalData(String key) {
    if (additionalData == null) {
      return null;
    }
    return additionalData.get(key);
  }

  @Override
  public Mandant getMandant() {
    if (getWahltermin() == null) {
      return null;
    }
    return getWahltermin().getMandant();
  }

  @Override
  public EntityMitMandant getParent() {
    return getWahltermin();
  }

  @Transient
  private transient WahlCreateDokumenteUebernehmen wahlCreateDokumenteUebernehmen;

  public WahlCreateDokumenteUebernehmen getWahlCreateDokumenteUebernehmen() {
    if (wahlCreateDokumenteUebernehmen == null) {
      return WahlCreateDokumenteUebernehmen.NICHT_UBERNEHMEN;
    }
    return wahlCreateDokumenteUebernehmen;
  }

  public void setWahlCreateDokumenteUebernehmen(WahlCreateDokumenteUebernehmen wahlCreateDokumenteUebernehmen) {
    this.wahlCreateDokumenteUebernehmen = wahlCreateDokumenteUebernehmen;
  }

  @Transient
  private transient WahlCreatePruefkriterienUebernehmen wahlCreatePruefkriterienUebernehmen;

  public WahlCreatePruefkriterienUebernehmen getWahlCreatePruefkriterienUebernehmen() {
    if (wahlCreatePruefkriterienUebernehmen == null) {
      return WahlCreatePruefkriterienUebernehmen.AUS_VORLAGEN_UEBEREHMEN;
    }
    return wahlCreatePruefkriterienUebernehmen;
  }

  public void setWahlCreatePruefkriterienUebernehmen(WahlCreatePruefkriterienUebernehmen wahlCreatePruefkriterienUebernehmen) {
    this.wahlCreatePruefkriterienUebernehmen = wahlCreatePruefkriterienUebernehmen;
  }

  @Transient
  private transient List<Gebiet> editGebiete;

  public List<Gebiet> getEditGebiete() {
    if (this.editGebiete == null) {
      this.editGebiete = new ArrayList<>();
    }
    return editGebiete;
  }

  public void setEditGebiete(List<Gebiet> editGebiete) {
    this.editGebiete = editGebiete;
  }

  @Override
  public boolean isWahldatumNichtFestgelegt() {
    return getWahltermin().isWahldatumNichtFestgelegt();
  }

  public boolean isWahlAufgeteiltOderBerichtetAn(AbstractWahlDAO wahlDAO) {
    if (getBerichtetAn() != null) {
      return true;
    }
    return isWahlAufgeteilt(wahlDAO);
  }

  public boolean isWahlAufgeteilt(AbstractWahlDAO wahlDAO) {
    return !wahlDAO.findByBerichtetAn((Wahl) this).isEmpty();
  }

}
