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

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.anschrift.entity.AnschriftArt;
import de.ivu.elect.business.anschrift.entity.AnschriftProperties;
import de.ivu.elect.business.person.entity.AbstractPersonendaten;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenProperties;
import de.ivu.elect.business.personenpruefung.control.PersonendatenMatcherKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.AbstractPersonendatenWahlrechtMatcher;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.WahlrechtGebietsart;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.WahlrechtPersonenregisterAbgleichbarkeit;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.HoechstalterAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.MindestalterAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.PersonenregisterAbgleichbarkeitKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.StaatsangehoerigkeitKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.WahlrechtsausschlussKriterium;
import de.ivu.elect.business.staat.entity.Staat;
import de.ivu.elect.business.wahl.entity.WahlterminConstants;
import de.ivu.elect.jee.presentation.dynamicform.AbstractBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.presentation.ViewFormBuilder;
import de.ivu.elect.presentation.person.form.AlterProperty;
import de.ivu.elect.presentation.person.form.StaatsangehoerigkeitProperty;

public class PersonenKonfliktWahlrechtlichePruefungAufloesenViewFormBuilder extends ViewFormBuilder {

  private static final long serialVersionUID = 1L;

  private static final String KRITERIUM_MSG_KEY_PREFIX = "personen.konflikt.wahlrechtliche.pruefung.ergebnis.kriterium.";

  public PersonenKonfliktWahlrechtlichePruefungAufloesenViewFormBuilder() {
    super();
  }

  public void addVerifiedPersonendatenProperties(AbstractPersonendatenWahlrechtMatcher matcher, FormBuilder formBuilder, Personendaten personendaten) {
    addVerifiedPersonendatenProperties(matcher, true, formBuilder, personendaten);
  }

  protected void addVerifiedPersonendatenProperties(AbstractPersonendatenWahlrechtMatcher matcher, boolean matcherEnabled, FormBuilder formBuilder, Personendaten personendaten) {
    PersonendatenProperties personendatenProperties = personendaten.getProperties();

    formBuilder.addProperty(personendatenProperties.getFamiliennameProperty());
    formBuilder.addProperty(personendatenProperties.getRufnameProperty());
    formBuilder.addProperty(personendatenProperties.getVornamenProperty());

    formBuilder.addProperty(personendatenProperties.getGeburtsdatumProperty());

    Date wahltag = personendaten.getWahldatum();

    Optional<MindestalterAmStichtagKriterium> mindestalterAmStichtagKriteriumOptional = findRelevantMindestalterAmStichtagKriterium(matcher, personendaten);

    Optional<HoechstalterAmStichtagKriterium> hoechstalterAmStichtagKriteriumOptional = findRelevantHoechstalterAmStichtagKriterium(matcher, personendaten);

    SortedSet<Date> alterStichtageSortedAsc = new TreeSet<>();

    if (mindestalterAmStichtagKriteriumOptional.isPresent()) {
      alterStichtageSortedAsc.add(mindestalterAmStichtagKriteriumOptional.get().getStichtag());
    }

    if (hoechstalterAmStichtagKriteriumOptional.isPresent()) {
      alterStichtageSortedAsc.add(hoechstalterAmStichtagKriteriumOptional.get().getStichtag());
    }

    alterStichtageSortedAsc.forEach(stichtag -> {
      String stichtagFormatted = formatDate(stichtag);

      if (wahltag.equals(stichtag)) {
        stichtagFormatted = getMessage(WahlterminConstants.MESSAGE_KEY_ENTITY_EINZAHL);
      }

      formBuilder.addProperty(new AlterProperty<>(personendaten, stichtag)).withLabel(getMessage(AbstractPersonendaten.MESSAGE_KEY_ALTER_EINZAHL, stichtagFormatted));

      if (matcherEnabled) {
        if (mindestalterAmStichtagKriteriumOptional.isPresent()) {
          MindestalterAmStichtagKriterium kriterium = mindestalterAmStichtagKriteriumOptional.get();

          if (stichtag.equals(kriterium.getStichtag()) && kriterium.matchesNot(personendaten)) {
            addVerificationFailedDetailsMessage(formBuilder, MindestalterAmStichtagKriterium.class);
          }
        }

        if (hoechstalterAmStichtagKriteriumOptional.isPresent()) {
          HoechstalterAmStichtagKriterium kriterium = hoechstalterAmStichtagKriteriumOptional.get();

          if (stichtag.equals(kriterium.getStichtag()) && kriterium.matchesNot(personendaten)) {
            addVerificationFailedDetailsMessage(formBuilder, HoechstalterAmStichtagKriterium.class);
          }
        }
      }
    });

    boolean staatsangehoerigkeitVerificationFailed = matcherEnabled && !matcher.matchesStaatsangehoerigkeiten(personendaten);

    if (personendaten.hasStaatsangehoerigkeit()) {
      List<Staat> staatsangehoerigkeitenSorted = personendaten.getStaatsangehoerigkeitenSorted();

      for (int i = 0; i < staatsangehoerigkeitenSorted.size(); i++) {
        formBuilder.addProperty(new StaatsangehoerigkeitProperty(i, personendaten, staatsangehoerigkeitenSorted.get(i)));

        if (i == 0 && staatsangehoerigkeitVerificationFailed) {
          addVerificationFailedDetailsMessage(formBuilder, StaatsangehoerigkeitKriterium.class);
        }
      }
    } else {
      formBuilder.addProperty(new StaatsangehoerigkeitProperty(0, personendaten, null));

      if (staatsangehoerigkeitVerificationFailed) {
        addVerificationFailedDetailsMessage(formBuilder, StaatsangehoerigkeitKriterium.class);
      }
    }

    formBuilder.addProperty(personendatenProperties.getWahlrechtsausschlussProperty());

    if (matcherEnabled && !matcher.matchesWahlrechtsauschluesse(personendaten)) {
      addVerificationFailedDetailsMessage(formBuilder, WahlrechtsausschlussKriterium.class);
    }

    formBuilder.addProperty(personendatenProperties.getPersonStatusProperty());

    PersonenregisterAbgleichbarkeitKriterium personenregisterAbgleichbarkeitKriterium = matcher.collectPersonenregisterAbgleichbarkeitKriterium(personendaten);
    boolean personenregisterAbgleichbarkeitRelevant = !personenregisterAbgleichbarkeitKriterium.getWahlrechtPersonenregisterAbgleichbarkeit().isIrrelevant();

    if (personenregisterAbgleichbarkeitRelevant) {
      AbstractBooleanProperty auswaertigProperty = personendatenProperties.getAuswaertigProperty();

      formBuilder.addProperty(new ReadOnlyStringProperty(auswaertigProperty.getLabel(), getMessage("component.value." + auswaertigProperty.getValue())));

      if (matcherEnabled && !personenregisterAbgleichbarkeitKriterium.matches(personendaten)) {
        addVerificationFailedDetailsMessage(formBuilder, PersonenregisterAbgleichbarkeitKriterium.class);
      }
    }

    boolean mindestaufenthaltAnAnschriftArtInGebietAmStichtagRelevant = !matcher.collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten).isEmpty();

    if (personenregisterAbgleichbarkeitRelevant || mindestaufenthaltAnAnschriftArtInGebietAmStichtagRelevant) {
      addVerifiedAnschriftProperties(matcher, mindestaufenthaltAnAnschriftArtInGebietAmStichtagRelevant, formBuilder, personendaten);
    }
  }

  protected void addVerifiedAnschriftProperties(AbstractPersonendatenWahlrechtMatcher matcher, boolean matcherEnabled, FormBuilder formBuilder, Personendaten personendaten) {
    PersonendatenProperties personendatenProperties = personendaten.getProperties();

    Anschrift anschrift = personendaten.getAnschrift();
    AnschriftProperties anschriftProperties = anschrift.getProperties();

    List<MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium> mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien = matcher
        .collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten);

    formBuilder.addProperty(personendatenProperties.getZuzugsdatumBundProperty());

    if (matcherEnabled && !matchesMindestaufenthaltInGebiet(mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien, personendaten, WahlrechtGebietsart.BUND)) {
      addMindestaufenthaltInGebietVerificationFailedDetailsMessage(formBuilder);
    }

    formBuilder.addProperty(personendatenProperties.getZuzugsdatumLandProperty());

    if (matcherEnabled && !matchesMindestaufenthaltInGebiet(mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien, personendaten, WahlrechtGebietsart.LAND)) {
      addMindestaufenthaltInGebietVerificationFailedDetailsMessage(formBuilder);
    }

    formBuilder.addProperty(personendatenProperties.getZuzugsdatumGemeindeProperty());

    if (matcherEnabled && !matchesMindestaufenthaltInGebiet(mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien, personendaten, WahlrechtGebietsart.GEMEINDE)) {
      addMindestaufenthaltInGebietVerificationFailedDetailsMessage(formBuilder);
    }

    formBuilder.addProperty(personendatenProperties.getZuzugsdatumStadtbezirkProperty());

    if (matcherEnabled && !matchesMindestaufenthaltInGebiet(mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien, personendaten, WahlrechtGebietsart.BEZIRK)) {
      addMindestaufenthaltInGebietVerificationFailedDetailsMessage(formBuilder);
    }

    formBuilder.addProperty(anschriftProperties.getAnschriftArtProperty());

    if (matcherEnabled && !matchesMindestaufenthaltAnAnschriftArt(mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien, personendaten, anschrift.getAnschriftArt())) {
      addVerificationFailedDetailsMessage(formBuilder, KRITERIUM_MSG_KEY_PREFIX + "mindestaufenthalt.an.anschrift.art");
    }

    formBuilder.addProperty(anschriftProperties.getStrasseProperty());
    formBuilder.addProperty(anschriftProperties.getHausnummerProperty());
    formBuilder.addProperty(anschriftProperties.getHausnummerzusatzProperty());
    formBuilder.addProperty(anschriftProperties.getWohnungsnummerProperty());

    formBuilder.addProperty(anschriftProperties.getPlzProperty());
    formBuilder.addProperty(anschriftProperties.getOrtProperty());
    formBuilder.addProperty(anschriftProperties.getOrtsteilProperty());

    if (matcherEnabled) {
      matcher.collectWahlrechtGebietsartInReferenzGebietKriterien(personendaten).forEach(kriterium -> {
        if (kriterium.getWahlrechtGebietsart().equals(WahlrechtGebietsart.GEMEINDE) && kriterium.matchesNot(personendaten)) {
          addVerificationFailedDetailsMessage(formBuilder, kriterium.getClass());
        }
      });
    }

    formBuilder.addProperty(anschriftProperties.getZustellanweisungProperty());
  }

  private boolean matchesMindestaufenthaltInGebiet(List<MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium> mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien,
      Personendaten personendaten, WahlrechtGebietsart wahlrechtGebietsart) {
    for (MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium kriterium : mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien) {
      if (kriterium.getWahlrechtGebietsart().equals(wahlrechtGebietsart) && !kriterium.matchesMindestaufenthaltInGebietAmStichtag(personendaten)) {
        return false;
      }
    }

    return true;
  }

  private boolean matchesMindestaufenthaltAnAnschriftArt(List<MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium> mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien,
      Personendaten personendaten, AnschriftArt anschriftArt) {
    for (MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium kriterium : mindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien) {
      if (!kriterium.matchesAnschriftArt(personendaten)) {
        return false;
      }
    }

    return true;
  }

  private void addMindestaufenthaltInGebietVerificationFailedDetailsMessage(FormBuilder formBuilder) {
    addVerificationFailedDetailsMessage(formBuilder, KRITERIUM_MSG_KEY_PREFIX + "mindestaufenthalt.in.gebiet");
  }

  public AdvancedDynaFormModel addPruefSchrittErgebnisProperties(AbstractPersonendatenWahlrechtMatcher matcher, FormBuilder formBuilder, Personendaten personendaten) {
    PersonenWahlrechtPruefSchrittErgebnisProperties properties = new PersonenWahlrechtPruefSchrittErgebnisProperties(matcher, personendaten);

    findRelevantMindestalterAmStichtagKriterium(matcher, personendaten)
        .ifPresent(kriterium -> formBuilder.addProperty(properties.getMindestalterAmStichtagProperty(kriterium.getStichtag(), kriterium.getMindestalterInJahren())));

    findRelevantHoechstalterAmStichtagKriterium(matcher, personendaten)
        .ifPresent(kriterium -> formBuilder.addProperty(properties.getHoechstalterAmStichtagProperty(kriterium.getStichtag(), kriterium.getHoechstalterInJahren())));

    List<StaatsangehoerigkeitKriterium> staatsangehoerigkeitKriterien = matcher.collectStaatsangehoerigkeitKriterien(personendaten);

    for (int i = 0; i < staatsangehoerigkeitKriterien.size(); i++) {
      formBuilder.addProperty(properties.getStaatsangehoerigkeitZugelassenProperty(staatsangehoerigkeitKriterien.get(i).getWahlrechtStaatsangehoerigkeit()));

      if (i > 0) {
        formBuilder.withLabel("");
      }
    }

    List<WahlrechtsausschlussKriterium> wahlrechtsausschlussKriterien = matcher.collectWahlrechtsausschlussKriterien(personendaten);

    for (int i = 0; i < wahlrechtsausschlussKriterien.size(); i++) {
      formBuilder.addProperty(properties.getWahlrechtsausschlussProperty(wahlrechtsausschlussKriterien.get(i).getWahlrechtsausschluss()));

      if (i > 0) {
        formBuilder.withLabel("");
      }
    }

    for (MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium kriterium : matcher.collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten)) {
      formBuilder.addProperty(properties.getMindestaufenthaltAnAnschriftArtInGebietAmStichtagProperty(kriterium.getStichtag(), kriterium.getWahlrechtAnschriftsart(),
          kriterium.getWahlrechtGebietsart(), kriterium.getMindestaufenthaltInMonaten()));
    }

    WahlrechtPersonenregisterAbgleichbarkeit personenregisterAbgleichbarkeit = matcher.collectPersonenregisterAbgleichbarkeitKriterium(personendaten).getWahlrechtPersonenregisterAbgleichbarkeit();

    if (!personenregisterAbgleichbarkeit.isIrrelevant()) {
      formBuilder.addProperty(properties.getPersonenregisterAbgleichbarkeitProperty(personenregisterAbgleichbarkeit));
    }

    return formBuilder.createModel();
  }

  protected Optional<MindestalterAmStichtagKriterium> findRelevantMindestalterAmStichtagKriterium(AbstractPersonendatenWahlrechtMatcher matcher, Personendaten personendaten) {
    MindestalterAmStichtagKriterium result = null;

    List<MindestalterAmStichtagKriterium> mindestalterAmStichtagKriterienSortedByStichtagAsc = matcher.collectMindestalterAmStichtagKriterien(personendaten);
    Collections.sort(mindestalterAmStichtagKriterienSortedByStichtagAsc);

    // Frühester Stichtag mit höchstem Alter!
    for (MindestalterAmStichtagKriterium mindestalterAmStichtagKriterium : mindestalterAmStichtagKriterienSortedByStichtagAsc) {
      if (result == null || mindestalterAmStichtagKriterium.getMindestalterInJahren() > result.getMindestalterInJahren()) {
        result = mindestalterAmStichtagKriterium;
      }
    }

    return Optional.ofNullable(result);
  }

  protected Optional<HoechstalterAmStichtagKriterium> findRelevantHoechstalterAmStichtagKriterium(AbstractPersonendatenWahlrechtMatcher matcher, Personendaten personendaten) {
    HoechstalterAmStichtagKriterium result = null;

    List<HoechstalterAmStichtagKriterium> hoechstalterAmStichtagKriterienSortedByStichtagAsc = matcher.collectHoechstalterAmStichtagKriterien(personendaten);
    Collections.sort(hoechstalterAmStichtagKriterienSortedByStichtagAsc);

    // Spätester Stichtag mit geringstem Alter!
    for (HoechstalterAmStichtagKriterium hoechstalterAmStichtagKriterium : hoechstalterAmStichtagKriterienSortedByStichtagAsc) {
      if (result == null || hoechstalterAmStichtagKriterium.getHoechstalterInJahren() <= result.getHoechstalterInJahren()) {
        result = hoechstalterAmStichtagKriterium;
      }
    }

    return Optional.ofNullable(result);
  }

  protected void addVerificationFailedDetailsMessage(FormBuilder formBuilder, Class< ? extends PersonendatenMatcherKriterium> kriteriumClass) {
    addVerificationFailedDetailsMessage(formBuilder, kriteriumClass.getSimpleName() + ".label");
  }

  private void addVerificationFailedDetailsMessage(FormBuilder formBuilder, String kriteriumMsgKey) {
    formBuilder.withWarningMessage(getMessage("personen.konflikt.wahlrechtliche.pruefung.INVALID_VALUE_detail", getMessage(kriteriumMsgKey)));
  }

  private String formatDate(Date date) {
    return getUserLocale().formatDate(date);
  }
}
