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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;

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

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.benutzerverwaltung.control.BenutzerEinsatzortCreate;
import de.ivu.elect.business.benutzerverwaltung.control.BenutzerEinsatzortImportExchange;
import de.ivu.elect.business.benutzerverwaltung.control.RolleCreate;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzort;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzortExchangeContext;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzortMeta;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzortTK;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.benutzerverwaltung.entity.RolleUK;
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.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;


import static java.util.Arrays.asList;

@Stateless
public class BenutzerEinsatzortImporter
    extends
      AbstractEntityImporter<BenutzerEinsatzort, BenutzerEinsatzortTK, BenutzerEinsatzortExchangeContext, DateiImportBerichtCustomData> {

  @Inject
  protected transient RolleDAO rolleDao;

  @Inject
  protected transient RolleCreate rolleCreate;

  @Inject
  protected transient BenutzerEinsatzortDAO benutzerEinsatzortDao;

  @Inject
  protected transient BenutzerEinsatzortMeta benutzerEinsatzortMeta;

  @Inject
  protected transient BenutzerEinsatzortCreate benutzerEinsatzortCreate;

  @Inject
  protected transient BenutzerEinsatzortImportExchange importExchange;

  public BenutzerEinsatzortImporter() {
    super();
  }

  @Override
  public Collection<ImportOptions> getSupportedImportOptions(BenutzerEinsatzortExchangeContext context) {
    return asList(ImportOptions.IMPORT_VOLLSTAENDIG_UEBERNEHMEN, ImportOptions.NEUE_DATEN_ANLEGEN_BESTEHENDE_DATEN_UNVERAENDERT_LASSEN);
  }

  @Override
  protected ImportExchange<List<BenutzerEinsatzort>, BenutzerEinsatzortExchangeContext> getExchange() {
    return importExchange;
  }

  @Override
  protected void validiere(Violations violations, List<BenutzerEinsatzort> benutzerEinsatzortList, BenutzerEinsatzortExchangeContext context, ImportOptions importOptions) {
    checkUniqueBenutzerEinsatzortNameInFile(benutzerEinsatzortList, violations);

    for (BenutzerEinsatzort benutzerEinsatzort : benutzerEinsatzortList) {
      validateEntity(violations, benutzerEinsatzort, benutzerEinsatzort.getBenutzerEinsatzortTK());
      validiere(violations, benutzerEinsatzort);
    }
  }

  protected void validiere(Violations violations, BenutzerEinsatzort benutzerEinsatzort) {
    // ELECT-5633
    if (StringUtils.isNotBlank(benutzerEinsatzort.getClientIpMuster())) {
      try {
        Pattern.compile(benutzerEinsatzort.getClientIpMuster());
      } catch (Exception e) {
        violations.addError("regexp.validation.failed", benutzerEinsatzort.getClientIpMuster(), e.getLocalizedMessage());
      }
    }
  }

  private void checkUniqueBenutzerEinsatzortNameInFile(Collection<BenutzerEinsatzort> benutzerEinsatzorts, Violations violations) {
    Map<String, BenutzerEinsatzort> benutzerEinsatzortNamen = new HashMap<>();
    for (BenutzerEinsatzort benutzerEinsatzort : benutzerEinsatzorts) {
      if (benutzerEinsatzortNamen.containsKey(benutzerEinsatzort.getName())) {
        violations.addError("benutzer.einsatzort.import.validation.datei.enthaelt.benutzer.mehrfach", benutzerEinsatzort.getName());
      } else {
        benutzerEinsatzortNamen.put(benutzerEinsatzort.getName(), benutzerEinsatzort);
      }
    }
  }

  private List<Rolle> extractRollen(Collection<BenutzerEinsatzort> benutzerEinsatzorts) {
    Collection<Rolle> rollen = new ArrayList<>();
    for (BenutzerEinsatzort benutzerEinsatzort : benutzerEinsatzorts) {
      rollen.addAll(benutzerEinsatzort.getRolles());
    }

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

  private void speichereRolle(Rolle rolleAusDatei, Collection<BenutzerEinsatzort> benutzerEinsatzortNichtInDatenbank) {

    RolleUK rolleUK = rolleAusDatei.getRolleUK();

    if (rolleDao.isEntityWithUKPresent(rolleUK)) {
      removeRolleAusDateiAndAddRolleAusDb(rolleDao.findByUK(rolleUK), benutzerEinsatzortNichtInDatenbank);
    } else {
      rolleCreate.save(rolleAusDatei);
      // NOTE, dass rolleAusDatei nun eine rolleAusDb ist...
      removeRolleAusDateiAndAddRolleAusDb(rolleAusDatei, benutzerEinsatzortNichtInDatenbank);
    }
  }

  protected void removeRolleAusDateiAndAddRolleAusDb(Rolle rolleInDb, Collection<BenutzerEinsatzort> benutzerEinsatzortNichtInDatenbank) {
    RolleUK rolleUK = rolleInDb.getRolleUK();

    // Rolle von Benutzern entfernen, da CASCADE.PERSIST aktiv ist
    for (BenutzerEinsatzort benutzerEinsatzort : benutzerEinsatzortNichtInDatenbank) {
      Rolle rolleAusDatei = RolleUK.mappeRolleNachUK(benutzerEinsatzort.getRolles()).get(rolleUK);

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

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

  private void processRollen(final Collection<BenutzerEinsatzort> benutzerEinsatzortNichtInDatenbank, BenutzerEinsatzortExchangeContext context) {
    List<Rolle> rollen = extractRollen(benutzerEinsatzortNichtInDatenbank);
    for (Rolle rolle : rollen) {
      rolle.setMandant(context.getMandant());
      speichereRolle(rolle, benutzerEinsatzortNichtInDatenbank);
    }
  }

  private void aktualisiereBenutzerDaten(final BenutzerEinsatzort benutzerEinsatzort, final BenutzerEinsatzort benutzerEinsatzortDB, BenutzerEinsatzortExchangeContext context) {

    benutzerEinsatzortDB.removeAllRolles();

    processRollen(Collections.singletonList(benutzerEinsatzort), context);
    for (Rolle rolle : benutzerEinsatzort.getRolles()) {
      benutzerEinsatzortDB.addRolle(rolle);
    }

    benutzerEinsatzortDB.setName(benutzerEinsatzort.getName());
    benutzerEinsatzortDB.setBemerkung(benutzerEinsatzort.getBemerkung());
    benutzerEinsatzortDB.setClientIpMuster(benutzerEinsatzort.getClientIpMuster());
  }

  @Override
  protected void persistiere(Collection<BenutzerEinsatzort> benutzerEinsatzortNurInDateiNichtInDatenbank, BenutzerEinsatzortExchangeContext context,
      DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    processRollen(benutzerEinsatzortNurInDateiNichtInDatenbank, context);
    for (BenutzerEinsatzort benutzerEinsatzort : benutzerEinsatzortNurInDateiNichtInDatenbank) {
      benutzerEinsatzort.setMandant(context.getMandant());
      benutzerEinsatzortCreate.save(benutzerEinsatzort);
    }
  }

  @Override
  protected void aktualisiere(BenutzerEinsatzort benutzerEinsatzortInDatei, BenutzerEinsatzort benutzerEinsatzortInDB, BenutzerEinsatzortExchangeContext context,
      DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    aktualisiereBenutzerDaten(benutzerEinsatzortInDatei, benutzerEinsatzortInDB, context);
  }

  @Override
  protected void loescheAusDatenbank(Collection<BenutzerEinsatzort> entitiesInDb, BenutzerEinsatzortExchangeContext context,
      DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    for (BenutzerEinsatzort benutzerEinsatzort : entitiesInDb) {
      benutzerEinsatzortDao.remove(benutzerEinsatzort);
    }
  }

  @Override
  protected List<BenutzerEinsatzort> getAllEntitiesInDB(BenutzerEinsatzortExchangeContext context) {
    return benutzerEinsatzortDao.findByAll(context.getMandant());
  }

  @Override
  protected Function<BenutzerEinsatzort, BenutzerEinsatzortTK> getEntityToEntityTKFunction() {
    return BenutzerEinsatzortTK.getBenutzerEinsatzortToTKFunction();
  }

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