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

import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzort;
import de.ivu.elect.business.benutzerverwaltung.entity.Nachricht;
import de.ivu.elect.business.benutzerverwaltung.entity.NachrichtEmpfaengerGruppeArt;
import de.ivu.elect.business.benutzerverwaltung.entity.QNachricht;
import de.ivu.elect.business.benutzerverwaltung.entity.QRolle;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.jee.entity.QueryDsl;


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

@UseCaseSecure(UseCasePermission.NACHRICHT_SEARCH)
public class NachrichtSearch extends AbstractNachrichtSearch {

  @Inject
  private transient BenutzerEinsatzortExtendedDAO benutzerEinsatzortExtendedDAO;

  @Inject
  private transient RolleExtendedDAO rolleExtendedDAO;

  public NachrichtSearch() {
    super();
  }

  @Override
  protected List<Nachricht> querySearchResult(NachrichtSearchParameters searchParameters) {
    QNachricht nachricht = new QNachricht("nachricht");

    QueryDsl nachrichtQueryDsl = createQueryDsl(nachricht);
    JPAQuery<Nachricht> nachrichtQuery = nachrichtQueryDsl.getQuery();
    BooleanExpression nachrichtMatches = nachricht.id.isNotNull(); // Immer wahr.

    QNachricht urspruenglicheNachricht = new QNachricht("urspruenglicheNachricht");
    QueryDsl urspruenglicheNachrichtSubQueryDsl = createQueryDsl(urspruenglicheNachricht);
    JPAQuery<Nachricht> urspruenglicheNachrichtSubQuery = urspruenglicheNachrichtSubQueryDsl.getQuery();
    BooleanExpression urspruenglicheNachrichtMatches = urspruenglicheNachricht.id.eq(nachricht.urspruenglicheNachricht.id);

    // Filter implizit nach aktuellem Benutzer.
    Benutzer login = searchParameters.getLogin();
    BenutzerEinsatzort benutzerEinsatzort = benutzerEinsatzortExtendedDAO.attach(searchParameters.getBenutzerEinsatzort());
    boolean filterByLogin = !login.isAdmin();

    if (filterByLogin) {
      String loginkennung = login.getBenutzerkennung();
      Collection<Long> loginRolleIds = collectToNonEmptyRolleIds(login.getAlleRollen(benutzerEinsatzort));

      nachrichtMatches = nachrichtMatches.and(loginMatches(nachricht, loginkennung, loginRolleIds));

      urspruenglicheNachrichtMatches = urspruenglicheNachrichtMatches.and(loginMatches(urspruenglicheNachricht, loginkennung, loginRolleIds));
    }

    // Filter explizit nach eingegebenen Suchparametern.
    NachrichtEmpfaengerGruppeArt empfaengerGruppeArt = searchParameters.getEmpfaengerGruppeArt();

    if (nonNull(empfaengerGruppeArt)) {
      nachrichtMatches = nachrichtMatches.and(empfaengerGruppeArtMatches(nachricht, empfaengerGruppeArt));

      urspruenglicheNachrichtMatches = urspruenglicheNachrichtMatches.and(empfaengerGruppeArtMatches(urspruenglicheNachricht, empfaengerGruppeArt));
    }

    if (isNotBlank(searchParameters.getEmpfaengerRolleFreitext())) {
      String empfaengerRolleFreitextPattern = nachrichtQueryDsl.applySearchStringWildcards(searchParameters.getEmpfaengerRolleFreitext());

      nachrichtMatches = nachrichtMatches.and(empfaengerRolleMatches(nachricht, empfaengerRolleFreitextPattern));

      urspruenglicheNachrichtMatches = urspruenglicheNachrichtMatches.and(empfaengerRolleMatches(urspruenglicheNachricht, empfaengerRolleFreitextPattern));
    }

    Date zeitpunktVon = searchParameters.getZeitpunktVon();

    if (nonNull(zeitpunktVon)) {
      nachrichtMatches = nachrichtMatches.and(zeitpunktVonMatches(nachricht, zeitpunktVon));

      urspruenglicheNachrichtMatches = urspruenglicheNachrichtMatches.and(zeitpunktVonMatches(urspruenglicheNachricht, zeitpunktVon));
    }

    Date zeitpunktBis = searchParameters.getZeitpunktBis();

    if (nonNull(zeitpunktBis)) {
      nachrichtMatches = nachrichtMatches.and(zeitpunktBisMatches(nachricht, zeitpunktBis));

      urspruenglicheNachrichtMatches = urspruenglicheNachrichtMatches.and(zeitpunktBisMatches(urspruenglicheNachricht, zeitpunktBis));
    }

    nachrichtQuery.where(nachrichtMatches.or(urspruenglicheNachrichtSubQuery.where(urspruenglicheNachrichtMatches).exists()));

    return nachrichtQueryDsl.orderBy(nachricht.id.desc()).list(nachricht);
  }

  private QueryDsl createQueryDsl(QNachricht nachricht) {
    return dao.getQueryDsl().from(nachricht);
  }

  private BooleanExpression loginMatches(QNachricht nachricht, String loginkennung, Collection<Long> loginRolleIds) {
    return senderkennungMatches(nachricht, loginkennung).or(empfaengerGruppeArtMatches(nachricht, NachrichtEmpfaengerGruppeArt.ALLE)).or(empfaengerRolleMatches(nachricht, loginRolleIds));
  }

  private BooleanExpression senderkennungMatches(QNachricht nachricht, String senderkennung) {
    return nachricht.senderkennung.eq(senderkennung);
  }

  private BooleanExpression empfaengerGruppeArtMatches(QNachricht nachricht, NachrichtEmpfaengerGruppeArt empfaengerGruppeArt) {
    return nachricht.empfaengerGruppeArt.eq(empfaengerGruppeArt);
  }

  private BooleanExpression empfaengerRolleMatches(QNachricht nachricht, Collection<Long> empfaengerRolleIds) {
    QRolle anyRolle = nachricht.rolles.any();

    return anyRolle.id.in(empfaengerRolleIds);
  }

  private BooleanExpression empfaengerRolleMatches(QNachricht nachricht, String freitextPattern) {
    QRolle anyRolle = nachricht.rolles.any();

    return anyRolle.name.like(freitextPattern).or(anyRolle.beschreibung.like(freitextPattern));
  }

  @SuppressWarnings("unchecked")
  private BooleanExpression zeitpunktVonMatches(QNachricht nachricht, Date zeitpunkt) {
    return nachricht.zeitpunkt.goe(zeitpunkt);
  }

  @SuppressWarnings("unchecked")
  private BooleanExpression zeitpunktBisMatches(QNachricht nachricht, Date zeitpunkt) {
    return nachricht.zeitpunkt.loe(zeitpunkt);
  }

  public Long countNachrichtenUngelesen(Benutzer benutzer, BenutzerEinsatzort benutzerEinsatzort) {
    Long letzteId = 0L;

    if (nonNull(benutzer.getLetzteNachrichtId())) {
      letzteId = benutzer.getLetzteNachrichtId();
    }

    return dao.findByCountByUngelesen(benutzer.getBenutzerkennung(), letzteId, collectToNonEmptyRolleIds(benutzer.getAlleRollen(benutzerEinsatzort, rolleExtendedDAO)), benutzer.getMandant());
  }

  private Collection<Long> collectToNonEmptyRolleIds(Collection<Rolle> rollen) {
    Collection<Long> result = new HashSet<>();

    result.add(0L); // Garantiert nicht leere Menge!
    result.addAll(rollen.stream().map(Rolle::getId).collect(Collectors.toList()));

    return result;
  }
}
