
package de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium;

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Date;

import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenpruefung.control.AbstractPersonendatenMatcherKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.WahlrechtAnschriftsart;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.WahlrechtGebietsart;
import de.ivu.elect.business.wahl.entity.WahlterminStichtagCalculator;
import de.ivu.elect.jee.faces.Messages;

public class MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium extends AbstractPersonendatenMatcherKriterium {

  private static final long serialVersionUID = 1L;

  private static final String KRITERIUM_WAHLTAG = "DatumWahltag";

  private static final String KRITERIUM_X_TAGE_VORM_WAHLTAG = "DatumXTageVormWahltag";

  private static final String KRITERIUM_TAG_DER_UNTERSCHRIFT = "DatumUnterschrift";

  private static final String KRITERIUM_TAG_DER_PRUEFUNG = "DatumPruefung";

  private final Date stichtag;

  private final int anzahlTageVormStichtag;

  private final WahlrechtAnschriftsart wahlrechtAnschriftsart;

  private final WahlrechtGebietsart wahlrechtGebietsart;

  private final Integer mindestaufenthalt;

  private final ChronoUnit mindestaufenthaltUnit;

  private final Date mindestzuzugsdatum;

  private final String descriptionMsgKeyInfix;

  public MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart,
      WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt, ChronoUnit mindestaufenthaltUnit, String descriptionMsgKeyInfix) {
    this(personendatenClasses, stichtag, 0, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, mindestaufenthaltUnit, descriptionMsgKeyInfix);
  }

  public MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, int anzahlTageVormStichtag,
      WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt, ChronoUnit mindestaufenthaltUnit, String descriptionMsgKeyInfix) {
    super(personendatenClasses);

    requireNonNull(stichtag, "Stichtag darf nicht null sein");
    requireNonNull(wahlrechtAnschriftsart, "WahlrechtAnschriftsart darf nicht null sein");
    requireNonNull(wahlrechtGebietsart, "WahlrechtGebietsart darf nicht null sein");

    this.stichtag = stichtag;
    this.anzahlTageVormStichtag = anzahlTageVormStichtag;

    this.wahlrechtAnschriftsart = wahlrechtAnschriftsart;
    this.wahlrechtGebietsart = wahlrechtGebietsart;

    this.mindestaufenthalt = mindestaufenthalt;
    this.mindestaufenthaltUnit = mindestaufenthaltUnit;

    this.mindestzuzugsdatum = calculateTagVormStichtag(stichtag, mindestaufenthalt, mindestaufenthaltUnit);

    this.descriptionMsgKeyInfix = descriptionMsgKeyInfix;
  }

  public Date getStichtag() {
    return stichtag;
  }

  private int getAnzahlTageVormStichtag() {
    return anzahlTageVormStichtag;
  }

  public WahlrechtAnschriftsart getWahlrechtAnschriftsart() {
    return wahlrechtAnschriftsart;
  }

  public WahlrechtGebietsart getWahlrechtGebietsart() {
    return wahlrechtGebietsart;
  }

  public Integer getMindestaufenthalt() {
    return mindestaufenthalt;
  }

  public ChronoUnit getMindestaufenthaltUnit() {
    return mindestaufenthaltUnit;
  }

  public Date getMindestzuzugsdatum() {
    return mindestzuzugsdatum;
  }

  public boolean isStichtagWahltag() {
    return KRITERIUM_WAHLTAG.equals(getDescriptionMsgKeyInfix());
  }

  public boolean isStichtagXTageVormWahltag() {
    return KRITERIUM_X_TAGE_VORM_WAHLTAG.equals(getDescriptionMsgKeyInfix());
  }

  public boolean isStichtagTagDerUnterschrift() {
    return KRITERIUM_TAG_DER_UNTERSCHRIFT.equals(getDescriptionMsgKeyInfix());
  }

  public boolean isStichtagTagDerPruefung() {
    return KRITERIUM_TAG_DER_PRUEFUNG.equals(getDescriptionMsgKeyInfix());
  }

  @Override
  protected boolean matchesInternal(Personendaten personendaten) {
    return matchesAnschriftArt(personendaten) && matchesMindestaufenthaltInGebietAmStichtag(personendaten);
  }

  public boolean matchesAnschriftArt(Personendaten personendaten) {
    return getWahlrechtAnschriftsart().equalsAnschriftArt(personendaten.getAnschrift());
  }

  public boolean matchesMindestaufenthaltInGebietAmStichtag(Personendaten personendaten) {
    if (getWahlrechtGebietsart().isIgnored() && getMindestaufenthalt() <= 0) {
      return true;
    }

    Date zuzugsdatum = tryResolveZuzugsdatumRecursive(personendaten, getWahlrechtGebietsart());

    return zuzugsdatum != null && !zuzugsdatum.after(getMindestzuzugsdatum());
  }

  // Cf. ELECTMUC-1055.
  private Date tryResolveZuzugsdatumRecursive(Personendaten personendaten, WahlrechtGebietsart wahlrechtGebietsart) {
    if (WahlrechtGebietsart.ALLE.equals(wahlrechtGebietsart) || WahlrechtGebietsart.BUND.equals(wahlrechtGebietsart)) {
      Date zuzugsdatumBund = personendaten.getZuzugsdatumBund();

      if (zuzugsdatumBund == null) {
        return tryResolveZuzugsdatumRecursive(personendaten, WahlrechtGebietsart.LAND);
      }

      return zuzugsdatumBund;
    }

    if (WahlrechtGebietsart.LAND.equals(wahlrechtGebietsart)) {
      Date zuzugsdatumLand = personendaten.getZuzugsdatumLand();

      if (zuzugsdatumLand == null) {
        return tryResolveZuzugsdatumRecursive(personendaten, WahlrechtGebietsart.GEMEINDE);
      }

      return zuzugsdatumLand;
    }

    if (WahlrechtGebietsart.GEMEINDE.equals(wahlrechtGebietsart)) {
      Date zuzugsdatumGemeinde = personendaten.getZuzugsdatumGemeinde();

      if (zuzugsdatumGemeinde == null) {
        return tryResolveZuzugsdatumRecursive(personendaten, WahlrechtGebietsart.BEZIRK);
      }

      return zuzugsdatumGemeinde;
    }

    if (WahlrechtGebietsart.BEZIRK.equals(wahlrechtGebietsart)) {
      return personendaten.getZuzugsdatumStadtbezirk();
    }

    throw new IllegalArgumentException("WahlrechtGebietsart ist ungueltig [wahlrechtGebietsart = " + wahlrechtGebietsart + "]");
  }

  @Override
  public String getDescription(Messages messages) {
    return messages.getMessageForMessageKey(getDescriptionMsgKey(), getStichtag(), messages.lokalisiere(getWahlrechtAnschriftsart()), messages.lokalisiere(getWahlrechtGebietsart()),
        getMindestaufenthalt(), getAnzahlTageVormStichtag());
  }

  @Override
  protected String getDescriptionMsgKey() {
    String result = getClass().getSimpleName() + "." + getWahlrechtGebietsart().name();

    if (isNotBlank(getDescriptionMsgKeyInfix())) {
      result += ".";
      result += getDescriptionMsgKeyInfix();
    }

    result += ".description";

    return result;
  }

  private String getDescriptionMsgKeyInfix() {
    return descriptionMsgKeyInfix;
  }

  @Override
  public String toString() {
    return "MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium [stichtag = " + getStichtag() + ", wahlrechtAnschriftsart = " + getWahlrechtAnschriftsart() + ", wahlrechtGebietsart = "
        + getWahlrechtGebietsart() + ", mindestaufenthalt = " + getMindestaufenthalt() + ", mindestaufenthaltUnit = " + getMindestaufenthaltUnit() + ", mindestzuzugsdatum = " + getMindestzuzugsdatum()
        + "]";
  }

  public static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietVormWahltagKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt) {
    return createMindestaufenthaltAnAnschriftArtInGebietVormWahltagKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, ChronoUnit.MONTHS);
  }

  public static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmXtenTagVormWahltagKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, int anzahlTageVormStichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart,
      WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt) {
    return createMindestaufenthaltAnAnschriftArtInGebietAmXtenTagVormWahltagKriterium(personendatenClasses, stichtag, anzahlTageVormStichtag, wahlrechtAnschriftsart, wahlrechtGebietsart,
        mindestaufenthalt, ChronoUnit.MONTHS);
  }

  public static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerUnterschriftKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt) {
    return createMindestaufenthaltAnAnschriftArtInGebietAmTagDerUnterschriftKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt,
        ChronoUnit.MONTHS);
  }

  public static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerPruefungKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt) {
    return createMindestaufenthaltAnAnschriftArtInGebietAmTagDerPruefungKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, ChronoUnit.MONTHS);
  }

  private static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltAnAnschriftArtInGebietVormWahltagKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt,
      ChronoUnit mindestaufenthaltUnit) {
    return new MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, mindestaufenthaltUnit,
        KRITERIUM_WAHLTAG);
  }

  private static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltAnAnschriftArtInGebietAmXtenTagVormWahltagKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, int anzahlTageVormStichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart,
      WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt, ChronoUnit mindestaufenthaltUnit) {
    return new MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(personendatenClasses, stichtag, anzahlTageVormStichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt,
        mindestaufenthaltUnit, KRITERIUM_X_TAGE_VORM_WAHLTAG);
  }

  private static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltAnAnschriftArtInGebietAmTagDerUnterschriftKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt,
      ChronoUnit mindestaufenthaltUnit) {
    return new MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, mindestaufenthaltUnit,
        KRITERIUM_TAG_DER_UNTERSCHRIFT);
  }

  private static MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltAnAnschriftArtInGebietAmTagDerPruefungKriterium(
      Collection<Class< ? extends Personendaten>> personendatenClasses, Date stichtag, WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthalt,
      ChronoUnit mindestaufenthaltUnit) {
    return new MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium(personendatenClasses, stichtag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthalt, mindestaufenthaltUnit,
        KRITERIUM_TAG_DER_PRUEFUNG);
  }

  private static Date calculateTagVormStichtag(Date stichtag, int anzahl, ChronoUnit unit) {
    if (ChronoUnit.MONTHS.equals(unit)) {
      return WahlterminStichtagCalculator.calculateTagVormStichtagByAnzahlMonate(stichtag, anzahl);
    }

    if (ChronoUnit.DAYS.equals(unit)) {
      return WahlterminStichtagCalculator.calculateTagVormStichtagByAnzahlTage(stichtag, anzahl);
    }

    throw new IllegalArgumentException("Unit not supported [anzahl = " + anzahl + ", unit = " + unit + "]");
  }
}
