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

import de.ivu.elect.business.benutzerverwaltung.boundary.AbstractBenutzerDAO;
import de.ivu.elect.business.benutzerverwaltung.boundary.AbstractBenutzerEinsatzortDAO;
import de.ivu.elect.business.benutzerverwaltung.entity.*;
import de.ivu.elect.business.konfiguration.entity.MandantKonfigurationWerte;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.mandant.entity.MandantPK;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.entity.ElectPricipalContext;
import de.ivu.elect.business.security.entity.TfaStatus;
import de.ivu.elect.business.system.entity.ElectSimplePrincipal;
import de.ivu.elect.jee.security.AbstractPrincipal;
import de.ivu.elect.jee.security.LoginData;
import de.ivu.elect.jee.security.LoginGruppe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;

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

abstract class AbstractElectCommonPrincipal extends AbstractPrincipal<UseCasePermission, BenutzerPK, Benutzer, ElectPricipalContext> implements ElectSimplePrincipal {

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

  private static final long serialVersionUID = 1L;

  private boolean isAdmin = false;

  private String protokollBenutzerkennung;

  private Set<String> rollenNamen = new HashSet<>();

  private transient Map<BenutzerParameterWerte, String> benutzerEinstellungen;

  private BenutzerEinsatzortPK benutzerEinsatzortPk;

  private MandantPK mandantPk;

  private TfaStatus tfaStatus = TfaStatus.NICHT_BESTANDEN;

  protected AbstractElectCommonPrincipal() {
    super();
  }

  public String getFormattedName() {
    return getPrincipalContext().getPrincipalNameFormatter().getName(getLogin(), getBenutzerEinsatzort());
  }

  public String getFormattedName(AbstractBenutzerDAO benutzerDAO, AbstractBenutzerEinsatzortDAO benutzerEinsatzortDAO) {
    if (benutzerEinsatzortPk == null) {
      return getPrincipalContext().getPrincipalNameFormatter().getName(getLogin(benutzerDAO), null);
    }

    return getPrincipalContext().getPrincipalNameFormatter().getName(getLogin(benutzerDAO), benutzerEinsatzortDAO.findByPrimaryKey(benutzerEinsatzortPk));
  }

  @Override
  protected Collection<? extends UseCasePermission> loadUseCasePermissions(Benutzer login) {
    Mandant mandant = login.getMandant();

    try {
      getPrincipalContext().setzeLoginMandant(mandant.getNummer());
    } catch (Exception e) {
      LOG.warn("Fehler beim Setzen der Mandanten-Nummer: " + e.getMessage());
    }

    bestimmeBenutzerEinsatzort(mandant);

    mandantPk = mandant.getPk();
    BenutzerEinsatzort benutzerEinsatzort = getBenutzerEinsatzort();

    loggeRollen(login, benutzerEinsatzort);

    rollenNamen = new HashSet<>(bestimmeRollennamen(login, benutzerEinsatzort));

    uebernehmeEinstellungen(login.getBenutzerParametersByBenutzer());
    isAdmin = login.isAdmin();

    Collection<UseCasePermission> permissionTmp = bestimmePermissions(login, benutzerEinsatzort);

    Collection<UseCasePermission> result;
    if (permissionTmp.isEmpty()) {
      result = Collections.emptySet();
    } else {
      result = EnumSet.copyOf/*ELECT-16561*/(permissionTmp);
    }

    tfaStatus = bestimmeTfaStatus(mandant, result);

    return result;
  }

  public void setzeBenuterEinsatzort(BenutzerEinsatzortPK benutzerEinsatzortPK) {
    if (isBenutzereinsatzOrtGesetzt()) {
      throw new IllegalStateException("Benutzereinsatzort bereits gesetzt: " + this.benutzerEinsatzortPk);
    }
    if (benutzerEinsatzortPK == null) {
      return;
    }

    this.benutzerEinsatzortPk = benutzerEinsatzortPK;
    BenutzerEinsatzort benutzerEinsatzort = getBenutzerEinsatzort();
    if (benutzerEinsatzort == null) {
      throw new IllegalStateException("Benutzereinsatzort nicht gefunden: " + benutzerEinsatzortPk);
    }
    LOG.info("Neuer Benutzereinsatzort: " + benutzerEinsatzort);

    Set<Rolle> neueRollen = benutzerEinsatzort.getRolles();
    LOG.info("Rollen für Benutzereinsatzort: " + neueRollen);
    rollenNamen.addAll(neueRollen.stream().map(Rolle::getName).collect(Collectors.toList()));
    addPermissions(neueRollen.stream().flatMap(r -> r.getRecht().stream()).collect(Collectors.toSet()));
  }

  protected Collection<UseCasePermission> bestimmePermissions(Benutzer login, BenutzerEinsatzort benutzerEinsatzort) {
    return filterCurrentCustomer(login.getPermissions(benutzerEinsatzort));
  }

  protected List<String> bestimmeRollennamen(Benutzer login, BenutzerEinsatzort benutzerEinsatzort) {
    return login.getRollenAsStrings(benutzerEinsatzort);
  }

  protected TfaStatus bestimmeTfaStatus(Mandant mandant, Collection<UseCasePermission> result) {
    return getPrincipalContext().getTfaPrincipalStatus().bestimmeTfaStatus(mandant, result, isAdmin, getPrincipalContext());
  }

  protected Collection<UseCasePermission> filterCurrentCustomer(Collection<UseCasePermission> permissions) {
    return permissions.stream().filter(permission -> getPrincipalContext().getCurrentCustomer().hasUseCasePermission(permission)).collect(Collectors.toSet());
  }

  protected void loggeRollen(Benutzer login, BenutzerEinsatzort benutzerEinsatzort) {
    Set<Rolle> rollenOhneEinsatzort = new HashSet<>(login.getRolles());
    Set<Rolle> rollenMitEinsatzort = new HashSet<>(login.getAlleRollen(benutzerEinsatzort));

    LOG.info("Anwender: '" + login.getBenutzerkennung() + "' hat folgende Rollen: " + rollenMitEinsatzort.stream().map(Rolle::getName).sorted().collect(
        Collectors.joining(", ")));

    if (benutzerEinsatzort != null) {
      if (rollenMitEinsatzort.size() > rollenOhneEinsatzort.size()) {
        rollenMitEinsatzort.removeAll(rollenOhneEinsatzort);

        LOG.info("Anwender: '" + login.getBenutzerkennung() + "' - folgende Rollen über den Einsatzort '" + benutzerEinsatzort.getName() + "' zugewiesen: "
            + rollenMitEinsatzort.stream().map(Rolle::getName).sorted().collect(Collectors.joining(", ")));
      }
    }
  }

  protected void bestimmeBenutzerEinsatzort(Mandant mandant) {
    benutzerEinsatzortPk = getPrincipalContext().getBenutzerEinsatzortBestimmer().bestimmeBenutzerEinsatzort(mandant, getIpHostname());
  }

  private void ladeEinstellungen() {
    Benutzer login = getLogin();

    if (login != null) {
      uebernehmeEinstellungen(login.getBenutzerParametersByBenutzer());
    }
  }

  public void resetEinstellungen() {
    benutzerEinstellungen = null;
  }

  protected void uebernehmeEinstellungen(Collection<BenutzerParameter> benutzerParameterList) {
    benutzerEinstellungen = new EnumMap<>(BenutzerParameterWerte.class);

    for (BenutzerParameter benutzerParameter : benutzerParameterList) {
      if (BenutzerParameterWerte.isGueltigerSchluesel(benutzerParameter.getSchluessel())) {
        benutzerEinstellungen.put(BenutzerParameterWerte.valueOf(benutzerParameter.getSchluessel()), benutzerParameter.getWert());
      }
    }
  }

  @Override
  protected UseCasePermission getDefaultPermission() {
    return UseCasePermission.OHNE;
  }

  @Override
  @Nullable
  public Benutzer getLogin() {
    return getLogin(getPrincipalContext().getBenutzerDao());
  }

  public String getBenutzerKennung() {
    Benutzer benutzer = getLogin();

    if (benutzer != null) {
      return benutzer.getBenutzerkennung();
    }

    return "";
  }

  @Nullable
  public BenutzerEinsatzort getBenutzerEinsatzort() {
    if (benutzerEinsatzortPk == null) {
      return null;
    }

    return getPrincipalContext().getBenutzerEinsatzortDAO().findByPrimaryKey(benutzerEinsatzortPk);
  }

  public boolean isBenutzereinsatzOrtGesetzt() {
    return benutzerEinsatzortPk != null;
  }

  @Nullable
  public Benutzer getLogin(AbstractBenutzerDAO benutzerDAO) {
    if (!isLoggedIn()) {
      return null;
    }

    Benutzer benutzer = benutzerDAO.findByPrimaryKey(getLoginPK());

    // ELECTVAPP-753
    if (benutzer == null || isGesperrt(benutzer)) {
      LOG.warn("Nutzer gesperrt - logout " + benutzer);
      logout();

      return null;
    }

    return benutzer;
  }

  @Override
  protected UseCasePermission getUseCasePermissionFuerAnmeldung() {
    return UseCasePermission.ANWENDUNG_BENUTZEN;
  }

  @Override
  protected int getSessionTimeoutInMinutes() {
    return getPrincipalContext().getKonfigurationLoader().resolveIntegerValue(getMandant(), MandantKonfigurationWerte.HTTP_SESSION_TIMEOUT_IN_MINUTEN);
  }

  @Override
  protected void updateGruppen(Benutzer benutzer, List<LoginGruppe> loginGruppen) {
    List<Rolle> rollenImSystem = getPrincipalContext().getRolleDao()
        .attachAll(getPrincipalContext().getPrincipalBenutzerCreator().findeRollenOderLegeAn(benutzer, loginGruppen));
    List<Rolle> rollenDesBenutzers = new ArrayList<>(benutzer.getRolles());

    for (Rolle rolle : rollenImSystem) {
      if (!rollenDesBenutzers.contains(rolle)) {
        benutzer.addRolle(rolle);
      }
    }

    for (Rolle rolleDesBenutzers : rollenDesBenutzers) {
      if (!rollenImSystem.contains(rolleDesBenutzers)) {
        benutzer.removeRolle(rolleDesBenutzers);
      }
    }
  }

  @Override
  protected Benutzer findOrCreateEntity(LoginData loginData) {
    Benutzer result;
    Mandant logInMandant = getPrincipalContext().getLogInMandant(loginData.getMandantenNummer());
    String benutzerkennung = loginData.getBenutzerkennung();

    List<Benutzer> benutzerInDb = getPrincipalContext().getBenutzerDao().findByBenutzerkennungUpper(logInMandant, benutzerkennung);

    if (benutzerInDb.isEmpty()) {
      LOG.info("Benutzerinformationen: '" + benutzerkennung + "' nicht gefunden, lege an");
      Benutzer benutzer = getPrincipalContext().getPrincipalBenutzerCreator().createBenutzer(logInMandant, loginData);
      result = getPrincipalContext().getBenutzerDao().attach(benutzer);
    } else {
      result = benutzerInDb.get(0);
      if (benutzerInDb.size() > 1) {
        LOG.error(
            "Mehr als ein Benutzer mit der Benutzerkennung ''" + benutzerkennung + "'' für den Mandanten: " + logInMandant + " vorhanden: " + benutzerInDb + ", verwende: " + result);
      }
    }

    protokollBenutzerkennung = result.getBenutzerkennungProtokoll();

    return result;
  }

  @Override
  public void flush() {
    getPrincipalContext().getBenutzerDao().flush();
  }

  @Override
  public boolean hatRolle(String rollenName) {
    if (isBlank(rollenName)) {
      return true;
    }

    return rollenNamen.contains(rollenName);
  }

  @Override
  protected void clearPrincipal() {
    super.clearPrincipal();
    resetEinstellungen();
    rollenNamen.clear();
    mandantPk = null;
    tfaStatus = TfaStatus.NICHT_BESTANDEN;
    isAdmin = false;
    protokollBenutzerkennung = null;
  }

  public Map<BenutzerParameterWerte, String> getBenutzerEinstellungen() {
    if (benutzerEinstellungen == null) {
      ladeEinstellungen();
      if (benutzerEinstellungen == null) {
        return Collections.emptyMap();
      }
    }
    return benutzerEinstellungen;
  }

  @Override
  public boolean hasUseCasePermission(UseCasePermission useCasePermission) {
    return super.hasUseCasePermission(useCasePermission) && getPrincipalContext().getSystemStatusPermissions().hasUseCasePermission(this, useCasePermission);
  }

  @Override
  public boolean isAdmin() {
    return isAdmin;
  }

  @Deprecated // PrincipalBenutzerParameterLoader verwenden
  public int getDefaultPageSize() {
    try {
      return Integer.parseInt(getBenutzerEinstellungen().get(BenutzerParameterWerte.ANZAHL_SUCHERGEBNISSE_PRO_SEITE));
    } catch (Exception e) {
      return 25;
    }
  }

  @Deprecated // PrincipalBenutzerParameterLoader verwenden
  public BenutzerEinstellungenMessageStickiness getMessageStickiness() {
    try {
      return BenutzerEinstellungenMessageStickiness.valueOf(getBenutzerEinstellungen().get(BenutzerParameterWerte.POPUP_MELDUNGEN_ENTFERNEN));
    } catch (Exception e) {
      return BenutzerEinstellungenMessageStickiness.STICKY;
    }
  }

  @Deprecated // PrincipalBenutzerParameterLoader verwenden
  public BenutzerEinstellungenSearchAction getSearchActions() {
    try {
      return BenutzerEinstellungenSearchAction.valueOf(getBenutzerEinstellungen().get(BenutzerParameterWerte.POSITION_SUCHAKTIONEN));
    } catch (Exception e) {
      return BenutzerEinstellungenSearchAction.MENU;
    }
  }

  @Deprecated // PrincipalBenutzerParameterLoader verwenden
  public BenutzerEinstellungenMessageSeverity getMessageSeverity() {
    try {
      return BenutzerEinstellungenMessageSeverity.valueOf(getBenutzerEinstellungen().get(BenutzerParameterWerte.ANZUZEIGENDE_MELDUNGSARTEN));
    } catch (Exception e) {
      return BenutzerEinstellungenMessageSeverity.INFO;
    }
  }

  @Deprecated // PrincipalBenutzerParameterLoader verwenden
  public BenutzerEinstellungenMessagePosition getMessagePosition() {
    try {
      return BenutzerEinstellungenMessagePosition.valueOf(getBenutzerEinstellungen().get(BenutzerParameterWerte.MESSAGE_POSITION));
    } catch (Exception e) {
      return BenutzerEinstellungenMessagePosition.POPUP;
    }
  }

  @Override
  @Nullable
  public Mandant getMandant() {
    if (isLoggedIn()) {
      Benutzer login = getLogin();

      if (login == null) {
        return null;
      }

      return login.getMandant();
    }

    return null;
  }

  @Override
  public MandantPK getMandantPK() {
    return mandantPk;
  }

  @Override
  public String getNameFuerProtokollierung() {
    return defaultIfBlank(getProtokollBenutzerkennung(), getName()); // ELECT-8253
  }

  protected String getProtokollBenutzerkennung() {
    return protokollBenutzerkennung;
  }

  public boolean isMustTfaChallenge() {
    return tfaStatus == null || tfaStatus.isMustTfaChallenge();
  }

  public void tfaChallengePassed() {
    tfaStatus = TfaStatus.BESTANDEN;
  }

  public boolean isLoggedInTfa() {
    return isLoggedIn() && !isMustTfaChallenge();
  }

  @Override
  public Collection<UseCasePermission> getUseCasePermissions() {
    return super.getUseCasePermissions();
  }
}
