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

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.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.security.Customer;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.util.ClassUtil;


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

public interface CustomParameter<T extends CustomParameterTemplate> {

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

  String TIMESTAMP_PATTERN = "yyyyMMddHHmmss";

  default String getName(Messages messages) {
    return getTemplate().map(template -> messages.getMessageForMessageKey(ClassUtil.getClassSimpleName(template.getClass()) + "." + getKey())).orElse("");
  }

  default String getKurzname(Messages messages) {
    return getTemplate().map(template -> {
      String msgKeyLabel = generateKurznameMsgKey(template);

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

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

  static String generateKurznameMsgKey(CustomParameterTemplate template) {
    return ClassUtil.getClassSimpleName(template.getClass()) + "." + template.getKey() + ".label";
  }

  default String getBeschreibung(Messages messages) {
    return getTemplate().map(template -> {
      String msgKeyBeschreibung = generateBeschreibungMsgKey(template);

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

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

  static String generateBeschreibungMsgKey(CustomParameterTemplate template) {
    return ClassUtil.getClassSimpleName(template.getClass()) + "." + template.getKey() + ".beschreibung";
  }

  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);

  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();
  }

  @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
  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 e) {
      LOG.warn("Fehler beim Parsen: " + e.getMessage(), e);
      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);
    }

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

    return result;
  }

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

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

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

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

    return false;
  }

  default String getColumnDisplayValue(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 = repeat("*", 10);
      }
    }

    return result;
  }
}
