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

import de.ivu.elect.business.benutzerverwaltung.control.BenutzerCreate;
import de.ivu.elect.business.benutzerverwaltung.control.BenutzerExchangeContext;
import de.ivu.elect.business.benutzerverwaltung.control.BenutzerImportExchange;
import de.ivu.elect.business.benutzerverwaltung.control.RolleCreate;
import de.ivu.elect.business.benutzerverwaltung.entity.*;
import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.business.exchange.entity.ImportUpdateReport;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;
import org.apache.commons.lang3.StringUtils;

import javax.ejb.Stateless;
import javax.inject.Inject;
import java.util.*;
import java.util.function.Function;

import static de.ivu.elect.business.security.boundary.BenutzerCreator.ADMIN_KENNUNG;

@Stateless
public class BenutzerImporter extends AbstractEntityImporter<Benutzer, BenutzerTK, BenutzerExchangeContext, DateiImportBerichtCustomData> {

  @Inject
  protected transient RolleDAO rolleDao;

  @Inject
  protected transient RolleCreate rolleCreate;

  @Inject
  protected transient BenutzerDAO benutzerDao;

  @Inject
  protected transient BenutzerMeta benutzerMeta;

  @Inject
  protected transient BenutzerCreate benutzerCreate;

  @Inject
  protected transient BenutzerImportExchange benutzerImportExchange;

  public BenutzerImporter() {
    super();
  }

  @Override
  protected ImportExchange<List<Benutzer>, BenutzerExchangeContext> getExchange() {
    return benutzerImportExchange;
  }

  @Override
  protected ImportUpdateReport createImportUpdateReport(List<Benutzer> inDatei, Violations violations, BenutzerExchangeContext context, ImportOptions importOptions, StringBuilder bericht) {
    return super.createImportUpdateReport(inDatei, violations, context, importOptions, bericht);
  }

  @Override
  protected void validiere(Violations violations, List<Benutzer> benutzerList, BenutzerExchangeContext context, ImportOptions importOptions) {
    checkUniqueBenutzerkennungInFile(benutzerList, violations);

    for (Benutzer benutzer : benutzerList) {
      validateEntity(violations, benutzer, benutzer.getBenutzerTK());
    }

    if (importOptions.isLoescheBestehendenDatenWennNichtInImportDatenVorhanden()) {
      checkNutzernameNichtVorhanden(violations, benutzerList, ADMIN_KENNUNG, "benutzer.import.validation.admin.benutzer.wird.nicht.geloescht");
      if (StringUtils.isNotBlank(context.getEingeloggterBenutzerKennung())) {
        checkNutzernameNichtVorhanden(violations, benutzerList, context.getEingeloggterBenutzerKennung(), "benutzer.import.validation.eingeloggter.benutzer.wird.nicht.geloescht");
      }
    }
  }

  private void checkNutzernameNichtVorhanden(Violations violations, List<Benutzer> benutzerList, String kennung, String msgkey) {
    if (benutzerList.stream().noneMatch(benutzer -> kennung.equals(benutzer.getBenutzerkennung()))) {
      violations.addWarning(msgkey);
    }
  }

  private void checkUniqueBenutzerkennungInFile(Collection<Benutzer> benutzers, Violations violations) {
    Map<String, Benutzer> benutzerkennungen = new HashMap<>();
    for (Benutzer benutzer : benutzers) {
      if (benutzerkennungen.containsKey(benutzer.getBenutzerkennung())) {
        violations.addError("benutzer.import.validation.datei.enthaelt.benutzer.mehrfach", benutzer.getBenutzerkennung());
      } else {
        benutzerkennungen.put(benutzer.getBenutzerkennung(), benutzer);
      }
    }
  }

  @Override
  protected void importiereImportObject(List<Benutzer> benutzers, BenutzerExchangeContext BenutzerImportContext, ImportOptions importOptions, DateiImportBerichtCustomData dateiImportBerichtCustomData,
      StringBuilder bericht) {
    super.importiereImportObject(benutzers, BenutzerImportContext, importOptions, dateiImportBerichtCustomData, bericht);
  }

  private List<Rolle> extractRollen(Collection<Benutzer> benutzers, Mandant mandant) {
    Collection<Rolle> rollen = new ArrayList<>();
    for (Benutzer benutzer : benutzers) {
      rollen.addAll(benutzer.getRolles());
    }

    Map<RolleUK, Rolle> rollenMap = mappeRolleNachUK(rollen, mandant);
    return new ArrayList<>(rollenMap.values());
  }

  private void speichereRolle(Mandant mandant, Rolle rolleAusDatei, Collection<Benutzer> benutzerNichtInDatenbank) {

    rolleAusDatei.setMandant(mandant);
    RolleUK rolleUK = new RolleUK(rolleAusDatei.getRolleUK().getName(), mandant.getMandantUK());

    if (rolleDao.isEntityWithUKPresent(rolleUK)) {
      removeRolleAusDateiAndAddRolleAusDb(rolleDao.findByUK(rolleUK), benutzerNichtInDatenbank, mandant);
    } else {

      // WASDE-1037
      Rolle rolleInDb = new Rolle();
      rolleAusDatei.copyEntityAttributesUsingSetter(rolleInDb);
      rolleInDb.setMandant(mandant);
      rolleCreate.save(rolleInDb);
      rolleDao.flush();

      removeRolleAusDateiAndAddRolleAusDb(rolleInDb, benutzerNichtInDatenbank, mandant);
    }
  }

  protected void removeRolleAusDateiAndAddRolleAusDb(Rolle rolleInDb, Collection<Benutzer> benutzerNichtInDatenbank, Mandant mandant) {
    RolleUK rolleUK = rolleInDb.getRolleUK();

    // Rolle von Benutzern entfernen, da CASCADE.PERSIST aktiv ist
    for (Benutzer benutzer : benutzerNichtInDatenbank) {
      Rolle rolleAusDatei = mappeRolleNachUK(benutzer.getRolles(), mandant).get(rolleUK);

      if (rolleAusDatei != null) { // d.h. der Nutzer hat eine Rolle mit diesem UK
        benutzer.removeRolle(rolleAusDatei);
        benutzer.addRolle(rolleInDb);
      }
    }
  }

  private  Map<RolleUK, Rolle> mappeRolleNachUK(Collection<Rolle> rolles, Mandant mandant) {
    Map<RolleUK, Rolle> result = new HashMap<>();

    for (Rolle rolle : rolles) {
      result.put(new RolleUK(rolle.getName(), mandant.getMandantUK()), rolle);
    }

    return result;
  }

  @Override
  public Meta getMetaForEntityInternal() {
    return benutzerMeta;
  }

  private void processRollen(Mandant mandant, Collection<Benutzer> benutzerNichtInDatenbank) {
    List<Rolle> rollen = extractRollen(benutzerNichtInDatenbank, mandant);
    for (Rolle rolle : rollen) {
      speichereRolle(mandant, rolle, benutzerNichtInDatenbank);
    }
  }

  private void aktualisiereBenutzerDaten(Mandant mandant, Benutzer benutzer, Benutzer benutzerDB) {
    // inaktiv gemäß BenutzerImportBestaetigenView#createDefaultImportOptions()

    benutzerDB.removeAllRolles();

    processRollen(mandant, Collections.singletonList(benutzer));
    for (Rolle rolle : benutzer.getRolles()) {
      benutzerDB.addRolle(rolle);
    }

    benutzerDB.setVorname(benutzer.getVorname());
    benutzerDB.setGeschlecht(benutzer.getGeschlecht());
    benutzerDB.setFamilienname(benutzer.getFamilienname());
    benutzerDB.setPasswort(benutzer.getPasswort());
    benutzerDB.setSalt(benutzer.getSalt());
    benutzerDB.setGesperrt(benutzer.getGesperrt());
    benutzerDB.setBemerkung(benutzer.getBemerkung());
    if (StringUtils.isNotBlank(benutzer.getHotpSecret())){
      benutzerDB.setHotpSecret(benutzer.getHotpSecret());
    }
    if (StringUtils.isNotBlank(benutzer.getTanliste())){
      benutzerDB.setTanliste(benutzer.getTanliste());
    }
  }

  @Override
  protected void persistiere(Collection<Benutzer> benutzerNurInDateiNichtInDatenbank, BenutzerExchangeContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    processRollen(context.getMandant(), benutzerNurInDateiNichtInDatenbank);
    for (Benutzer benutzer : benutzerNurInDateiNichtInDatenbank) {
      benutzer.setMandant(context.getMandant());
      benutzerCreate.saveMitEinstellungen(benutzer);
    }
  }

  @Override
  protected void aktualisiere(Benutzer benutzerInDatei, Benutzer benutzerInDB, BenutzerExchangeContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    aktualisiereBenutzerDaten(context.getMandant(), benutzerInDatei, benutzerInDB);
  }

  @Override
  protected void loescheAusDatenbank(Collection<Benutzer> entitiesInDb, BenutzerExchangeContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    for (Benutzer benutzer : entitiesInDb) {
      if (!ADMIN_KENNUNG.equals(benutzer.getBenutzerkennung()) && !benutzer.getBenutzerkennung().equals(context.getEingeloggterBenutzerKennung())) {
        benutzerDao.remove(benutzer);
      }
    }
  }

  @Override
  protected boolean isUnterstuetztAktualisiereBestehendenDaten() {
    return true;
  }

  @Override
  protected boolean isUnterstuetztLoescheBestehendenDatenWennNichtInImportDatenVorhanden() {
    return true;
  }

  @Override
  protected boolean isUnterstuetztAnlegenWennNichtVorhanden() {
    return true;
  }

  @Override
  protected List<Benutzer> getAllEntitiesInDB(BenutzerExchangeContext context) {
    return benutzerDao.findByAllOrderByBenutzerkennung(context.getMandant());
  }

  @Override
  protected Function<Benutzer, BenutzerTK> getEntityToEntityTKFunction() {
    return BenutzerTK.getBenutzerToTKFunction();
  }

  @Override
  protected void erstelleBericht(List<Benutzer> importObject, BenutzerExchangeContext context, ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    // nichts zu tun
  }
}
