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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.contains;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;

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

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.personenpruefung.entity.NlKandidaturKonfliktPersonenregisterAbgleich;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.business.wahl.entity.Wahl;

/**
 * @see https://support.elect-it.com/wiki/display/ELECT/Datenmodell+-+NL+spezifisches+Mapping
 */
public class NlPersonendaten {

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

  private static final String GEBURTSDATUM_ERFASST_TAG_MONAT_REG_EX = "00-\\d{2}" + "|" + "[xX]{2}-([xX]{2}|(\\d?[1-9]|[1-9]0))" + "|" + "(\\d?[1-9]|[1-9]0)-(\\d?[1-9]|[1-9]0)";

  private static final String GEBURTSDATUM_ERFASST_JAHR_IMMER_4_STELLIG_REG_EX = "[1-9]\\d{3}"; // ELECT-10832

  // Bei Anpassungen ggf. auch Regex de.ivu.elect.wvp.presentation.validation.NlGeburtsdatumValidator#REGEX anpassen!!!
  public static final String GEBURTSDATUM_ERFASST_REG_EX = "" //
      + "((" + GEBURTSDATUM_ERFASST_TAG_MONAT_REG_EX + ")-(" + GEBURTSDATUM_ERFASST_JAHR_IMMER_4_STELLIG_REG_EX + "))" //
      + "|" + "00-00-0000" //
      + "|" + "([xX]{2}-[xX]{2}-([xX]{2}|[xX]{4}))" //
  ;

  public static final String GEBURTSDATUM_ERFASST_DATE_FORMAT_PATTERN = "dd-MM-yyyy";

  public static final Pattern GEBURTSDATUM_ERFASST_REG_EX_PATTERN_PLACEHOLDER = Pattern.compile("[a-z]", Pattern.CASE_INSENSITIVE);

  private final Personendaten personendaten;

  public NlPersonendaten(Personendaten personendaten) {
    super();

    this.personendaten = personendaten;
  }

  protected PersonendatenPK getPk() {
    return personendaten.getPk();
  }

  public boolean isPersistent() {
    return personendaten.isPersistent();
  }

  protected Wahl getWahl() {
    return personendaten.getWahl();
  }

  public boolean hasChangedOrdnungsmerkmal() {
    return personendaten.hasChangedExternesOrdnungsmerkmal();
  }

  public String getOrdnungsmerkmal() {
    return personendaten.getOrdnungsmerkmal();
  }

  public void setOrdnungsmerkmal(String ordnungsmerkmal) {
    personendaten.setOrdnungsmerkmal(ordnungsmerkmal);
  }

  public boolean hasChangedShortCode() {
    return personendaten.hasChangedExterneId();
  }

  public String getShortCode(Collection<NlPersonendaten> allePersonen, Map<String, String> shortCodeNachExterneId, Customer customer) {
    String externeId = personendaten.getExterneId();

    if (Customer.NL_WAS.equals(customer)) {

      // ELECTVAPP-1377 ELECTVAPP-1378
      if (StringUtils.isBlank(externeId) && personendaten.getId() != null) {
        return Long.toString(personendaten.getId());
      }
      return externeId; // im WAS direkt verwenden
    }

    if (isBlank(externeId)) {
      return "";
    }

    return (String) personendaten.getTransientContext()
        .computeIfAbsent("NL_SHORT_CODE", x -> calculateShortCode(allePersonen, shortCodeNachExterneId, externeId));

  }

  @Nonnull
  private String calculateShortCode(Collection<NlPersonendaten> allePersonen, Map<String, String> shortCodeNachExterneId, String externeId) {
    String pureShortCode = ShortCodeCalculator.createShortCode(getFamilienname(), getInitialen());

    if (externeId.startsWith(pureShortCode)) {
      return externeId;
    }

    return shortCodeNachExterneId.computeIfAbsent(externeId, x -> {
      TreeMap<String, String> shortCodeByExterneIdMap = new TreeMap<>(String::compareTo); // Nach key (externId) sortierte Map
      for (NlPersonendaten nlPersonendaten : allePersonen) {
        shortCodeByExterneIdMap.computeIfAbsent(nlPersonendaten.getPersonendaten().getExterneId(),
            xx -> ShortCodeCalculator.createShortCode(nlPersonendaten.getFamilienname(), nlPersonendaten.getInitialen()));
      }

      if (!shortCodeByExterneIdMap.containsKey(externeId) || !shortCodeByExterneIdMap.get(externeId).equals(pureShortCode)) {
        throw new IllegalStateException();
      }

      long pureShortCodeVorkommenBisHier = shortCodeByExterneIdMap.headMap(externeId, true).values().stream().filter(s -> s.equals(pureShortCode)).count();
      if (pureShortCodeVorkommenBisHier > 1) {
        return pureShortCode.concat(String.valueOf(pureShortCodeVorkommenBisHier - 1));
      }
      return pureShortCode;
    });
  }

  public void setShortCode(String shortCode) {
    personendaten.setExterneId(shortCode);
  }

  public boolean hasChangedInitialen() {
    return personendaten.hasChangedVornamen();
  }

  public String getInitialen() {
    return personendaten.getVornamen();
  }

  public void setInitialen(String initialen) {
    personendaten.setVornamen(initialen);
  }

  public boolean hasChangedVorname() {
    return personendaten.hasChangedRufname();
  }

  public String getVorname() {
    return personendaten.getRufname();
  }

  public void setVorname(String vorname) {
    personendaten.setRufname(vorname);
  }

  public boolean hasChangedFamilienname() {
    return personendaten.hasChangedFamilienname();
  }

  public String getFamilienname() {
    return personendaten.getFamilienname();
  }

  public void setFamilienname(String familienname) {
    personendaten.setFamilienname(familienname);
  }

  public boolean hasChangedFamiliennameBestandteil() {
    return personendaten.hasChangedFamiliennameBestandteil();
  }

  public String getFamiliennameBestandteil() {
    return personendaten.getFamiliennameBestandteil();
  }

  public void setFamiliennameBestandteil(String familiennameBestandteil) {
    personendaten.setFamiliennameBestandteil(familiennameBestandteil);
  }

  public boolean hasChangedGeburtsdatum() {
    return hasChangedGeburtsdatumErfasst(); // Cf. NlPersonendaten#setGeburtsdatum!!!
  }

  public Date getGeburtsdatum() {
    return personendaten.getGeburtsdatum();
  }

  public void setGeburtsdatum(Date geburtsdatum) {
    if (geburtsdatum == null) {
      setGeburtsdatumErfasst(null);
    } else {
      setGeburtsdatumErfasst(new SimpleDateFormat(GEBURTSDATUM_ERFASST_DATE_FORMAT_PATTERN).format(geburtsdatum));

      if (personendaten.getGeburtsdatum() == null) {
        personendaten.setGeburtsdatum(geburtsdatum); // Doppelt hält besser...
      }
    }
  }

  public boolean hasChangedGeburtsdatumErfasst() {
    return personendaten.hasChangedStimmbezirkSchluessel();
  }

  public boolean isGeburtsdatumErfasstUnvollstaendig() {
    return matchesDatumErfasstUnvollstaendig(getGeburtsdatumErfasst());
  }

  public String getGeburtsdatumErfasst() {
    return personendaten.getStimmbezirkSchluessel();
  }

  public void setGeburtsdatumErfasst(String geburtsdatumErfasst) {
    personendaten.setStimmbezirkSchluessel(geburtsdatumErfasst);
    personendaten.setGeburtsdatum(null);

    String value = trimToEmpty(geburtsdatumErfasst);

    // ELECT-10832, nur 4-stellige Jahreszahlen können (ohne Sonderbehandlung) korrekt als Date geparsed werden!
    if (!matchesDatumErfasstUnvollstaendig(value) && value.matches(".+-\\d{4}$")) {
      try {
        personendaten.setGeburtsdatum(new SimpleDateFormat(GEBURTSDATUM_ERFASST_DATE_FORMAT_PATTERN).parse(geburtsdatumErfasst));
      } catch (Exception ignored) {
        LOG.warn("FAILED to parse date [pattern = {}, value = {}]", GEBURTSDATUM_ERFASST_DATE_FORMAT_PATTERN, geburtsdatumErfasst);
      }
    }
  }

  private boolean matchesDatumErfasstUnvollstaendig(String value) {
    return isBlank(value) || containsIgnoreCase(value, "x") || contains(value, "00-") || contains(value, "0000");
  }

  public boolean hasChangedGeschlecht() {
    return personendaten.hasChangedGeschlecht();
  }

  public Geschlecht getGeschlecht() {
    return personendaten.getGeschlecht();
  }

  public void setGeschlecht(Geschlecht geschlecht) {
    personendaten.setGeschlecht(geschlecht);
  }

  // ELECT-11609
  public boolean isPersonStatusAbzugleichen() {
    return personendaten.isInstanceOf(Kandidatur.class) && NlKandidaturKonfliktPersonenregisterAbgleich.isAbzugleichen(castTo(personendaten, Kandidatur.class));
  }

  public boolean hasChangedPersonStatus() {
    return personendaten.hasChangedPersonStatus();
  }

  // ELECT-11565
  public PersonStatus getPersonStatus() {
    if (NlKandidaturKonfliktPersonenregisterAbgleich.isUnterstuetzt(getWahl()) && isPersonStatusAbzugleichen()) {
      if (!personendaten.isPersonStatusNichtZuPruefen()) {
        PersonenregisterAbgleichStatus personenregisterAbgleichStatus = personendaten.getPersonenregisterAbgleichStatus();

        if (personenregisterAbgleichStatus != null) {
          if (!personenregisterAbgleichStatus.isAbgleichMoeglich() && (personendaten.isPersonStatusGeeignet() || personendaten.isPersonStatusNichtGeeignet())) {
            return PersonStatus.NICHT_ZU_PRUEFEN;
          }

          if (personenregisterAbgleichStatus.isAbgleichNotwendig()) {
            return PersonStatus.UNGEPRUEFT;
          }
        }
      }
    }

    return personendaten.getPersonStatus();
  }

  public void setPersonStatus(PersonStatus personStatus) {
    personendaten.setPersonStatus(personStatus);
  }

  public boolean hasChangedWahlrechtsausschluss() {
    return personendaten.hasChangedWahlrechtsausschluss();
  }

  public Wahlrechtsausschluss getWahlrechtsausschluss() {
    return personendaten.getWahlrechtsausschluss();
  }

  public void setWahlrechtsausschluss(Wahlrechtsausschluss wahlrechtsausschluss) {
    personendaten.setWahlrechtsausschluss(wahlrechtsausschluss);
  }

  public boolean isWohnanschriftInNiederlande() {
    return isAnschriftInNiederlande(getWohnanschrift());
  }

  public Anschrift getWohnanschrift() {
    return personendaten.getAnschrift();
  }

  public boolean isErreichbarkeitsanschriftInNiederlande() {
    return isAnschriftInNiederlande(getErreichbarkeitsanschrift());
  }

  public Anschrift getErreichbarkeitsanschrift() {
    return personendaten.getErreichbarkeitsanschrift();
  }

  private boolean isAnschriftInNiederlande(Anschrift anschrift) {
    if (anschrift != null) {
      String value = trimToEmpty(anschrift.getStaat());

      return isBlank(value) || asList("NL", "NIEDERLANDE", "Nederland").stream().anyMatch(code -> equalsAnyIgnoreCase(code, value));
    }

    return false;
  }

  public NlPersonendatenProperties getProperties() {
    return new NlPersonendatenProperties(personendaten);
  }

  public Personendaten getPersonendaten() {
    return personendaten;
  }
}
