package de.ivu.elect.business.benutzerverwaltung.boundary;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.ElectSystemProperties;
import de.ivu.elect.business.benutzerverwaltung.control.LogoutException;
import de.ivu.elect.business.benutzerverwaltung.entity.AngemeldeterBenutzer;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzort;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerPK;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.jee.security.ActiveUsers;
import de.ivu.elect.jee.security.IpHostname;
import de.ivu.elect.jee.security.LoginChangeEvent;
import de.ivu.elect.jee.security.LogoutChangeEvent;

/**
 * ELECT-106
 */
@ApplicationScoped
public class AngemeldeteBenutzer implements Serializable, ActiveUsers {

  private static final Logger LOG = LoggerFactory.getLogger(AngemeldeteBenutzer.class);

  private static final long serialVersionUID = 1L;

  private static final AtomicLong ID_POOL = new AtomicLong(0L);

  private static final Map<String, AngemeldeterBenutzer> ANGEMELDETE_BENUTZER_NACH_SESSION_ID = new ConcurrentHashMap<>();

  private static final Map<Long, String> LETZTE_SESSION_FUER_BENUTZER = new ConcurrentHashMap<>();

  private static final boolean NUR_EINE_SESSION_PRO_USER_SYSTEM_PROPERTY = Boolean.TRUE.toString()
      .equalsIgnoreCase(System.getProperty(ElectSystemProperties.ELECT_NUR_EINE_SESSION_PRO_USER));

  @UseCaseSecure(UseCasePermission.ANGEMELDETE_BENUTZER_SEHEN)
  public List<AngemeldeterBenutzer> getAngemeldeteBenutzer(Predicate<AngemeldeterBenutzer> filter) {

    Date letzteInteraktionNotwendig = getFruehestenZeitpunktAnDemNochEineInteraktionErfolgtSeinMussDamitDieSessionNochGueltigIst();

    List<AngemeldeterBenutzer> result = new ArrayList<>();

    ArrayList<AngemeldeterBenutzer> benutzer = new ArrayList<>(ANGEMELDETE_BENUTZER_NACH_SESSION_ID.values());
    for (AngemeldeterBenutzer angemeldeterBenutzer : benutzer) {
      if (angemeldeterBenutzer.getLetzteInteraktion().after(letzteInteraktionNotwendig)) {
        if (filter.test(angemeldeterBenutzer)) {
          result.add(angemeldeterBenutzer);
        }
      } else {
        logout(angemeldeterBenutzer.getSessionId());
      }
    }

    Collections.sort(result);
    return result;
  }

  // Nur intern verwenden, wenn Security geprüft, ELECTMUC-2335
  public List<AngemeldeterBenutzer> getAngemeldeteBenutzerNoPermissionCheck() {
    return getAngemeldeteBenutzer(x -> true);
  }

  public void onLoginChanged(@Observes(notifyObserver = Reception.ALWAYS) LoginChangeEvent loginChangeEvent) {
    logout(loginChangeEvent.getSessionId()); // erstmal alles bekannte wegschmeißen, aktuelle Daten kommen bei der ersten Interaction
  }

  public void login(String sessionId, BenutzerPK benutzerPK, Long mandantNummer, String kennung, IpHostname ipHostname, List<String> rollen,
                    BenutzerEinsatzort benutzerEinsatzort) {
    if (StringUtils.isBlank(sessionId)) {
      LOG.warn("Keine SessionId für Nutzer: " + kennung + ", Mandant= " + mandantNummer + ", " + ipHostname + ", benutzerPK=" + benutzerPK);
      return;
    }
    AngemeldeterBenutzer angemeldeterBenutzer = ANGEMELDETE_BENUTZER_NACH_SESSION_ID.get(sessionId);
    if (angemeldeterBenutzer == null) {
      String benutzerEinsatzortName = null;
      if (benutzerEinsatzort != null) {
        benutzerEinsatzortName = benutzerEinsatzort.getName();
      }
      angemeldeterBenutzer = new AngemeldeterBenutzer(ID_POOL.incrementAndGet(), sessionId, benutzerPK, mandantNummer, kennung, ipHostname, rollen,
          getSessionCreateTime(),
          benutzerEinsatzortName);
      ANGEMELDETE_BENUTZER_NACH_SESSION_ID.put(sessionId, angemeldeterBenutzer);
      try {
        LETZTE_SESSION_FUER_BENUTZER.put(benutzerPK.getId(), sessionId);
      } catch (Exception e) {
        LOG.warn("Last session error: " + e.getMessage());
      }

    } else {
      angemeldeterBenutzer.interact();
    }
  }

  private Date getSessionCreateTime() {
    try {
      HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance()
          .getExternalContext().getRequest();
      return new Date(httpServletRequest.getSession().getCreationTime());
    } catch (Exception e) {
      LOG.warn("Fehler beim Bestimmen der Session Creation Time: " + e.getMessage(), e);
      return new Date();
    }
  }

  public void onLogoutChanged(@Observes(notifyObserver = Reception.ALWAYS) LogoutChangeEvent logoutChangeEvent) {
    logout(logoutChangeEvent.getSessionId());
  }

  private void logout(String sessionId) {
    if (StringUtils.isNotBlank(sessionId)) {
      ANGEMELDETE_BENUTZER_NACH_SESSION_ID.remove(sessionId);
    }
  }

  private Date getFruehestenZeitpunktAnDemNochEineInteraktionErfolgtSeinMussDamitDieSessionNochGueltigIst() {
    try {
      long now = System.currentTimeMillis();
      HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance()
          .getExternalContext().getRequest();
      int maxInactiveIntervalInSeconds = httpServletRequest.getSession().getMaxInactiveInterval();

      long maxInactiveIntervalInMilliSeconds = maxInactiveIntervalInSeconds * 1000L;
      return new Date(now - maxInactiveIntervalInMilliSeconds);
    } catch (Exception e) {
      LOG.warn("Fehler beim Bestimmen der Session MaxInactiveInterval: " + e.getMessage(), e);
      return new Date(0L);
    }
  }

  public boolean interaction(String sessionId) {
    AngemeldeterBenutzer angemeldeterBenutzer = ANGEMELDETE_BENUTZER_NACH_SESSION_ID.get(sessionId);
    if (angemeldeterBenutzer == null) {
      return false;
    }

    checkLetzteSession(angemeldeterBenutzer, sessionId);

    angemeldeterBenutzer.interact();
    return true;
  }

  public void setzeBenutzereinsatzort(String sessionId, String benutzereinsatzOrtName) {
    AngemeldeterBenutzer angemeldeterBenutzer = ANGEMELDETE_BENUTZER_NACH_SESSION_ID.get(sessionId);
    if (angemeldeterBenutzer != null) {

      AngemeldeterBenutzer neu = new AngemeldeterBenutzer(angemeldeterBenutzer.getId(), angemeldeterBenutzer.getSessionId(),
          angemeldeterBenutzer.getBenutzerPK(), angemeldeterBenutzer.getMandantNummer(), angemeldeterBenutzer.getBenutzerkennung(),
          angemeldeterBenutzer.getIpAdresse(), angemeldeterBenutzer.getRollen(), angemeldeterBenutzer.getAnmeldezeit(), benutzereinsatzOrtName);
      ANGEMELDETE_BENUTZER_NACH_SESSION_ID.put(sessionId, neu);
    }
  }

  private void checkLetzteSession(AngemeldeterBenutzer angemeldeterBenutzer, String sessionId) {
    if (isNurEineSessionProUserErlaubt()) {
      if (angemeldeterBenutzer == null || angemeldeterBenutzer.getBenutzerPK() == null || angemeldeterBenutzer.getBenutzerPK()
          .getId() == null || StringUtils.isBlank(sessionId)) {
        return;
      }

      String erwarteteLetzteSessionId = LETZTE_SESSION_FUER_BENUTZER.get(angemeldeterBenutzer.getBenutzerPK().getId());

      if (StringUtils.isBlank(erwarteteLetzteSessionId)) {
        return;
      }

      if (!sessionId.equals(erwarteteLetzteSessionId)) {
        LOG.warn("Nicht die aktuelle Session - Logout: " + angemeldeterBenutzer);
        throw new LogoutException("elect.mehr.als.eine.session.fuer.nutzer");
      }
    }
  }

  protected boolean isNurEineSessionProUserErlaubt() {
    return NUR_EINE_SESSION_PRO_USER_SYSTEM_PROPERTY;
  }

  @Override
  public long getActiveUserCount() {
    return getAngemeldeteBenutzer(x -> true).size();
  }
}
