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

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

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

import org.slf4j.Logger;

import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.konfiguration.entity.CustomParameter;
import de.ivu.elect.business.konfiguration.entity.CustomParameterTemplate;
import de.ivu.elect.business.konfiguration.entity.KonfigurationInitContext;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.entity.tk.EntityTK;

public abstract class AbstractCustomParameterCreator<T extends CustomParameterTemplate, E extends Entity<?, ?> & CustomParameter<T>, K extends EntityTK> {

  @Inject
  private transient CurrentCustomer currentCustomers;

  protected AbstractCustomParameterCreator() {
    super();
  }

  protected void createOrUpdateDefaultParameters(Wahltermin wahltermin, Wahl wahl, Mandant mandant, Benutzer benutzer, List<E> parametersInDb,
                                                 Iterable<? extends CustomParameterInit<E>> parameterInits) {
    String context = "";

    if (wahltermin != null) {
      context = " fuer Wahltermin " + wahltermin.getName();

      if (wahl != null) {
        context = " fuer Wahl " + wahl.getName();
      }
    }
    if (mandant != null) {
      context += " fuer Mandant " + mandant.getNummer();
    }

    if (benutzer != null) {
      context = " fuer Benutzer " + benutzer.getBenutzerkennung();
    }

    info(getEntityNameMehrzahl() + context + " initialisieren");

    Map<K, E> tksInDb = parametersInDb.stream().collect(Collectors.toMap(e -> getEntityToTkFunktion().apply(e), e -> e, (a, b) -> b));
    Set<K> tksExpected = new HashSet<>();

    Set<E> parametersCreated = new HashSet<>();
    Set<E> parametersUnexpected = new HashSet<>();

    KonfigurationInitContext konfigurationInitContext = new KonfigurationInitContext(mandant, benutzer, wahltermin, wahl);
    for (CustomParameterInit<E> init : parameterInits) {
      for (E parameter : init.getDefaultParameters(konfigurationInitContext)) {
        if (hasAnyOfTheCurrentCustomers(parameter)) { // WASDE-996
          K tk = getEntityToTkFunktion().apply(parameter);

          tksExpected.add(tk);

          if (tksInDb.containsKey(tk)) {
            updateInDb(parameter, tksInDb.get(tk));
          } else {
            persistParameter(wahltermin, wahl, mandant, benutzer, parameter);

            info(getEntityNameEinzahl() + " mit Schlüssel " + parameter.getKey() + " und Wert " + parameter.getDisplayValueRaw() + " angelegt");

            parametersCreated.add(parameter);
          }
        }
      }
    }

    for (E parameter : parametersInDb) {
      K tk = getEntityToTkFunktion().apply(parameter);

      if (!tksExpected.contains(tk)) {
        parametersUnexpected.add(parameter);
      }
    }

    processParametersCreated(parametersCreated);
    processParametersUnexpected(parametersUnexpected);

    info(getEntityNameMehrzahl() + context + " initialisiert");
  }

  protected void updateInDb(E konfigurationAnzulegen, E konfigurationInDb) {
    if (isInitOverrideDb(konfigurationAnzulegen)) {
      info(
          "Aktualisiere Konfigurationswert " + konfigurationInDb + " von '" + konfigurationInDb.getDisplayValueRaw() + "' auf '" + konfigurationAnzulegen.getDisplayValueRaw() + "'");

      konfigurationInDb.setWert(konfigurationAnzulegen.getWert());
    }
  }

  protected boolean isInitOverrideDb(E konfigurationAnzulegen) {
    return konfigurationAnzulegen.isInitOverrideDb();
  }

  private boolean hasAnyOfTheCurrentCustomers(E parameter) {
    //noinspection Convert2streamapi Nicht durch Lambdas ersetzen: LambdaConversionException: Invalid receiver type interface
    for (Customer customer : currentCustomers.getCustomer().getContainedCustomers()) {
      if (parameter.hasCustomer(customer)) {
        return true;
      }
    }

    return false;
  }

  protected abstract void processParametersCreated(Collection<E> parameters);

  protected void processParametersUnexpected(Collection<E> parameters) {
    for (E parameter : parameters) {
      info(getEntityNameEinzahl() + " mit Schlüssel " + parameter.getKey() + " und Wert " + parameter.getDisplayValueRaw() + " IST NICHT MEHR RELEVANT");

      deleteParameter(parameter);

      info(getEntityNameEinzahl() + " mit Schlüssel " + parameter.getKey() + " und Wert " + parameter.getDisplayValueRaw() + " gelöscht");
    }
  }

  private void info(String msg) {
    getLog().info(msg);
  }

  private String getEntityNameMehrzahl() {
    return getEntityMeta().getMehrzahlMessage();
  }

  private String getEntityNameEinzahl() {
    return getEntityMeta().getEinzahlMessage();
  }

  protected abstract Meta getEntityMeta();

  protected abstract Function<? super E, ? extends K> getEntityToTkFunktion();

  protected abstract void persistParameter(Wahltermin wahltermin, @Nullable Wahl wahl, @Nullable Mandant mandant, @Nullable Benutzer benutzer, E parameter);

  protected abstract void deleteParameter(E parameter);

  protected abstract Logger getLog();
}
