
package de.ivu.elect.presentation.person.form;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.apache.commons.lang3.StringUtils.deleteWhitespace;
import static org.apache.commons.lang3.StringUtils.isAlpha;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.inject.Inject;

import de.ivu.elect.business.person.control.GeschlechtFilter;
import de.ivu.elect.business.person.control.KreditinstitutErmittler;
import de.ivu.elect.business.person.entity.Kontoverbindung;
import de.ivu.elect.business.person.entity.KontoverbindungProperties;
import de.ivu.elect.business.person.entity.Kreditinstitut;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenProperties;
import de.ivu.elect.business.person.entity.PersonendatenProperties.GeschlechtProperty;
import de.ivu.elect.jee.faces.BicValidator;
import de.ivu.elect.jee.faces.IbanValidator;
import de.ivu.elect.jee.presentation.dynamicform.AbstractStringProperty;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.Property;
import de.ivu.elect.jee.presentation.dynamicform.StringMaskDecoratorProperty;
import de.ivu.elect.jee.presentation.dynamicform.converter.AutoFormatDateTimeConverter;
import de.ivu.elect.presentation.personenregister.form.AbstractPersonenregisterPropertiesViewFormBuilder;

public class PersonendatenViewFormBuilder extends AbstractPersonenregisterPropertiesViewFormBuilder {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient KreditinstitutErmittler kreditinstitutErmittler;

  @Inject
  private transient GeschlechtFilter geschlechtFilter;

  public PersonendatenViewFormBuilder() {
    super();
  }

  public void addBasicPersonendatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesReadonly) {
    addBasicPersonendatenProperties(formBuilder, personendaten, propertiesReadonly, new ArrayList<>(), propertiesReadonly);
  }

  public void addBasicPersonendatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesReadonly,
      Collection<Class< ? extends Property>> personenregisterPropertiesUebernommen, boolean personenregisterPropertiesReadonly) {
    addBasicPersonendatenProperties(formBuilder, personendaten, false, propertiesReadonly, personenregisterPropertiesUebernommen, personenregisterPropertiesReadonly);
  }

  public void addBasicPersonendatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesErweitertInitialVisibleOnPageLoad, boolean propertiesReadonly,
      Collection<Class< ? extends Property>> personenregisterPropertiesUebernommen, boolean personenregisterPropertiesReadonly) {
    PersonendatenProperties personendatenProperties = personendaten.getProperties();

    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getFamiliennameBestandteilProperty(), propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly,
        personenregisterPropertiesUebernommen, personenregisterPropertiesReadonly);

    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getFamiliennameProperty(), propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly,
        personenregisterPropertiesUebernommen, personenregisterPropertiesReadonly);
    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getRufnameProperty(), propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly,
        personenregisterPropertiesUebernommen, personenregisterPropertiesReadonly);
    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getVornamenProperty(), propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly,
        personenregisterPropertiesUebernommen, personenregisterPropertiesReadonly);

    GeschlechtProperty geschlechtProperty = personendatenProperties.getGeschlechtProperty();
    geschlechtProperty.setValueFilter(geschlechtFilter);

    addPersonenregisterProperty(formBuilder, personendaten, geschlechtProperty, propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);

    Property geburtsdatumProperty = personendatenProperties.getGeburtsdatumProperty();
    geburtsdatumProperty.setConverter(new AutoFormatDateTimeConverter(true)); // ELECTMUC-2429

    addPersonenregisterProperty(formBuilder, personendaten, geburtsdatumProperty, propertiesErweitertInitialVisibleOnPageLoad, propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);

    // ELECTDD-707, Pflichtfelder immer nach Art der Personendaten setzen, bspw. WahlvorschlagsPersonViewFormBuilder#setWahlvorschlagsPersonPersonendatenPropertiesRequired...
    // setBasicPersonendatenPropertiesRequired(formBuilder, personendaten);
  }

  public FormBuilder setBasicPersonendatenPropertiesRequired(FormBuilder formBuilder, Personendaten personendaten) {
    return setPropertiesRequired(formBuilder, getBasicPersonendatenPropertiesRequired(personendaten));
  }

  @Deprecated
  public Set<Class< ? extends Property>> getBasicPersonendatenPropertiesRequired(Personendaten personendaten) {
    Set<Class< ? extends Property>> result = new HashSet<>();

    result.add(PersonendatenProperties.FamiliennameProperty.class);
    result.add(PersonendatenProperties.RufnameProperty.class);

    result.add(PersonendatenProperties.GeburtsdatumProperty.class);

    return result;
  }

  public void addDefaultErreichbarkeitsdatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesReadonly) {
    PersonendatenProperties personendatenProperties = personendaten.getProperties();

    formBuilder.addProperty(personendatenProperties.getTelefonnummerFestnetzProperty()).withReadonly(propertiesReadonly);
    formBuilder.addProperty(personendatenProperties.getTelefonnummerMobilProperty()).withReadonly(propertiesReadonly);

    formBuilder.addProperty(personendatenProperties.getFaxnummerProperty()).withReadonly(propertiesReadonly);

    formBuilder.addProperty(personendatenProperties.getEmailAdresseProperty()).withReadonly(propertiesReadonly);

    setDefaultErreichbarkeitsdatenRequired(formBuilder, personendaten);
  }

  public FormBuilder setDefaultErreichbarkeitsdatenRequired(FormBuilder formBuilder, Personendaten personendaten) {
    return setPropertiesRequired(formBuilder, getDefaultErreichbarkeitsdatenPropertiesRequired(personendaten));
  }

  public Set<Class< ? extends Property>> getDefaultErreichbarkeitsdatenPropertiesRequired(Personendaten personendaten) {
    return emptySet();
  }

  public void addDefaultKontoverbindungProperties(FormBuilder formBuilder, Personendaten personendaten, Kontoverbindung kontoverbindung, boolean propertiesReadonly) {
    if (isFinanzIdPropertyRendered(personendaten)) { // ELECTKOM-1352
      formBuilder.addProperty(personendaten.getProperties().getFinanzIdProperty()) //
          .withTooltip(getMessage("entity.tooltip.PERSONENDATEN.FINANZ_ID")) //
          .withReadonly(propertiesReadonly);
    }

    KontoverbindungProperties kontoverbindungProperties = kontoverbindung.getProperties();

    formBuilder.addProperty(kontoverbindungProperties.getKontoinhaberProperty()).withReadonly(propertiesReadonly);

    AbstractStringProperty ibanProperty = addKontoverbindungIbanProperty(formBuilder, kontoverbindungProperties.getIbanProperty(), isKontoverbindungIbanValidationEnabled(), propertiesReadonly);

    AbstractStringProperty ibanVorAenderungProperty = kontoverbindungProperties.getIbanVorAenderungProperty();

    if (!ibanVorAenderungProperty.hasValue()) {
      ibanVorAenderungProperty.setValue(kontoverbindung.getIban());
    }

    formBuilder.addProperty(ibanVorAenderungProperty).readonly().withVisible(false);

    AbstractStringProperty ibanBearbeiterProperty = kontoverbindungProperties.getIbanBearbeiterProperty();
    ibanBearbeiterProperty.setValue(getPrincipal().getLogin().getBenutzerkennung());

    formBuilder.addProperty(ibanBearbeiterProperty).readonly().withVisible(false);

    addKontoverbindungBicProperty(formBuilder, kontoverbindungProperties.getBicProperty(), ibanProperty, isKontoverbindungBicValidationEnabled(), propertiesReadonly);

    // ELECTDD-236
    addKreditinstitutProperty(formBuilder, kontoverbindungProperties.getKreditinstitutProperty(), ibanProperty, propertiesReadonly);
  }

  protected boolean isFinanzIdPropertyRendered(Personendaten personendaten) {
    return isNotBlank(personendaten.getFinanzId());
  }

  private AbstractStringProperty addKontoverbindungIbanProperty(FormBuilder formBuilder, AbstractStringProperty ibanProperty, boolean validatorRequired, boolean readonly) {
    boolean autocomplete = false; // ELECT-11865

    ibanProperty.setValue(normalizeIbanMaskValue(getUserLocale().getLanguage(), ibanProperty.getValue()));
    ibanProperty.setAutocomplete(autocomplete);

    StringMaskDecoratorProperty stringMaskDecoratorProperty = new StringMaskDecoratorProperty(ibanProperty, IbanValidator.DE_IBAN_MASK, IbanValidator.DE_IBAN_MASK_SLOT_CHAR);
    stringMaskDecoratorProperty.setAutocomplete(autocomplete);

    formBuilder.addProperty(stringMaskDecoratorProperty).withReadonly(readonly);

    if (validatorRequired) {
      formBuilder.withValidator(IbanValidator.INSTANCE);
    }

    return stringMaskDecoratorProperty;
  }

  protected String normalizeIbanMaskValue(String language, String value) {
    String valueNormalized = trimToEmpty(deleteWhitespace(value));

    if (!valueNormalized.isEmpty() && (valueNormalized.length() < language.length() || !isAlpha(valueNormalized.substring(0, language.length())))) {
      valueNormalized = language + valueNormalized;
    }

    return valueNormalized;
  }

  private void addKontoverbindungBicProperty(FormBuilder formBuilder, AbstractStringProperty bicProperty, AbstractStringProperty observedIbanProperty, boolean validatorRequired, boolean readonly) {
    if (kreditinstitutErmittler.isErmittlungAktiv()) {
      bicProperty.appendObservedPropertyValueChangedConsumer(other -> {
        if (other.equals(observedIbanProperty)) {
          Kreditinstitut kreditinstitut = kreditinstitutErmittler.fromIban(observedIbanProperty.getValue());

          if (kreditinstitut != null) {
            bicProperty.setValue(kreditinstitut.getBic());
          }
        }
      });
    }

    formBuilder.addProperty(bicProperty).withReadonly(readonly);

    if (kreditinstitutErmittler.isErmittlungAktiv()) {
      formBuilder.withObservedProperty(observedIbanProperty);
    }

    if (validatorRequired) {
      formBuilder.withValidator(BicValidator.INSTANCE);
    }
  }

  private void addKreditinstitutProperty(FormBuilder formBuilder, AbstractStringProperty kreditinstitutProperty, AbstractStringProperty observedIbanProperty, boolean propertiesReadonly) {
    if (kreditinstitutErmittler.isErmittlungAktiv()) {
      kreditinstitutProperty.appendObservedPropertyValueChangedConsumer(other -> {
        if (other.equals(observedIbanProperty)) {
          Kreditinstitut kreditinstitut = kreditinstitutErmittler.fromIban(observedIbanProperty.getValue());

          if (kreditinstitut != null) {
            kreditinstitutProperty.setValue(kreditinstitut.getBezeichnung());
          }
        }
      });
    }

    formBuilder.addProperty(kreditinstitutProperty).withReadonly(propertiesReadonly);

    if (kreditinstitutErmittler.isErmittlungAktiv()) {
      formBuilder.withObservedProperty(observedIbanProperty);
    }
  }

  public void addZuzugsdatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesReadonly) {
    addZuzugsdatenProperties(formBuilder, personendaten, propertiesReadonly, new ArrayList<Class< ? extends Property>>(), propertiesReadonly);
  }

  public void addZuzugsdatenProperties(FormBuilder formBuilder, Personendaten personendaten, boolean propertiesReadonly, Collection<Class< ? extends Property>> personenregisterPropertiesUebernommen,
      boolean personenregisterPropertiesReadonly) {
    PersonendatenProperties personendatenProperties = personendaten.getProperties();

    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getZuzugsdatumBundProperty(), propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);
    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getZuzugsdatumLandProperty(), propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);
    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getZuzugsdatumGemeindeProperty(), propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);
    addPersonenregisterProperty(formBuilder, personendaten, personendatenProperties.getZuzugsdatumStadtbezirkProperty(), propertiesReadonly, personenregisterPropertiesUebernommen,
        personenregisterPropertiesReadonly);
  }

  @Override
  public Collection<Class< ? extends Property>> getPersonenregisterPropertiesUebernommen(Personendaten personendaten) {
    // NOTE, dass keine allgemeine Implementierung für existiert, da die übernommenen Felder von der jeweiligen Entität,
    // bspw. Wahlhelfer, Wahlausschussmitglied, etc., abhängen!
    return emptyList();
  }

  public Collection<Class< ? extends Property>> getDefaultPersonenregisterPropertiesUebernommen(Personendaten personendaten) {
    Collection<Class< ? extends Property>> result = new ArrayList<>();

    // Cf. PersonendatenPersonenregisterPersonendatenMapper.
    result.add(PersonendatenProperties.ExternesOrdnungsmerkmalProperty.class);

    result.add(PersonendatenProperties.AnredeProperty.class);
    result.add(PersonendatenProperties.TitelProperty.class);

    result.add(PersonendatenProperties.FamiliennameProperty.class);
    result.add(PersonendatenProperties.RufnameProperty.class);
    result.add(PersonendatenProperties.VornamenProperty.class);
    result.add(PersonendatenProperties.FamiliennameBestandteilProperty.class);

    result.add(PersonendatenProperties.KuenstlernameProperty.class);
    result.add(PersonendatenProperties.OrdensnameProperty.class);

    result.add(PersonendatenProperties.GeburtsdatumProperty.class);
    result.add(PersonendatenProperties.GeburtsortProperty.class);

    result.add(PersonendatenProperties.GeschlechtProperty.class);
    result.add(PersonendatenProperties.GeschlechtMitLeererAuswahlProperty.class);

    result.add(PersonendatenProperties.SperrmerkmalProperty.class);
    result.add(PersonendatenProperties.SperrmerkmalMitLeererAuswahlProperty.class);

    result.add(PersonendatenProperties.WahlrechtsausschlussProperty.class);
    result.add(PersonendatenProperties.WahlrechtsausschlussMitLeererAuswahlProperty.class);

    result.add(PersonendatenProperties.StimmbezirkSchluesselProperty.class);

    return result;
  }

  public boolean isKontoverbindungBicValidationEnabled() {
    return !isKontoverbindungIbanBicValidationDisabled();
  }

  public boolean isKontoverbindungIbanValidationEnabled() {
    return !isKontoverbindungIbanBicValidationDisabled();
  }

  private boolean isKontoverbindungIbanBicValidationDisabled() {
    // Fallback, die Qualität der Iban/Bic Validierungskomponente ist nicht ganz klar...
    return "true".equalsIgnoreCase(System.getProperty("disable.iban.bic.validation"));
  }
}
