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

import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.repeat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlInputText;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.konfiguration.control.EnumValueNotDeprecatedValidator;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.util.ClassUtil;

public interface CustomParameter<T extends CustomParameterTemplate> {

  Logger LOG = LoggerFactory.getLogger(CustomParameter.class);

  String TIMESTAMP_PATTERN = "yyyyMMddHHmmss";

  default String getName(Messages messages) {
    return getMessage(messages, "");
  }

  default String getKurzname(Messages messages) {
    return defaultIfBlank(getMessage(messages, "label"), getName(messages));
  }

  static String generateKurznameMsgKey(CustomParameterTemplate template) {
    return generateMsgKey(template, "label");
  }

  default String getBeschreibung(Messages messages) {
    return getMessage(messages, "beschreibung");
  }

  default String getMessage(Messages messages, String msgKeySuffix) {
    return getTemplate().map(template -> {
      var msgKeyLabel = generateMsgKey(template, msgKeySuffix);

      if (messages.isMessageKeyVorhanden(msgKeyLabel)) {
        return messages.getMessageForMessageKey(msgKeyLabel);
      }

      return "";
    }).orElse("");
  }

  static String generateMsgKey(CustomParameterTemplate template, String msgKeySuffix) {
    return ClassUtil.getClassSimpleName(template.getClass()) + "." + template.getKey() + (isNotBlank(msgKeySuffix) ? "." : "") + msgKeySuffix;
  }

  default Optional<T> getTemplate() {
    // NOTE ELECT-3635, dass Konfigurationseinträge existieren, deren 'Key' nicht dem Namen des Enum-Wertes entsprechen, bspw. der Key 'WAHLHELFER_EXTERNE_QUELLE_BEREITSCHAFT_DD.1'.
    return getTemplates().stream().filter(value -> value.getKey().equals(getKey())).findFirst();
  }

  default boolean hasCustomer(Customer customer) {
    return getTemplate().map(template -> template.hasCustomer(customer)).orElse(false);
  }

  Collection<T> getTemplates();

  String getKey();

  void setKey(String key);

  default String getWertMaskiert() {
    return repeat("*", 10);
  }

  /**
   * @return den TECHNISCHEN Wert, der ggf. sensible Daten enthält, die nicht ohne Weiteres geloggt oder ausgegeben werden dürfen!!!
   *
   * @see CustomParameter#getDisplayValueRaw
   * @see CustomParameter#getDisplayValueFormatted
   */
  String getWert();

  void setWert(String wert);

  default CustomParameterWertTyp getWertTyp() {
    return getTemplate().map(T::getWertTyp).orElse(CustomParameterWertTyp.STRING);
  }

  default boolean expectsWertIsStringMultiline() {
    return CustomParameterWertTyp.STRING_MULTILINE.equals(getWertTyp());
  }

  default boolean expectsWertIsSelect() {
    return getWertTyp().expectsWertIsSelect();
  }

  default boolean expectsWertIsReadonly() {
    return getWertTyp().expectsWertIsReadonly();
  }

  default boolean expectsWertIsBoolean() {
    return getWertTyp().expectsWertIsBoolean();
  }

  default boolean expectsWertIsDate() {
    return getWertTyp().expectsWertIsDate();
  }

  default boolean expectsWertIsTimestamp() {
    return getWertTyp().expectsWertIsTimestamp();
  }

  default boolean expectsWertIsEnum() {
    return getWertEnumClass().isPresent();
  }

  default boolean expectsWertIsSecret() {
    return getWertTyp().expectsWertIsSecret();
  }

  default boolean expectsWertIsCrontab() {
    return getWertTyp().expectsWertIsCrontab();
  }

  @Nullable
  default Enum< ? > getWertEnumValue() {
    String name = getWert();

    return getWertEnumClass().flatMap(aClass -> CustomParameterTemplate.allEnumValues(aClass).stream().filter(anEnum -> anEnum.name().equals(name)).findFirst()).orElse(null);
  }

  @Nullable
  @SuppressWarnings("unchecked")
  default <V extends Enum< ? >> V getWertEnumValue(Class<V> enumClass) {
    String name = getWert();

    return (V) CustomParameterTemplate.allEnumValues(enumClass).stream().filter(anEnum -> anEnum.name().equals(name)).findFirst().orElse(null);
  }

  default void setWertEnumValue(Enum< ? > value) {
    setWert(value == null ? "" : value.name());
  }

  default Optional<Class< ? extends Enum< ? >>> getWertEnumClass() {
    return getTemplate().map(T::getWertEnumClass);
  }

  default boolean matchesKategorie(@Nonnull CustomParameterKategorie kategorie) {
    return getTemplate().map(template -> kategorie.equals(template.getKategorie())).orElse(false);
  }

  @Nullable
  static Date parseDate(String value) {
    if (isBlank(value)) {
      return null;
    }

    try {
      return new SimpleDateFormat(TIMESTAMP_PATTERN).parse(value);
    } catch (ParseException ex) {
      LOG.warn("Fehler beim Parsen: " + ex.getMessage(), ex);
    }

    return null;
  }

  default List<ValidatorException> validate() {
    List<ValidatorException> result = new ArrayList<>();

    UIComponent dummy = new HtmlInputText() {

      @Override
      public String getClientId() {
        return "dummy" + getKey(); // ELECT-12098
      }
    };

    dummy.setId("dummy" + getKey());
    dummy.getAttributes().put("label", getKey()); // ELECT-12098

    for (Validator validator : getWertTyp().getValidators(this)) {
      validate(result, dummy, validator, getWert());
    }

    getTemplate().ifPresent(template -> {
      for (Validator validator : template.getWertValidators()) {
        validate(result, dummy, validator, getWert());
      }
    });

    if (expectsWertIsEnum()) { // Sonderbehandlung an dieser Stelle, da der Validator einen Hinweis ausgeben soll, aber (in der Maske) nicht verhindern darf, dass ein als deprecated markierter Wert gespeichert werden kann...
      validate(result, dummy, new EnumValueNotDeprecatedValidator(), getWertEnumValue());
    }

    return result;
  }

  default void validate(List<ValidatorException> result, UIComponent dummy, Validator validator, Object value) {
    try {
      FacesContext facesContext = FacesContext.getCurrentInstance();

      // passiert nur beim Start der Anwendung
      if (facesContext == null) {
        facesContext = new CustomParameterFakeFacesContext();
      }

      validator.validate(facesContext, dummy, value);
    } catch (ValidatorException expected) {
      result.add(expected);
    } catch (Exception ex) {
      LOG.warn("Validation failed: " + ex.getMessage() + ", " + this + ", " + getKey() + ", " + value, ex); // ELECT-12098

      throw ex;
    }
  }

  default boolean isValid() {
    try {
      return validate().isEmpty();
    } catch (Exception expected) {
    }

    return false;
  }

  default String getDisplayValueRaw() {
    String result = getWert();

    if (isNotBlank(result) && expectsWertIsSecret()) {
      result = getWertMaskiert();
    }

    return result;
  }

  default String getDisplayValueFormatted(Messages messages, Locale locale) {
    String result = getWert();

    if (isNotBlank(result)) {
      if (expectsWertIsBoolean()) {
        result = messages.lokalisiere(Boolean.parseBoolean(result));
      } else if (expectsWertIsEnum()) {
        Enum< ? > enumValue = getWertEnumValue();

        if (enumValue != null) {
          result = messages.lokalisiere(enumValue);
        }
      } else if (expectsWertIsDate()) {
        Date value = CustomParameter.parseDate(result);

        if (value != null) {
          result = UserLocale.formatDateMedium(value, locale);
        }
      } else if (expectsWertIsTimestamp()) {
        Date value = CustomParameter.parseDate(result);

        if (value != null) {
          result = UserLocale.formatDateTimeMedium(value, locale);
        }
      } else if (expectsWertIsSecret()) {
        result = getWertMaskiert();
      }
    }

    return result;
  }

  default boolean isInitOverrideDb() {
    return (boolean) getTransientContextConfig().getOrDefault("CustomParameter.InitOverrideDb", false);
  }

  default void setInitOverrideDb() {
    getTransientContextConfig().put("CustomParameter.InitOverrideDb", true);
  }

  Map<String, Object> getTransientContextConfig();
}
