
package de.ivu.elect.presentation.gebietsbaum.form;

import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.upperCase;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;

import de.ivu.elect.business.benutzerverwaltung.boundary.RolleExtendedDAO;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.benutzerverwaltung.entity.RolleByNameComparator;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsartExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietConstants;
import de.ivu.elect.business.gebietsbaum.entity.GebietMeta;
import de.ivu.elect.business.gebietsbaum.entity.GebietProperties;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.mandant.boundary.MandantExtendedDAO;
import de.ivu.elect.business.mandant.control.MandantSupport;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenMeta;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.wahl.boundary.WahlExtendedDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.Property;
import de.ivu.elect.jee.presentation.dynamicform.PropertyType;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.presentation.LambdaEntitySelectOneMenuProperty;
import de.ivu.elect.presentation.ViewFormBuilder;
import de.ivu.elect.presentation.anschrift.form.AnschriftViewFormBuilder;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.mandant.MandantNameFormatter;

public class GebietViewFormBuilder extends ViewFormBuilder {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient GebietsartExtendedDAO gebietsartDao;

  @Inject
  private transient RolleExtendedDAO rolleDao;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient PersonendatenMeta personendatenMeta;

  @Inject
  protected transient GebietMeta gebietMeta;

  @Inject
  private transient AnschriftViewFormBuilder anschriftViewFormBuilder;

  @Inject
  private transient GebietAnsprechpartnerViewFormBuilder ansprechpartnerViewFormBuilder;

  @Inject
  protected transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient MandantExtendedDAO mandantExtendedDAO;

  @Inject
  private transient MandantSupport mandantSupport;

  @Inject
  private transient MandantNameFormatter mandantNameFormatter;

  @Inject
  private transient WahlExtendedDAO wahlExtendedDAO;

  public GebietViewFormBuilder() {
    super();
  }

  public void addGebietProperties(FormBuilder formBuilder, Gebiet gebiet) {
    addGebietProperties(formBuilder, gebiet, false);
    addAnschriftProperties(formBuilder, gebiet, false);
    addAnsprechpartnerProperties(formBuilder, gebiet, false);
    addBemerkungProperties(formBuilder, gebiet, false);
    addSecurityProperties(formBuilder, gebiet, false);
  }

  public void addGebietProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    addBasicGebietProperties(formBuilder, gebiet, readonly);

    addSitzeStimmenProperties(formBuilder, gebiet, readonly);

    // ELECT-3000
    // TODO: ELECTMUC-684 -- die Anzahl der Unterstützungsunterschriften bezieht sich auf das gesamte Wahlgebiet.

    GebietProperties gebietProperties = gebiet.getProperties();

    if (gebiet.isWahlvorschlaggebiet()) {
      formBuilder.addProperty(gebietProperties.getAnzahlErforderlicherUnterstuetzerProperty());
    }

    if (isErfassungsgebietBezirkFlagsPropertiesRendered(gebiet)) { // ELECT-3099
      formBuilder.addProperty(gebietProperties.getAuswahlbezirkProperty()).withReadonly(readonly).visibleOnPageLoad(); // ELECTKOM-49
      formBuilder.addProperty(gebietProperties.getSonderstimmbezirkProperty()).withReadonly(readonly);
      formBuilder.addProperty(gebietProperties.getBeweglicherWahlvorstandProperty()).withReadonly(readonly);
      formBuilder.addProperty(gebietProperties.getUmfragebezirkProperty()).withReadonly(readonly);
      formBuilder.addProperty(gebietProperties.getWahlbeteiligungsbezirkProperty()).withReadonly(readonly);
    }
  }

  protected void addSitzeStimmenProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    // Cf. de.ivu.elect.presentation.dashboard.entries.WasWahlDashboardEntry.getSeverity!!!
    boolean anzahlSitzeRequired = gebiet.isWahlgebiet() && !gebiet.isDirektwahlgebiet(); // hat sich ugo gewünscht

    addSitzeStimmenProperties(formBuilder, gebiet, anzahlSitzeRequired, readonly);
  }

  public void addSitzeStimmenProperties(FormBuilder formBuilder, Gebiet gebiet, boolean anzahlSitzeRequired, boolean readonly) {
    Wahl wahl = gebiet.getWahl();

    if (wahl != null && gebiet.isWahlgebietOderWurzelgebiet()) { // ELECT-9077
      GebietProperties gebietProperties = gebiet.getProperties();

      Property anzahlSitzeProperty = gebietProperties.getAnzahlSitzeProperty();

      if (wahl.isWahlMitAnzahlSitze() || anzahlSitzeProperty.hasValue()) { // ELECT-9377
        formBuilder.addProperty(anzahlSitzeProperty) //
            .withRequired(anzahlSitzeRequired) //
            .withReadonly(readonly);
      }

      Property anzahlStimmenProWaehlerProperty = gebietProperties.getAnzahlStimmenProWaehlerProperty();

      if (wahl.isWahlMitAnzahlStimmenProWaehlerPerWahlgebiet() || anzahlStimmenProWaehlerProperty.hasValue()) {
        formBuilder.addProperty(anzahlStimmenProWaehlerProperty) //
            .withVisibleOnPageLoad(anzahlSitzeRequired) //
            .withTooltip(getMessage("gebiet.edit.anzahl.stimmen.pro.waehler.tooltip")) // ELECTKOM-725
            .withReadonly(readonly);
      }
    }
  }

  protected boolean isErfassungsgebietBezirkFlagsPropertiesRendered(Gebiet gebiet) {
    return gebiet.isErfassungsgebiet(); // ELECT-3799
  }

  protected void addBasicGebietProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    addGebietsinformationProperties(formBuilder, gebiet, readonly);

    if (!hasGebietsartErfassungsgebiet(gebiet) && hasUseCasePermission(UseCasePermission.WAHL_FUER_SUBSYSTEME_KONFIGURIEREN) && mandantSupport.isMandantenUnterstuetzt()) {
      addVerantwortlicherMandant(formBuilder, gebiet, readonly);
    }
  }

  protected void addVerantwortlicherMandant(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    List<Mandant> mandanten = mandantExtendedDAO.findByAll().stream().filter(m -> m.getArt().isCustomerMandant()).filter(m -> !m.equals(gebiet.getMandant())) // nicht der eigene Mandant
        .filter(m -> m.equals(gebiet.getVerantwortlicherMandant()) || !isBereitsInGebietenAusgewaehlt(gebiet, m))
        .filter(m -> m.equals(gebiet.getVerantwortlicherMandant()) || !isBereitsAufWahlAufgeteilt(gebiet, m)).sorted(Comparator.comparing(Mandant::getNummer)).collect(Collectors.toList());

    LambdaEntitySelectOneMenuProperty<Mandant> mandantProperty = new LambdaEntitySelectOneMenuProperty<>(mandanten, gebiet::setVerantwortlicherMandant, gebiet::getVerantwortlicherMandant,
        mandantNameFormatter::formatiereName, GebietConstants.MESSAGE_KEY_VERANTWORTLICHER_MANDANT_ID_EINZAHL, false, true);
    formBuilder.addProperty(mandantProperty).withReadonly(readonly || isBereitsAufWahlAufgeteilt(gebiet, gebiet.getVerantwortlicherMandant()));
  }

  private boolean isBereitsAufWahlAufgeteilt(Gebiet gebiet, Mandant mandant) {
    if (gebiet == null || mandant == null || gebiet.getWahl() == null) {
      return false;
    }

    return !wahlExtendedDAO.findByBerichtetAnAndMandant(gebiet.getWahl(), mandant).isEmpty();

  }

  private boolean isBereitsInGebietenAusgewaehlt(Gebiet gebiet, Mandant mandant) {
    if (gebiet.getWahl() == null) {
      return !gebietExtendedDAO.findByWahlterminVerantwortlicherMandant(gebiet.getWahltermin(), mandant).isEmpty();
    } else {
      return !gebietExtendedDAO.findByWahlVerantwortlicherMandant(gebiet.getWahl(), mandant).isEmpty();
    }
  }

  protected void addGebietsinformationProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    GebietProperties gebietProperties = gebiet.getProperties();

    addGebietNameSeparator(formBuilder, gebiet);

    addGebietsartProperty(formBuilder, gebiet, readonly);

    addBasicGebietsinformationProperties(formBuilder, gebiet, readonly, gebietProperties);
  }

  // Cf. BezirkViewFormBuilder#addBasicBezirkProperties!
  private void addBasicGebietsinformationProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly, GebietProperties gebietProperties) {
    addGebietNummerProperty(gebiet, formBuilder, readonly, gebietProperties);

    addBezirkNummerProperty(formBuilder, gebiet, readonly, gebietProperties);

    if (gebiet.hasUebergeordnetesGebiet()) {
      formBuilder.addProperty(new ReadOnlyStringProperty(gebietMeta.getUebergeordnetesGebietEinzahlMessage(), gebietNummerFormatter.getNameMitNummer(gebiet.getUebergeordnetesGebiet())));
    }

    formBuilder.addProperty(gebietProperties.getNameProperty()).withReadonly(readonly).visibleOnPageLoad();
    formBuilder.addProperty(gebietProperties.getKuerzelProperty()).withReadonly(readonly);
    formBuilder.addProperty(gebietProperties.getPositionProperty()).withReadonly(readonly).visibleOnPageLoad();
  }

  protected void addGebietNummerProperty(Gebiet gebiet, FormBuilder formBuilder, boolean readonly, GebietProperties gebietsinformationProperties) {
    formBuilder.addProperty(gebietsinformationProperties.getNummerProperty()) //
        .withTooltip(getMessageByGebietsart(gebiet, "gebiet.edit.nummer.tooltip")) //
        .withReadonly(readonly);
  }

  protected void addBezirkNummerProperty(FormBuilder formBuilder, Gebiet gebiet, boolean readonly, GebietProperties gebietProperties) {
    if (hasGebietsartErfassungsgebiet(gebiet)) {
      formBuilder.addProperty(gebietProperties.getNummerBezirkProperty()) //
          .withTooltip(getMessage("gebiet.edit.bezirk.nummer.tooltip")) //
          .withReadonly(readonly) //
          .visibleOnPageLoad();
    }
  }

  protected void addGebietNameSeparator(FormBuilder formBuilder, Gebiet gebiet) {
    formBuilder.addSeparator(GebietConstants.MESSAGE_KEY_ENTITY_EINZAHL);
  }

  protected void addGebietsartProperty(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    GebietsartSelectOneMenuProperty property = new GebietsartSelectOneMenuProperty(getGebietsarten(gebiet), gebiet);
    property.setItemIconMapper((icons, gebietsart) -> gebietsart.getIcon().getIcon());

    formBuilder.addProperty(property).withReadonly(readonly || hasGebietsartErfassungsgebiet(gebiet));
  }

  private List<Gebietsart> getGebietsarten(Gebiet gebiet) {
    return gebietsartDao.findByAllOrderByPosition(gebiet.getMandant()).stream().filter(gebietsart -> isGebietsartZurAuswahl(gebiet, gebietsart)).collect(Collectors.toList());
  }

  protected boolean isGebietsartZurAuswahl(Gebiet gebiet, Gebietsart gebietsart) {
    return true;
  }

  private void addSecurityProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    // ELECT-3505 - Wahl(art)- und gebietsabhängiges Rechtekonzept umsetzen
    List<Rolle> alleRollenInDb = rolleDao.findByAll(gebiet.getMandant());

    // Filter Rollen, die bereits Gebieten zugeordnet sind, oder keine Rechte haben
    List<Rolle> alleRollen = alleRollenInDb.stream().filter(r -> r.isGebietRolle(rolleDao) || r.isFachlicheRolle(rolleDao) && r.getRecht().isEmpty()).collect(Collectors.toList());

    Collections.sort(alleRollen, new RolleByNameComparator());
    String noSelectionLabelKey;

    if (gebiet.hasUebergeordnetesGebiet()) {
      noSelectionLabelKey = "gebiet.edit.rolle.keine.untergeordnetes.gebiet";
    } else {
      noSelectionLabelKey = "gebiet.edit.rolle.keine.oberstes.gebiet";
    }

    List<Rolle> einsehenRollen = new ArrayList<>(alleRollen);
    List<Rolle> bearbeitenRollen = new ArrayList<>(alleRollen);

    if (gebiet.isPersistent()) {
      String rollenNameEinsehen = "";

      if (isNotBlank(gebiet.getName())) {
        rollenNameEinsehen = gebiet.getName() + " " + getMessage("gebiet.einsehen.recht.suffix");
      }

      String rollenNameBearbeiten = "";

      if (isNotBlank(gebiet.getName())) {
        rollenNameBearbeiten = gebiet.getName() + " " + getMessage("gebiet.bearbeiten.recht.suffix");
      }

      if (gebiet.getEinsehenRolle() == null) {
        addRolleWennNichtVorhanden(einsehenRollen, rollenNameEinsehen);
      }

      if (gebiet.getBearbeitenRolle() == null) {
        addRolleWennNichtVorhanden(bearbeitenRollen, rollenNameBearbeiten);
      }

      if (isNotBlank(rollenNameEinsehen)) {
        final String rollenName = rollenNameEinsehen;
        Collections.sort(einsehenRollen, (u, v) -> {
          if (rollenName.equalsIgnoreCase(u.getName())) {
            return -1;
          }
          if (rollenName.equalsIgnoreCase(v.getName())) {
            return 1;
          }
          return 0;
        });
      }
      if (isNotBlank(rollenNameBearbeiten)) {
        final String rollenName = rollenNameBearbeiten;
        Collections.sort(bearbeitenRollen, (u, v) -> {
          if (rollenName.equalsIgnoreCase(u.getName())) {
            return -1;
          }
          if (rollenName.equalsIgnoreCase(v.getName())) {
            return 1;
          }
          return 0;
        });
      }

    }

    formBuilder.addSeparator("gebiet.security.separator");
    formBuilder.addProperty(new GebietRolleEinsehenProperty(gebiet, einsehenRollen, getMessage(noSelectionLabelKey))).withReadonly(readonly);
    formBuilder.addProperty(new GebietRolleBearbeitenProperty(gebiet, bearbeitenRollen, getMessage(noSelectionLabelKey))).withReadonly(readonly);
  }

  private void addRolleWennNichtVorhanden(List<Rolle> rollen, String rollenName) {
    if (isBlank(rollenName)) {
      return;
    }

    for (Rolle rolle : rollen) {
      if (rolle.getName().equalsIgnoreCase(rollenName)) {
        return; // Rolle vorhanden
      }
    }

    Rolle rolle = new Rolle();
    rolle.setName(rollenName);
    rolle.setBeschreibung(getMessage("gebiet.recht.automatisch.erstellt"));
    rollen.add(0, rolle);
  }

  private void addAnsprechpartnerProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    formBuilder.addSeparator(gebietMeta.getAnsprechpartnerEinzahlMessageKey());

    Personendaten ansprechpartner = gebiet.getAnsprechpartner();
    ansprechpartnerViewFormBuilder.addProperties(formBuilder, ansprechpartner, readonly);

  }

  protected void addBemerkungProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    Personendaten ansprechpartner = gebiet.getAnsprechpartner();
    // ELECTDD-239 - Bemerkungsfeld an Gebiet anfügen - war: Es wird analog Ansprechpartner ein 2. Ansprechpartner benötigt
    // Auch für Destatis lassen, hier wird die Bemerkung für ausbleibende Gebiete erfasst
    formBuilder.addSeparator(personendatenMeta.getBemerkungEinzahlMessageKey());
    formBuilder.addProperty(ansprechpartner.getProperties().getBemerkungProperty()).withReadonly(readonly).withType(PropertyType.INPUT_TEXTAREA);
  }

  private void addAnschriftProperties(FormBuilder formBuilder, Gebiet gebiet, boolean readonly) {
    formBuilder.addSeparator(gebietMeta.getAnschriftEinzahlMessageKey());

    anschriftViewFormBuilder.addLokalitaetAnschriftProperties(formBuilder, gebiet.getAnschrift(), readonly);
  }

  protected String getMessageByGebietsart(Gebiet gebiet, String msgKey, Object... params) {
    String result = "";

    String msgKeyGebietsart = msgKey + "." + upperCase(gebiet.getGebietsart().getName());

    if (hasGebietsartErfassungsgebiet(gebiet)) {
      msgKeyGebietsart = msgKey + ".ERFASSUNGSGEBIET";
    } else if (hasGebietsartGemeinde(gebiet)) {
      msgKeyGebietsart = msgKey + ".GEMEINDE";
    }

    if (existsMessage(msgKey)) {
      result = getMessage(msgKey, params);
    }

    if (existsMessage(msgKeyGebietsart)) {
      result = getMessage(msgKeyGebietsart, params);
    }

    return result;
  }

  private boolean hasGebietsartGemeinde(Gebiet gebiet) {
    return nonNull(gebiet.getGebietsart()) && gebiet.getGebietsart().getSystemGebietsebene().isGemeinde();
  }

  public static boolean hasGebietsartErfassungsgebiet(Gebiet gebiet) {
    return nonNull(gebiet.getGebietsart()) && !gebiet.getGebietsart().getBezirksart().isKeine();
  }
}
