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

import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.ejb.Stateless;
import javax.inject.Inject;

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

import de.ivu.elect.business.benutzerverwaltung.boundary.PermissionService;
import de.ivu.elect.business.benutzerverwaltung.boundary.RechteKonfigurationCreate;
import de.ivu.elect.business.benutzerverwaltung.boundary.RechteKonfigurationDAO;
import de.ivu.elect.business.benutzerverwaltung.boundary.RolleDAO;
import de.ivu.elect.business.benutzerverwaltung.control.RolleCreate;
import de.ivu.elect.business.benutzerverwaltung.entity.RechteKonfiguration;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;
import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.control.DefaultRollen;
import de.ivu.elect.business.security.entity.RolleTemplate;
import de.ivu.elect.business.security.entity.StandardRollenAnpassbarArt;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.jee.faces.Messages;


import static de.ivu.elect.business.konfiguration.entity.MandantKonfigurationWerte.ADMIN_ROLLE_DARF_ALLES;
import static de.ivu.elect.business.konfiguration.entity.MandantKonfigurationWerte.STANDARD_ROLLEN_ANPASSBAR;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;

@Stateless
public class RolleCreator {

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

  @Inject
  private transient RolleDAO rolleDao;

  @Inject
  private transient RolleCreate rolleCreate;

  @Inject
  private transient RechteKonfigurationDAO rechteKonfigurationDAO;

  @Inject
  private transient RechteKonfigurationCreate rechteKonfigurationCreate;

  @Inject
  private transient PermissionService permissionService;

  @Inject
  private transient KonfigurationLoader konfigurationLoader;

  @Inject
  private transient Messages messages;

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient MandantDAO mandantDAO;

  public RolleCreator() {
    super();
  }

  public void initRollenAlleMandanten() {
    for (Mandant mandant : mandantDAO.findByAll()) {
      initRollen(mandant);
    }
  }

  public void initRollen(Mandant mandant) {
    LOG.info("Initialisiere Rollen für Mandant: " + mandant.getNummer());

    Collection<? extends RolleTemplate> rollenDefiniertCustomer = Collections.emptyList();
    if (mandant.getArt().isCustomerMandant()) {
      rollenDefiniertCustomer = getAnzulegendeKundenRollen();
    }

    Map<String, Rolle> rolleInDbPerName = rolleDao.findByAll(mandant).stream().collect(toMap(Rolle::getName, rolle -> rolle, (a, b) -> b)/*ELECT-15839*/);

    if (rolleInDbPerName.isEmpty()) {
      LOG.info("Keine Rollen vorhanden - anlegen");

      if (isAdminRolleMitAllenRechtenAnlegen(mandant)) {
        persistRolle(mandant, RolleTemplate.ADMIN, u -> true);
      }

      if (isAnwenderRolleMitMinimalenRechtenAnlegen(mandant)) {
        persistRolle(mandant, RolleTemplate.ANWENDER, u -> true);
      }

      persistRollen(mandant, rollenDefiniertCustomer, u -> true, rolleInDbPerName);
    } else {
      Map<String, ? extends RolleTemplate> rolleTemplateAnzulegenPerName = rollenDefiniertCustomer.stream() //
          .filter(
              rolleTemplate -> rolleInDbPerName.keySet().stream().noneMatch(rolleNameInDb -> equalsIgnoreCase(rolleNameInDb, rolleTemplate.getRolleName()))) //
          .collect(toMap(RolleTemplate::getRolleName, rolleTemplate -> rolleTemplate));

      if (rolleTemplateAnzulegenPerName.isEmpty()) {
        LOG.info("Rollen bereits vorhanden");
      } else {
        LOG.info("Rollen aktualisieren");
        persistRollen(mandant, rolleTemplateAnzulegenPerName.values(), u -> true, rolleInDbPerName);
      }

      aktualisiereRollenWennNotwendig(mandant, rolleInDbPerName, rollenDefiniertCustomer);
    }
  }

  protected void aktualisiereRollenWennNotwendig(Mandant mandant, Map<String, Rolle> rolleInDbPerName, Collection<? extends RolleTemplate> rollenDefiniertCustomer) {

    Map<String, RolleTemplate> rollenTamplateNachName = new HashMap<>();
    for (RolleTemplate rolleTemplate : rollenDefiniertCustomer) {
      rollenTamplateNachName.put(rolleTemplate.getRolleName(), rolleTemplate);
    }

    StandardRollenAnpassbarArt standardRollenAnpassbarArt = konfigurationLoader
        .resolveEnumValue(mandant, STANDARD_ROLLEN_ANPASSBAR, StandardRollenAnpassbarArt.class);

    if (standardRollenAnpassbarArt != null && standardRollenAnpassbarArt.isBeimStartAktualisieren()) {
      for (Rolle rolleInDb : rolleInDbPerName.values()) {
        RolleTemplate rolleTemplate = rollenTamplateNachName.get(rolleInDb.getName());
        if (rolleTemplate != null) {
          // aktualisiere
          Set<UseCasePermission> rechteVorher = EnumSet.copyOf(rolleInDb.getRecht());
          Set<UseCasePermission> neueRechte = new HashSet<>();
          rolleInDb.removeAllRechts();
          Set<UseCasePermission> alleMoeglichenRechteDerAnwendung = EnumSet.copyOf(getAlleMoeglichenRechteDerAnwendung(mandant));
          for (UseCasePermission recht : rolleTemplate.getRechte(permissionService)) {
            if (alleMoeglichenRechteDerAnwendung.contains(recht) /*ELECT-11040*/) {
              rolleInDb.addRecht(recht);
              if (!rechteVorher.contains(recht)) {
                neueRechte.add(recht);
              }
            }
          }
          LOG.info("Rolle auf Standard-Rechte gesetzt: " + rolleInDb.getName() + ", " + rolleInDb.getRecht());
          if (isNeueRechteLoggen() && !neueRechte.isEmpty()) {
            String neueRechteText = getRechteAlsText(neueRechte);
            systemLogWriter.writeInfoLogMessageFuerMandant(mandant, messages.getMessageForMessageKey("rolle.rechte.neu", rolleInDb.getName(), neueRechteText),
                SystemLogQuelle.SYSTEM);
          }

        }
      }
    } else {
      LOG.info("Standard-Rollen werden nicht aktualisiert");
    }

  }

  private String getRechteAlsText(Set<UseCasePermission> neueRechte) {
    return neueRechte.stream().map(u -> messages.lokalisiere(u)).sorted().collect(Collectors.joining(", "));
  }

  protected boolean isAdminRolleMitAllenRechtenAnlegen(Mandant mandant) {
    return true;
  }

  protected boolean isAnwenderRolleMitMinimalenRechtenAnlegen(Mandant mandant) {
    return true;
  }

  public Collection<? extends RolleTemplate> getAnzulegendeKundenRollen() {
    return asList(DefaultRollen.values());
  }

  private Rolle persistRolle(Mandant mandant, RolleTemplate rolleTemplate, Predicate<UseCasePermission> filter) {
    Collection<UseCasePermission> rechteNichtInAnwendung = getRechteNichtInAnwendung(mandant);

    Collection<UseCasePermission> rolleRechteInAnwendung = new HashSet<>(rolleTemplate.getRechte(permissionService));
    rolleRechteInAnwendung.removeAll(rechteNichtInAnwendung); // Rechte abziehen, die gar nicht für die Anwendung vorgesehen sind...
    rolleRechteInAnwendung.removeIf(u -> !filter.test(u));

    // Nur Rollen anlegen, die mindestens ein Recht in der Anwendung enthalten...
    if (!rolleRechteInAnwendung.isEmpty()) {
      rolleRechteInAnwendung.add(UseCasePermission.ANWENDUNG_BENUTZEN); // Recht automatisch hinzufügen, ist immer notwendig

      return persistRolle(mandant, rolleTemplate.getRolleName(), rolleTemplate.getRolleBeschreibung(), rolleRechteInAnwendung);
    }
    return null;
  }

  private void persistRollen(Mandant mandant, Collection<? extends RolleTemplate> rolleTemplates, Predicate<UseCasePermission> filter, Map<String, Rolle> rolleInDbPerName) {
    for (RolleTemplate rolleTemplate : rolleTemplates) {

      Rolle neueRolle = persistRolle(mandant, rolleTemplate, filter);
      if (neueRolle != null) {
        rolleInDbPerName.put(neueRolle.getName(), neueRolle);
      }
    }

    LOG.info("Rollen angelegt");
  }

  private Rolle persistRolle(Mandant mandant, String name, String beschreibung, Collection<UseCasePermission> rechte) {
    Rolle rolle = new Rolle();
    rolle.setName(name);
    rolle.setBeschreibung(beschreibung);

    for (UseCasePermission recht : rechte) {
      rolle.addRecht(recht);
    }

    rolle.setMandant(mandant);
    rolleCreate.save(rolle);

    LOG.info("Rolle '" + rolle.getName() + "' für Mandanten " + mandant.getNummer() + " angelegt");
    return rolle;
  }

  public void aktualisiereAdminRolleAlleMandanten() {
    for (Mandant mandant : mandantDAO.findByAll()) {
      aktualisiereAdminRolle(mandant);
    }
  }

  public void aktualisiereAdminRolle(Mandant mandant) {
    LOG.info("Admin Rolle für Mandant: " + mandant.getNummer() + " aktualisieren");

    if (isAdminRolleMitAllenRechtenAnlegen(mandant) && konfigurationLoader.resolveBooleanValue(mandant, ADMIN_ROLLE_DARF_ALLES)) {
      LOG.info("Admin Rolle aktualisieren - Aktualisierung aktiviert");

      String adminRolleName = RolleTemplate.ADMIN.getRolleName();
      List<Rolle> adminRollen = rolleDao.findByName(mandant, adminRolleName);

      if (adminRollen.isEmpty()) {
        LOG.info("Admin Rolle aktualisieren - lege Admin Rolle an");
        persistRolle(mandant, RolleTemplate.ADMIN, u -> true);
      } else {
        LOG.info("Admin Rolle aktualisieren - aktualisiere Admin Rolle");

        for (Rolle rolle : adminRollen) {
          Set<UseCasePermission> rechteVorher = EnumSet.copyOf(rolle.getRecht());
          rolle.removeAllRechts();

          Set<UseCasePermission> neueAdminRechte = new HashSet<>();
          for (UseCasePermission useCasePermission : getAlleMoeglichenRechteDerAnwendungFuerAdmin(mandant)) {
            rolle.addRecht(useCasePermission);
            if (!rechteVorher.contains(useCasePermission)) {
              neueAdminRechte.add(useCasePermission);
            }
          }
          LOG.info("Admin Rolle aktualisieren - aktuelle Rechte: " + rolle.getRecht());
          if (isNeueRechteLoggen() && !neueAdminRechte.isEmpty()) {
            String neueRechteText = getRechteAlsText(neueAdminRechte);
            systemLogWriter.writeInfoLogMessageFuerMandant(mandant, messages.getMessageForMessageKey("admin.rechte.neu", adminRolleName, neueRechteText),
                SystemLogQuelle.SYSTEM);
          }
        }
      }
    }

    LOG.info("Admin Rolle aktualisieren - done");
  }

  protected boolean isNeueRechteLoggen() {
    return true;
  }

  public void cleanUpRollenAlleMandanten() {
    for (Mandant mandant : mandantDAO.findByAll()) {
      cleanUpRollen(mandant);
    }
  }

  // WASDE-1089
  public void cleanUpRollen(Mandant mandant) {
    LOG.info("Rollen cleanup für Mandant: " + mandant.getNummer());

    Set<UseCasePermission> moeglicheRechteDerAnwendung = new HashSet<>(getAlleMoeglichenRechteDerAnwendung(mandant));
    for (Rolle rolle : rolleDao.findByAll(mandant)) {

      Set<UseCasePermission> zuLoeschen = rolle.getRecht().stream().filter(useCasePermission -> !moeglicheRechteDerAnwendung.contains(useCasePermission))
          .collect(Collectors.toSet());

      if (!zuLoeschen.isEmpty()) {
        LOG.info("Rolle: '" + rolle.getName() + "' enthält ungültige Rechte: " + zuLoeschen);
        for (UseCasePermission useCasePermission : zuLoeschen) {
          rolle.removeRecht(useCasePermission);
        }
        LOG.info("Rolle: '" + rolle.getName() + "' bereinigt");
      }
    }

    LOG.info("Rollen cleanup - done");

  }

  private Collection<UseCasePermission> getAlleMoeglichenRechteDerAnwendungFuerAdmin(Mandant mandant) {
    return permissionService.getAvailablePermissionsForCurrentCustomer(mandant.getArt());
  }

  private Collection<UseCasePermission> getAlleMoeglichenRechteDerAnwendung(Mandant mandant) {
    return permissionService.getAvailablePermissionsForCurrentCustomer(mandant.getArt());
  }

  private Collection<UseCasePermission> getRechteNichtInAnwendung(Mandant mandant) {
    Collection<UseCasePermission> rechteNichtInAnwendung = new HashSet<>(asList(UseCasePermission.values()));
    rechteNichtInAnwendung.removeAll(getAlleMoeglichenRechteDerAnwendung(mandant));

    return rechteNichtInAnwendung;
  }

  public void initProduktivTestRechteAlleMandanten() {
    for (Mandant mandant : mandantDAO.findByAll()) {
      initProduktivTestRechte(mandant);
    }
  }

  // ELECT-5919
  public void initProduktivTestRechte(Mandant mandant) {
    LOG.info("Initialisiere Produktiv/Testrechte für Mandant: " + mandant.getNummer());
    Map<UseCasePermission, List<RechteKonfiguration>> modusPersistent = rechteKonfigurationDAO.findByAll(mandant).stream()
        .collect(Collectors.groupingBy(RechteKonfiguration::getRecht));

    for (UseCasePermission useCasePermission : getAlleMoeglichenRechteDerAnwendung(mandant)) {
      if (!modusPersistent.containsKey(useCasePermission)) {
        RechteKonfiguration rechteKonfiguration = new RechteKonfiguration();
        rechteKonfiguration.setMandant(mandant);
        rechteKonfiguration.setRecht(useCasePermission);
        rechteKonfiguration.setNurImTestmodusAktiv(useCasePermission.getInitialerModus().isNurImTestModusAktiv());
        rechteKonfigurationCreate.save(rechteKonfiguration);
        LOG.info("Rechtekonfiguration angelegt: " + rechteKonfiguration.getRecht() + ", " + rechteKonfiguration.getNurImTestmodusAktiv());
      }
    }
  }

  public void cleanUpProduktivTestRechteAlleMandanten() {

  }

  // WASDE-1089
  public void cleanUpProduktivTestRechte(Mandant mandant) {
    LOG.info("Cleanup ProduktivTestRechte für Mandant");
    HashSet<UseCasePermission> moeglicheRechte = new HashSet<>(getAlleMoeglichenRechteDerAnwendung(mandant));
    for (RechteKonfiguration rechteKonfiguration : rechteKonfigurationDAO.findByAll(mandant)) {
      if (!moeglicheRechte.contains(rechteKonfiguration.getRecht())) {
        LOG.info("Ungueltiges Produktiv/Testrecht wird geloscht: " + rechteKonfiguration.getRecht());
        rechteKonfigurationDAO.remove(rechteKonfiguration);
      }

    }
    LOG.info("Cleanup ProduktivTestRechte - done");
  }
}
