
package de.ivu.elect.jee.faces;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nullable;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpSession;

import de.ivu.elect.jee.cdi.BeanManagerUtil;
import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.faces.security.Shuffle;


import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.startsWith;

public abstract class AbstractEntityConverter implements Converter {

  private static final String PREFIX = "PK_HEADER";

  private static final Map<String, Class<?>> CLASS_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();

  @Inject
  private transient EntityConverterApplicationScope entityConverterApplicationScope;

  public AbstractEntityConverter() {
    super();
  }

  @Nullable
  @Override
  public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
    try {
      if (startsWith(value, PREFIX)) {
        return findEntity(decodeData(facesContext, value));
      }
    } catch (Exception e) {
      return null; // ELECTVAPP-354 nichts loggen
    }

    return null;
  }

  EntityConverterData decodeData(FacesContext facesContext, String value) {
    return decodeClassnameAndPrimaryKey(value.substring(PREFIX.length()), getShuffleOffset(facesContext));
  }

  @Nullable
  @Override
  public String getAsString(FacesContext facesContext, UIComponent component, Object value) {
    if (value instanceof Entity< ? , ? >) {
      Entity< ? , ? > entity = (Entity< ? , ? >) value;

      return PREFIX + encodeClassnameAndPrimaryKey(entity.getEntityClass(), entity.getId(), getShuffleOffset(facesContext));
    }

    return null;
  }

  private EntityConverterApplicationScope getEntityConverterApplicationScope() {
    if (isNull(entityConverterApplicationScope)) {
      // entityConverterApplicationScope = FacesContextUtil.getElValue("#{" + EntityConverterApplicationScope.COMPONENT_NAME + "}",       EntityConverterApplicationScope.class);
      entityConverterApplicationScope = BeanManagerUtil.getBeanByClass(EntityConverterApplicationScope.class);
    }

    return entityConverterApplicationScope;
  }

  String getShuffleOffset(FacesContext facesContext) {
    if (getShuffle().isEnabled()) {
      HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false);

      return session.getId();
    }

    return "";
  }

  private EntityConverterData decodeClassnameAndPrimaryKey(String value, String key) {
    if (getShuffle().isEnabled()) {
      byte[] data = decode(decodeBase(value.getBytes(StandardCharsets.UTF_8)), key);
      try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
        return (EntityConverterData) ois.readObject();
      } catch (IOException | ClassNotFoundException ex) {
        throw new RuntimeException(ex.getMessage(), ex);
      }
    }

    return new EntityConverterData(new String(decodeBase(value.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
  }

  private byte[] decodeBase(byte[] content){
    // ELECT-11147 return Base64.getDecoder().decode(content);
    return content;
  }

  private byte[] encodeBase(byte[] content){
    // ELECT-11147 return Base64.getEncoder().encode(content);
    return content;
  }

  byte[] decode(byte[] byteArray, String key) {
    return getShuffle().doShuffle(byteArray, key);
  }

  private String encodeClassnameAndPrimaryKey(Class<?> entityClass, Long primaryKey, String key) {
    EntityConverterData entityConverterData = new EntityConverterData(entityClass.getName(), primaryKey);

    byte[] content;
    if (getShuffle().isEnabled()) {
      try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
          oos.writeObject(entityConverterData);
        }
        content = encode(baos.toByteArray(), key);
      } catch (IOException ex) {
        throw new RuntimeException(ex.getMessage(), ex);
      }
    } else {
      content = entityConverterData.writeToString().getBytes(StandardCharsets.UTF_8);
    }


    return new String(encodeBase(content), StandardCharsets.UTF_8);
  }

  byte[] encode(byte[] byteArray, String key) {
    return getShuffle().shuffle(byteArray, key);
  }

  Shuffle getShuffle() {
    return getEntityConverterApplicationScope().getShuffle();
  }

  @Nullable
  Object findEntity(EntityConverterData converterData) {
    Long primaryKey = converterData.getPrimaryKey();

    if (nonNull(primaryKey)) {
      return getEntityManager().find(findClass(converterData.getClassName()), primaryKey);
    }

    return null;
  }

  private EntityManager getEntityManager() {
    return getEntityConverterApplicationScope().getExtendedDAO().getEntityManager();
  }

  protected static Class< ? > findClass(String className)  {
    return CLASS_CONCURRENT_HASH_MAP.computeIfAbsent(className, x -> {
      try {
        return Class.forName(className);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e.getMessage(), e);
      }
    });
  }
}
