package de.ivu.elect.business.wahl.control;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.mandant.entity.MandantPK;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlPK;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahl.entity.WahlaufteilenStatus;
import de.ivu.elect.business.wahl.entity.WahlaufteilenStatusContainer;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;


import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

@Stateless
public class WahlMandantenAufteilenStatusErmittler {

  @Inject
  private transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient MandantDAO mandantDAO;

  public Map<MandantPK, WahlaufteilenStatusContainer> bestimmeStatusFuerMandanten(WahlPK wahlPK) {
    Wahldurchfuehrung wahl = wahldurchfuehrungDAO.findByPrimaryKey(wahlPK);
    Map<MandantPK, WahlaufteilenStatusContainer> result = new HashMap<>();

    for (Map.Entry<Mandant, List<Gebiet>> entry : gebietDAO.findByWahlMitVerantwortlicherMandant(wahl).stream()
        .collect(Collectors.groupingBy(Gebiet::getVerantwortlicherMandant)).entrySet()) {
      result.put(entry.getKey().getPk(), berechneStatus(wahl, entry.getKey(), entry.getValue()));
    }

    return result;
  }

  public Set<MandantPK> bestimmeAufzuteilendeMandanten(WahlPK wahlPK) {
    return bestimmeStatusFuerMandanten(wahlPK).entrySet().stream().filter(entry -> entry.getValue().getWahlaufteilenStatus().isAufteilenMoeglich())
        .map(Map.Entry::getKey).collect(Collectors.toSet());
  }

  public Set<MandantPK> bestimmeZuLoeschendeMandanten(WahlPK wahlPK) {
    return bestimmeStatusFuerMandanten(wahlPK).entrySet().stream().filter(entry -> entry.getValue().getWahlaufteilenStatus().isWahlLoeschenMoeglich())
        .map(Map.Entry::getKey).collect(Collectors.toSet());
  }

  private WahlaufteilenStatusContainer berechneStatus(Wahldurchfuehrung wahl, Mandant mandant, List<Gebiet> gebiete) {
    if (gebiete.isEmpty()) {
      return new WahlaufteilenStatusContainer(WahlaufteilenStatus.MANDANT_NICHT_VERANTWORTLICH, emptyList());
    } else if (gebiete.size() == 1) {
      return bestimmeAufteilenStatus(wahl, mandant, gebiete.get(0));
    } else {
      return new WahlaufteilenStatusContainer(WahlaufteilenStatus.MANDANT_MEHRFACH_VERANWORTLICH,
          gebiete.stream().sorted(new GebietByBreadthFirstComparator()).map(Gebiet::getPk).collect(toList()));
    }
  }

  public WahlaufteilenStatusContainer bestimmeStatusFuerMandant(WahlPK wahlPK, MandantPK mandantPK) {
    Wahldurchfuehrung wahl = wahldurchfuehrungDAO.findByPrimaryKey(wahlPK);
    Mandant mandant = mandantDAO.findByPrimaryKey(mandantPK);
    Map<Mandant, List<Gebiet>> gebieteNachMandanten = gebietDAO.findByWahlMitVerantwortlicherMandant(wahl).stream()
        .collect(Collectors.groupingBy(Gebiet::getVerantwortlicherMandant));
    return berechneStatus(wahl, mandant, gebieteNachMandanten.getOrDefault(mandant, emptyList()));
  }

  private WahlaufteilenStatusContainer bestimmeAufteilenStatus(Wahl wahl, Mandant mandant, Gebiet gebiet) {
    WahlaufteilenStatus status = WahlaufteilenStatus.AUFTEILEN_MOEGLICH;
    if (isWahlBereitsAusgeteilt(wahl, mandant)) {
      status = WahlaufteilenStatus.WAHL_SCHON_VORHANDEN;
    } else if (isWahlBereitsManuellVorhanden(wahl, mandant, gebiet)) {
      status = WahlaufteilenStatus.WAHL_SCHON_VORHANDEN_MANUELL_ANGELEGT;
    }

    return new WahlaufteilenStatusContainer(status, singletonList(gebiet.getPk()));
  }

  private boolean isWahlBereitsManuellVorhanden(Wahl wahl, Mandant mandant, Gebiet gebiet) {
    Wahlart wahlartBeiAufteilung = gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity().bestimmeWahlartBeiAufteilung();
    return wahlDAO.findByAll(mandant).stream()
        .filter(w -> w.getWahlart().getName().equalsIgnoreCase(wahlartBeiAufteilung.getName()))
        .anyMatch(w -> w.getWahldatum().equals(wahl.getWahldatum()));
  }

  private boolean isWahlBereitsAusgeteilt(Wahl wahl, Mandant mandant) {
    // ELECT-8113
    return !wahlDAO.findByBerichtetAnAndMandant(wahl, mandant).isEmpty();
  }

}
