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

import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;

import de.ivu.elect.business.anschrift.entity.AnschriftArt;
import de.ivu.elect.business.person.entity.Geschlecht;
import de.ivu.elect.business.person.entity.Sperrmerkmal;
import de.ivu.elect.business.person.entity.Wahlrechtsausschluss;
import de.ivu.elect.business.security.entity.EntityMitSperrmerkmal;
import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.entity.EntityContainer;
import de.ivu.elect.jee.entity.EntityFachlichAbhaengigNode;
import de.ivu.elect.jee.entity.EntityLongPK;
import de.ivu.elect.jee.entity.tk.EntityTK;
import de.ivu.elect.jee.entity.tk.EntityUK;
import de.ivu.elect.jee.protokollierung.AttributWertVorher;
import de.ivu.elect.jee.protokollierung.VorherNachherWert;

public class PersonenregisterPersonendaten implements Entity<EntityLongPK, EntityUK>, EntityMitSperrmerkmal {

  private static final long serialVersionUID = 1L;

  private final PersonenregisterSuchstrategie personenregisterSuchstrategie;

  private final PersonenregisterFundkriterium personenregisterFundkriterium;

  private final UUID uuid;

  private final String ordnungsmerkmal;

  private final String anrede;

  private final String titel;

  private final String familienname;

  private final String rufname;

  private final String vornamen;

  private final String familiennameBestandteil;

  private final String kuenstlername;

  private final String ordensname;

  private final Date geburtsdatum;

  private final String geburtsort;

  private final Geschlecht geschlecht;

  private final List<String> staatsangehoerigkeiten;

  private final PersonenregisterEinwohnerstatus einwohnerstatus;

  private final Sperrmerkmal sperrmerkmal;

  private final Wahlrechtsausschluss wahlrechtsausschluss;

  private final String stimmbezirkSchluessel;

  private final Date zuzugsdatumBund;

  private final Date zuzugsdatumLand;

  private final Date zuzugsdatumGemeinde;

  private final Date zuzugsdatumStadtbezirk;

  private final List<PersonenregisterAnschrift> personenregisterAnschriften;

  private PersonenregisterPersonendatenProperties personenregisterPersonendatenProperties;

  public PersonenregisterPersonendaten(PersonenregisterPersonendaten personenregisterPersonendaten, Collection< ? extends PersonenregisterAnschrift> personenregisterAnschriften) {
    this(personenregisterPersonendaten.getPersonenregisterSuchstrategie(), personenregisterPersonendaten.getPersonenregisterFundkriterium(), //
        personenregisterPersonendaten.getOrdnungsmerkmal(), //

        personenregisterPersonendaten.getAnrede(), personenregisterPersonendaten.getTitel(), //

        personenregisterPersonendaten.getFamilienname(), personenregisterPersonendaten.getRufname(), personenregisterPersonendaten.getVornamen(),
        personenregisterPersonendaten.getFamiliennameBestandteil(), //

        personenregisterPersonendaten.getKuenstlername(), personenregisterPersonendaten.getOrdensname(), //

        personenregisterPersonendaten.getGeburtsdatum(), personenregisterPersonendaten.getGeburtsort(), personenregisterPersonendaten.getGeschlecht(), //

        personenregisterPersonendaten.getStaatsangehoerigkeiten(), //

        personenregisterPersonendaten.getEinwohnerstatus(), personenregisterPersonendaten.getSperrmerkmal(), personenregisterPersonendaten.getWahlrechtsausschluss(), //

        personenregisterPersonendaten.getStimmbezirkSchluessel(), //

        personenregisterPersonendaten.getZuzugsdatumBund(), personenregisterPersonendaten.getZuzugsdatumLand(), personenregisterPersonendaten.getZuzugsdatumGemeinde(),
        personenregisterPersonendaten.getZuzugsdatumStadtbezirk(), //

        personenregisterAnschriften //
    );
  }

  public PersonenregisterPersonendaten(PersonenregisterSuchstrategie personenregisterSuchstrategie, PersonenregisterFundkriterium personenregisterFundkriterium, //
      String ordnungsmerkmal, //
      String anrede, String titel, //
      String familienname, String rufname, String vornamen, String familiennameBestandteil, //
      String kuenstlername, String ordensname, //
      Date geburtsdatum, String geburtsort, Geschlecht geschlecht, //
      List<String> staatsangehoerigkeiten, //
      PersonenregisterEinwohnerstatus einwohnerstatus, Sperrmerkmal sperrmerkmal, Wahlrechtsausschluss wahlrechtsausschluss, //
      String stimmbezirkSchluessel, //
      Date zuzugsdatumBund, Date zuzugsdatumLand, Date zuzugsdatumGemeinde, Date zuzugsdatumStadtbezirk, //
      Collection< ? extends PersonenregisterAnschrift> personenregisterAnschriften //
  ) {
    super();

    this.personenregisterSuchstrategie = personenregisterSuchstrategie;
    this.personenregisterFundkriterium = personenregisterFundkriterium;

    this.uuid = UUID.randomUUID();

    this.ordnungsmerkmal = personenregisterFundkriterium.isOrdnungsmerkmal() || personenregisterSuchstrategie.isSearchByOrdnungsmerkmalErlaubt() ? ordnungsmerkmal : ""; // ELECT-16555

    this.anrede = anrede;
    this.titel = titel;

    this.familienname = familienname;
    this.familiennameBestandteil = familiennameBestandteil;
    this.rufname = rufname;
    this.vornamen = vornamen;
    this.kuenstlername = kuenstlername;
    this.ordensname = ordensname;

    this.geburtsdatum = geburtsdatum;
    this.geburtsort = geburtsort;
    this.geschlecht = geschlecht;

    this.staatsangehoerigkeiten = new ArrayList<>(staatsangehoerigkeiten);

    this.einwohnerstatus = einwohnerstatus;
    this.sperrmerkmal = sperrmerkmal;
    this.wahlrechtsausschluss = wahlrechtsausschluss;

    this.stimmbezirkSchluessel = getStimmbezirkSchluesselOrDefault(stimmbezirkSchluessel, personenregisterAnschriften);

    this.zuzugsdatumBund = zuzugsdatumBund;
    this.zuzugsdatumLand = zuzugsdatumLand;
    this.zuzugsdatumGemeinde = zuzugsdatumGemeinde;
    this.zuzugsdatumStadtbezirk = zuzugsdatumStadtbezirk;

    this.personenregisterAnschriften = personenregisterAnschriften.stream().filter(Objects::nonNull).collect(toList());
  }

  public PersonenregisterSuchstrategie getPersonenregisterSuchstrategie() {
    return personenregisterSuchstrategie;
  }

  public PersonenregisterFundkriterium getPersonenregisterFundkriterium() {
    return personenregisterFundkriterium;
  }

  /**
   * NOTE ELECT-2248, dass diese ID kein fachlicher Wert ist. Diese ist (im Gegensatz zu den anderen Attributen allerdings) immer gesetzt und eineindeutig, kann daher bswp. als <code>rowKey</code>
   * Attribut in einer {@link org.primefaces.component.datatable.DataTable} verwendet werden.
   *
   * @return The uuid.
   */
  public UUID getUuid() {
    return uuid;
  }

  public String getOrdnungsmerkmal() {
    return ordnungsmerkmal;
  }

  public String getAnrede() {
    return anrede;
  }

  public String getTitel() {
    return titel;
  }

  public String getFamilienname() {
    return familienname;
  }

  public String getFamiliennameBestandteil() {
    return familiennameBestandteil;
  }

  public String getRufname() {
    return rufname;
  }

  public String getVornamen() {
    return vornamen;
  }

  public String getKuenstlername() {
    return kuenstlername;
  }

  public String getOrdensname() {
    return ordensname;
  }

  public Date getGeburtsdatum() {
    return geburtsdatum;
  }

  public String getGeburtsort() {
    return geburtsort;
  }

  public Geschlecht getGeschlecht() {
    return geschlecht;
  }

  public boolean hasStaatsangehoerigkeit() {
    return !getStaatsangehoerigkeiten().isEmpty();
  }

  public List<String> getStaatsangehoerigkeiten() {
    return staatsangehoerigkeiten;
  }

  public PersonenregisterEinwohnerstatus getEinwohnerstatus() {
    return einwohnerstatus;
  }

  @Override
  public Sperrmerkmal getSperrmerkmal() {
    return sperrmerkmal;
  }

  public Wahlrechtsausschluss getWahlrechtsausschluss() {
    return wahlrechtsausschluss;
  }

  public String getStimmbezirkSchluessel() {
    return stimmbezirkSchluessel;
  }

  public Date getZuzugsdatumBund() {
    return zuzugsdatumBund;
  }

  public Date getZuzugsdatumLand() {
    return zuzugsdatumLand;
  }

  public Date getZuzugsdatumGemeinde() {
    return zuzugsdatumGemeinde;
  }

  public Date getZuzugsdatumStadtbezirk() {
    return zuzugsdatumStadtbezirk;
  }

  public List<PersonenregisterAnschrift> getAnschriften() {
    return unmodifiableList(personenregisterAnschriften);
  }

  public boolean hasAnschrift() {
    return !getAnschriften().isEmpty();
  }

  public PersonenregisterAnschrift getAnschrift() {
    for (PersonenregisterAnschrift anschrift : getAnschriften()) {
      return anschrift;
    }

    return new PersonenregisterAnschrift(AnschriftArt.UNBEKANNT);
  }

  public boolean hasAnschrift(AnschriftArt anschriftArt) {
    for (PersonenregisterAnschrift anschrift : getAnschriften()) {
      if (anschrift.hasAnschriftArt(anschriftArt)) {
        return true;
      }
    }

    return false;
  }

  public PersonenregisterAnschrift getAnschrift(AnschriftArt anschriftArt) {
    List<AnschriftArt> anschriftArten = new ArrayList<>();

    for (PersonenregisterAnschrift anschrift : getAnschriften()) {
      anschriftArten.add(anschrift.getAnschriftArt());

      if (anschrift.hasAnschriftArt(anschriftArt)) {
        return anschrift;
      }
    }

    throw new IllegalArgumentException("Keine Anschrift der Art gefunden [anschriftArt = " + anschriftArt + ", anschriftArten = " + anschriftArten + "]");
  }

  private static String getStimmbezirkSchluesselOrDefault(String stimmbezirkSchluessel, Collection< ? extends PersonenregisterAnschrift> personenregisterAnschriften) {
    // NOTE, dass bspw. mit der MESO-Wahlen-Schnittstelle der Stimmbezirkschlüssel per Anschrift bestimmt wird, während
    // EWO den Stimmbezirkschlüssel für eine Person direkt, also unabhängig von deren Anschrift, liefert.
    if (isBlank(stimmbezirkSchluessel)) {
      SortedSet<String> result = new TreeSet<>();

      for (PersonenregisterAnschrift personenregisterAnschrift : personenregisterAnschriften) {
        if (personenregisterAnschrift != null && personenregisterAnschrift.hasAnschriftArt(AnschriftArt.HAUPTWOHNUNG)) {
          result.add(personenregisterAnschrift.getStimmbezirkSchluessel());
        }
      }

      if (result.size() == 1) {
        return result.first();
      }
    }

    return stimmbezirkSchluessel;
  }

  public PersonenregisterPersonendatenProperties getProperties() {
    if (personenregisterPersonendatenProperties == null) {
      personenregisterPersonendatenProperties = createProperties();
    }

    return personenregisterPersonendatenProperties;
  }

  protected PersonenregisterPersonendatenProperties createProperties() {
    return new PersonenregisterPersonendatenProperties(this);
  }

  @Override
  public Long getId() {
    // ELECT-5245
    return null;
  }

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

  @Override
  public Class< ? > getEntityClass() {
    throw new UnsupportedOperationException();
  }

  @Override
  public String getEntityTechnicalName() {
    throw new UnsupportedOperationException();
  }

  @Override
  public EntityLongPK getPk() {
    throw new UnsupportedOperationException();
  }

  @Override
  public EntityUK getUk() {
    throw new UnsupportedOperationException();
  }

  @Override
  public EntityTK getTk() {
    throw new UnsupportedOperationException();
  }

  @Override
  public Map<String, VorherNachherWert> getProtokollierteAttributeMitWerten() {
    throw new UnsupportedOperationException();
  }

  @Override
  public void setProtokollierteAttributeMitWerten(Map<String, AttributWertVorher> werteVorher) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void resetProtokollierteAttribute() {
    throw new UnsupportedOperationException();
  }

  @Override
  public List<EntityFachlichAbhaengigNode> getFachlichAbhaengigeEntitaeten() {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean isInstanceOf(Class< ? > clazz) {
    return clazz.isInstance(this);
  }

  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public EntityContainer<PersonenregisterPersonendaten> castTo(Class clazz) {
    return new EntityContainer<>(this);
  }

  @Override
  public String getBaseClassName() {
    return getClass().getName();
  }

  @Override
  public void copyEntityAttributesToOtherEntity(Entity other) {
    throw new UnsupportedOperationException();
  }

  public PersonenregisterPersonendaten newInstance(List< ? extends PersonenregisterAnschrift> personenregisterAnschriften) {
    return new PersonenregisterPersonendaten(this, personenregisterAnschriften);
  }

  protected static String getAnredeOrDefault(Geschlecht geschlecht) {
    if (Geschlecht.MAENNLICH.equals(geschlecht)) {
      return "Herr";
    }

    if (Geschlecht.WEIBLICH.equals(geschlecht)) {
      return "Frau";
    }

    return "";
  }
}
