
package de.ivu.elect.business.entschaedigung.control;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import javax.annotation.Nullable;
import javax.inject.Inject;

import de.ivu.elect.business.entschaedigung.boundary.EntschaedigungKriteriumGesamtbetrag;
import de.ivu.elect.business.entschaedigung.boundary.EntschaedigungToCalculateParameters;
import de.ivu.elect.business.entschaedigung.boundary.EntschaedigungsartDAO;
import de.ivu.elect.business.entschaedigung.entity.Entschaedigung;
import de.ivu.elect.business.entschaedigung.entity.Entschaedigungsart;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.presentation.search.WildcardConverter;

public abstract class AbstractPersonendatenEntschaedigungCalculator implements PersonendatenEntschaedigungCalculator {

  @Inject
  private transient PersonendatenDAO personendatenDAO;

  @Inject
  private transient EntschaedigungsartDAO entschaedigungsartDAO;

  public AbstractPersonendatenEntschaedigungCalculator() {
    super();
  }

  @Override
  public Collection<Entschaedigungsart> getSupportedEntschaedigungsarten(Wahl wahl, Wahltermin wahltermin) {
    String kriteriumPersonendatenArt = getSupportedPersonendatenArt().getTechnicalName();

    if (wahl != null) {
      return entschaedigungsartDAO.findByWahlAndKriteriumPersonendatenArt(wahl, kriteriumPersonendatenArt);
    }

    if (wahltermin != null) {
      return entschaedigungsartDAO.findByWahlterminAndKriteriumPersonendatenArt(wahltermin, kriteriumPersonendatenArt);
    }

    return emptyList();
  }

  @Override
  public Collection<Entschaedigung> calculate(EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator, EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    return findAllPersonendaten(entschaedigungToCalculateParameters).stream() //
        .flatMap(personendaten -> calculate(entschaedigungenFuerPersonendatenAggregator, personendaten, entschaedigungToCalculateParameters).stream()) //
        .collect(toSet());
  }

  @Override
  public Collection<Entschaedigung> calculate(EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator, Personendaten personendaten,
      EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    return calculateInternal(entschaedigungenFuerPersonendatenAggregator, personendatenDAO.attach(personendaten), entschaedigungToCalculateParameters);
  }

  protected Collection<Entschaedigung> calculateInternal(EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator, Personendaten personendaten,
      EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    Collection<Entschaedigung> result = new ArrayList<>();

    Collection<Entschaedigungsart> entschaedigungsartenToCalculate = new HashSet<>();
    entschaedigungsartenToCalculate.addAll(getSupportedEntschaedigungsarten(entschaedigungToCalculateParameters.getWahl(), entschaedigungToCalculateParameters.getWahltermin()));

    if (entschaedigungToCalculateParameters.hasEntschaedigungsarten()) {
      entschaedigungsartenToCalculate.retainAll(entschaedigungToCalculateParameters.getEntschaedigungsarten());
    }

    for (Entschaedigungsart entschaedigungsart : entschaedigungsartenToCalculate) {
      Entschaedigung entschaedigung = findOrCreateEntschaedigungFuerPersondendaten(entschaedigungenFuerPersonendatenAggregator, personendaten, entschaedigungsart);

      if (isEntschaedigungZulaessig(personendaten, entschaedigung, entschaedigungToCalculateParameters)) {
        BigDecimal gesamtbetrag = calculateGesamtbetragZulaessigOrNull(personendaten, entschaedigung, entschaedigungToCalculateParameters);

        if (gesamtbetrag != null) {
          entschaedigung.setGesamtbetrag(gesamtbetrag);

          result.add(entschaedigung);
        }
      }
    }

    return filterByEntschaedigungToCalculateParameters(result, entschaedigungToCalculateParameters);
  }

  protected boolean isEntschaedigungZulaessig(Personendaten personendaten, Entschaedigung entschaedigung, EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    return entschaedigungToCalculateParameters.getEntschaedigungKriteriumEinsatzStatus().getEntschaedigungStatus().contains(entschaedigung.getStatus());
  }

  @Nullable
  private BigDecimal calculateGesamtbetragZulaessigOrNull(Personendaten personendaten, Entschaedigung entschaedigung, EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    if (entschaedigung.isBerechnetPerAnzahl()) {
      return calculateGesamtbetragZulaessigByAnzahlOrNull(personendaten, entschaedigung, entschaedigungToCalculateParameters);
    }

    if (entschaedigung.isBerechnetPerEinsatz()) {
      return calculateGesamtbetragZulaessigByEinsatzOrNull(personendaten, entschaedigung, entschaedigungToCalculateParameters);
    }

    return null;
  }

  @Nullable
  protected BigDecimal calculateGesamtbetragZulaessigByAnzahlOrNull(Personendaten personendaten, Entschaedigung entschaedigung,
      EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    Long anzahl = entschaedigung.getAnzahl();

    if (anzahl == null) {
      anzahl = entschaedigung.getAnzahlDefault();
    }

    if (anzahl != null) {
      BigDecimal einzelbetrag = entschaedigung.getEinzelbetrag();

      if (einzelbetrag == null) {
        einzelbetrag = entschaedigung.getEinzelbetragDefault();
      }

      if (einzelbetrag != null) {
        return einzelbetrag.multiply(BigDecimal.valueOf(anzahl));
      }
    }

    return null;
  }

  @Nullable
  protected abstract BigDecimal calculateGesamtbetragZulaessigByEinsatzOrNull(Personendaten personendaten, Entschaedigung entschaedigung,
      EntschaedigungToCalculateParameters entschaedigungToCalculateParameters);

  private Collection<Entschaedigung> filterByEntschaedigungToCalculateParameters(Collection<Entschaedigung> entschaedigungen, EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    return entschaedigungen.stream() //
        .filter(filterEntschaedigungByPersonendaten(entschaedigungToCalculateParameters) //
            .and(filterEntschaedigungByKontoverbindung(entschaedigungToCalculateParameters)) //
            .and(filterEntschaedigungByGesamtbetrag(entschaedigungToCalculateParameters))) //
        .collect(toSet());
  }

  private Predicate<Entschaedigung> filterEntschaedigungByPersonendaten(EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    String familienname = entschaedigungToCalculateParameters.getFamilienname();
    String vorname = entschaedigungToCalculateParameters.getVorname();

    if (isNotBlank(familienname) || isNotBlank(vorname)) {
      return entschaedigung -> entschaedigung.hasPersonendaten() //
          && matchesPersonendatenName(entschaedigung.getPersonendaten().getFamilienname(), familienname) //
          && matchesPersonendatenName(entschaedigung.getPersonendaten().getRufname(), vorname);
    }

    return entschaedigung -> true;
  }

  private boolean matchesPersonendatenName(String name, String nameContainingWildcards) {
    return trimToEmpty(name).matches(convertToPersonendatenNameRegEx(nameContainingWildcards));
  }

  private String convertToPersonendatenNameRegEx(String string) {
    return new WildcardConverter().addAndConvertToStringMatcherExpression(string);
  }

  private Predicate<Entschaedigung> filterEntschaedigungByKontoverbindung(EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    if (entschaedigungToCalculateParameters.hasKontoverbindungFilterKriterium()) {
      return entschaedigung -> entschaedigung.hasKontoverbindung() && entschaedigungToCalculateParameters.getKontoverbindungFilterKriterium().getPredicate().test(entschaedigung.getKontoverbindung());
    }

    return entschaedigung -> true;
  }

  private Predicate<Entschaedigung> filterEntschaedigungByGesamtbetrag(EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    EntschaedigungKriteriumGesamtbetrag entschaedigungKriteriumGesamtbetrag = entschaedigungToCalculateParameters.getEntschaedigungKriteriumGesamtbetrag();

    Predicate<Entschaedigung> predicateGesamtbetragNonZero = Entschaedigung::hasGesamtbetragNonZero;

    if (EntschaedigungKriteriumGesamtbetrag.NUR_GROESSER_NULL.equals(entschaedigungKriteriumGesamtbetrag)) {
      return predicateGesamtbetragNonZero;
    }

    if (EntschaedigungKriteriumGesamtbetrag.NUR_GLEICH_NULL.equals(entschaedigungKriteriumGesamtbetrag)) {
      return predicateGesamtbetragNonZero.negate();
    }

    return entschaedigung -> true;
  }

  private Entschaedigung findOrCreateEntschaedigungFuerPersondendaten(EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator, Personendaten personendaten,
      Entschaedigungsart entschaedigungsart) {
    Entschaedigung entschaedigung = findEntschaedigungFuerPersondendaten(entschaedigungenFuerPersonendatenAggregator, personendaten, entschaedigungsart)
        .orElseGet(() -> createEntschaedigungFuerPersondendaten(personendaten, entschaedigungsart));

    fillInEntschaedigungFuerPersondendatenDefaults(entschaedigung);

    if (!entschaedigung.hasEinzelbetrag()) {
      entschaedigung.setEinzelbetrag(entschaedigung.getEinzelbetragDefault());
    }

    if (!entschaedigung.hasAnzahl()) {
      entschaedigung.setAnzahl(entschaedigung.getAnzahlDefault());
    }

    return entschaedigung;
  }

  protected Optional<Entschaedigung> findEntschaedigungFuerPersondendaten(EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator, Personendaten personendaten,
      Entschaedigungsart entschaedigungsart) {
    return entschaedigungenFuerPersonendatenAggregator.findEntschaedigungByEntschaedigungsart(personendaten, entschaedigungsart);
  }

  protected Entschaedigung createEntschaedigungFuerPersondendaten(Personendaten personendaten, Entschaedigungsart entschaedigungsart) {
    Entschaedigung entschaedigung = new Entschaedigung();
    entschaedigung.setEntschaedigungsart(entschaedigungsart);
    entschaedigung.setPersonendaten(personendaten);

    entschaedigung.setBezeichnung(entschaedigungsart.getBezeichnung());

    entschaedigung.setWaehrungAlsIsoCode(entschaedigungsart.getWaehrungAlsIsoCode());

    return entschaedigung;
  }

  protected void fillInEntschaedigungFuerPersondendatenDefaults(Entschaedigung entschaedigung) {
    // To be overridden...
  }

  protected abstract Set<Personendaten> findAllPersonendaten(EntschaedigungToCalculateParameters entschaedigungToCalculateParameters);
}
