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

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

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelDAO;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelKopfdatumDAO;
import de.ivu.elect.business.stimmzettel.entity.AbstractStimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumArt;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumTK;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelTK;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelTeam;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelWesUpdateResult;
import de.ivu.elect.business.stimmzettel.entity.WesStimmzettelDaten;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;

public class StimmzettelWesUpdate {

  private static final Logger LOG = LoggerFactory.getLogger(StimmzettelWesUpdate.class);

  @Inject
  private transient GruppeDAO gruppeDAO;

  @Inject
  private transient StimmzettelDAO stimmzettelDAO;

  @Inject
  private transient StimmzettelKopfdatumDAO stimmzettelKopfdatumDAO;

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient Messages messages;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;


  public StimmzettelWesUpdateResult importiereFuerHauptteam(Gebiet gebiet, WesStimmzettelDaten wesStimmzettelDaten, StimmzettelTeam stimmzettelTeam) {

    LOG.info("importiereFuerHauptteam Stimmzettel für: " + stimmzettelTeam);
    if (!stimmzettelTeam.isFestgelegt()) {
      throw new IllegalStateException("Falsches Team: " + stimmzettelTeam);
    }

    Map<StimmzettelTK, Stimmzettel> stimmzettelListInDb = stimmzettelDAO.findByGebiet(gebiet).stream()
        .collect(Collectors.toMap(Stimmzettel::getStimmzettelTK, stimmzettel -> stimmzettel, (a, b) -> b));
    Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb = stimmzettelKopfdatumDAO.findByGebiet(gebiet).stream()
        .collect(Collectors.toMap(StimmzettelKopfdatum::getStimmzettelKopfdatumTK, stimmzettelKopfdatum -> stimmzettelKopfdatum, (a, b) -> b));

    Set<Stimmzettel> ueberzaehligeStimmzettel = new HashSet<>(stimmzettelListInDb.values());

    Map<String, Gruppe> gruppeNachSchluessel = new HashMap<>();
    for (Gruppe gruppe : gruppeDAO.findByWahl(gebiet.getWahl())) {
      gruppeNachSchluessel.put(gruppe.getSchluessel(), gruppe);
    }

    List<StimmzettelKopfdatum> stimmzettelKopfdatenInDatei = wesStimmzettelDaten.getStimmzettelKopfdaten();
    Long versionsnummerInDatei = bestimmeVersionsnummer(stimmzettelKopfdatenInDatei);

    LOG.info("importiereFuerHauptteam - versionsnummerInDatei=" + versionsnummerInDatei);
    if (versionsnummerInDatei == null) {
      throw new IllegalStateException("Keine Versionsnummer");
    }

    Map<StimmzettelKopfdatumArt, Boolean> teamUeberfluessigeStimmzettelEntfernen = new EnumMap<>(StimmzettelKopfdatumArt.class);
    boolean hauptTeamUeberfluessigeStimmzettelEntfernen = false;

    if (stimmzettelTeam.isKopfzahlenErfassen()) {
      Long versionsnummerInDb = bestimmeVersionsnummer(stimmzettelKopfdatenInDb, gebiet);
      LOG.info("importiereFuerHauptteam - versionsnummerInDb=" + versionsnummerInDb);

      boolean kopfdatenAktualisieren = versionsnummerInDb == null || versionsnummerInDb < versionsnummerInDatei;
      // Nur aktualisieren, wenn neuere Version importiert werden soll
      LOG.info("importiereFuerHauptteam - kopfdatenAktualisieren: " + kopfdatenAktualisieren);
      hauptTeamUeberfluessigeStimmzettelEntfernen = true;

      for (StimmzettelKopfdatum stimmzettelKopfdatum : stimmzettelKopfdatenInDatei) {

        if (StimmzettelKopfdatumArt.ERFASSUNGSTEAM.equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt())) {
          continue; // nicht importieren, für WAS irrelevant
        }

        stimmzettelKopfdatum.setGebiet(gebiet);
        if (stimmzettelKopfdatum.getGruppe() != null && StringUtils.isNotBlank(stimmzettelKopfdatum.getGruppe().getSchluessel())) {
          Gruppe gruppeInDb = gruppeNachSchluessel.get(stimmzettelKopfdatum.getGruppe().getSchluessel());
          if (gruppeInDb == null) {
            throw new IllegalStateException("Gruppe nicht gefunden: " + stimmzettelKopfdatum.getGruppe().getSchluessel());
          }
          stimmzettelKopfdatum.setGruppe(gruppeInDb);
        }

        StimmzettelKopfdatum stimmzettelKopfdatumInDB = stimmzettelKopfdatenInDb.get(stimmzettelKopfdatum.getStimmzettelKopfdatumTK());

        if (stimmzettelKopfdatumInDB == null) {
          // Anlegen immer, unabhängig von Versionsnummern
          LOG.info("importiereFuerHauptteam - Kopfdatum nicht gefunden, lege an: " + stimmzettelKopfdatum);
          stimmzettelKopfdatumDAO.persist(stimmzettelKopfdatum);
          stimmzettelKopfdatenInDb.put(stimmzettelKopfdatum.getStimmzettelKopfdatumTK(), stimmzettelKopfdatum);
        } else {
          StimmzettelKopfdatumArt stimmzettelKopfdatumArt = stimmzettelKopfdatumInDB.getStimmzettelKopfdatumArt();
          // Aktualisieren nur, wenn Hauptversion kleiner und keine Versionsnummer eines Teams, oder Versionsnummer eines Teams und diese kleiner
          if ((!stimmzettelKopfdatumArt.isHilfsTeamVersion() && kopfdatenAktualisieren) ||
              (stimmzettelKopfdatumArt.isHilfsTeamVersion() && stimmzettelKopfdatumInDB.parseWertAsLong() < stimmzettelKopfdatum.parseWertAsLong())) {
            stimmzettelKopfdatumInDB.setWert(stimmzettelKopfdatum.getWert());
            if (stimmzettelKopfdatumArt.isHilfsTeamVersion()) {
              teamUeberfluessigeStimmzettelEntfernen.put(stimmzettelKopfdatumArt, Boolean.TRUE);
            }
          }
        }
      }
    }

    if (stimmzettelTeam.isHilfsteam()) {
      StimmzettelKopfdatum stimmzettelKopfdatumTeamVersion = stimmzettelKopfdatenInDb
          .get(new StimmzettelKopfdatumTK(stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer().getKey(), gebiet.getGebietTK(), null));
      if (stimmzettelKopfdatumTeamVersion == null) {
        stimmzettelKopfdatumTeamVersion = new StimmzettelKopfdatum();
        stimmzettelKopfdatumTeamVersion.setGebiet(gebiet);
        stimmzettelKopfdatumTeamVersion.setSchluessel(stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer().getKey());
        stimmzettelKopfdatumDAO.persist(stimmzettelKopfdatumTeamVersion);
        stimmzettelKopfdatenInDb.put(stimmzettelKopfdatumTeamVersion.getStimmzettelKopfdatumTK(), stimmzettelKopfdatumTeamVersion);
        stimmzettelKopfdatumTeamVersion.setWert(Long.toString(versionsnummerInDatei));
        teamUeberfluessigeStimmzettelEntfernen.put(stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer(), Boolean.TRUE);
      } else if (stimmzettelKopfdatumTeamVersion.parseWertAsLong() < versionsnummerInDatei) {
        stimmzettelKopfdatumTeamVersion.setWert(Long.toString(versionsnummerInDatei));
        teamUeberfluessigeStimmzettelEntfernen.put(stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer(), Boolean.TRUE);
      }
    }

    boolean stimmzettelInDbNeuer = isAktualisiereStimmzettelInDb(gebiet, wesStimmzettelDaten, stimmzettelTeam, stimmzettelListInDb, ueberzaehligeStimmzettel);

    ueberzaehligeStimmzettel.removeIf(s -> !s.getStimmzettelTeam().isFestgelegt());

    if (stimmzettelTeam.isHilfsteam()) {
      ueberzaehligeStimmzettel.removeIf(s -> !stimmzettelTeam.equals(s.getStimmzettelTeam()));
    }
    boolean hauptTeamUeberfluessigeStimmzettelEntfernenFinal = hauptTeamUeberfluessigeStimmzettelEntfernen;
    LOG.info("Importiere Stimmzettel - hauptTeamUeberfluessigeStimmzettelEntfernenFinal=" + hauptTeamUeberfluessigeStimmzettelEntfernenFinal);
    LOG.info("Importiere Stimmzettel - teamUeberfluessigeStimmzettelEntfernen=" + teamUeberfluessigeStimmzettelEntfernen);
    ueberzaehligeStimmzettel.removeIf(s -> {
      if (s.getStimmzettelTeam().isKopfzahlenErfassen() && !hauptTeamUeberfluessigeStimmzettelEntfernenFinal) {
        return true;
      }

      return s.getStimmzettelTeam().isHilfsteam() && !Boolean.TRUE
          .equals(teamUeberfluessigeStimmzettelEntfernen.get(s.getStimmzettelTeam().getStimmzettelKopfdatumVersionsnummer()));
    });

    Set<Stimmzettel> stimmzettelInDbFinal = new HashSet<>(stimmzettelListInDb.values());
    stimmzettelInDbFinal.removeAll(ueberzaehligeStimmzettel);

    entferneUeberzaehligeStimmzettel(ueberzaehligeStimmzettel);

    pruefeSignatur(gebiet, wesStimmzettelDaten, stimmzettelTeam);

    return new StimmzettelWesUpdateResult(
        isDatenInDbNeuer(stimmzettelInDbNeuer, stimmzettelTeam, gebiet, stimmzettelKopfdatenInDb, stimmzettelKopfdatenInDatei),
        stimmzettelKopfdatenInDb.values(), stimmzettelInDbFinal);
  }

  private void pruefeSignatur(Gebiet gebiet, WesStimmzettelDaten wesStimmzettelDaten, StimmzettelTeam stimmzettelTeam) {
    if (!Boolean.TRUE.equals(wesStimmzettelDaten.getSignaturGueltig())){
      systemLogWriter.writeWarnLogMessageFuerMandant(gebiet.getMandant(), messages.getMessageForMessageKey("wes.import.ohne.gueltige.signatur", gebietNummerFormatter.getDarstellung(gebiet), gebiet.getWahl().getName(), messages.lokalisiere(stimmzettelTeam)), SystemLogQuelle.WAHLLOKALSYSTEM);
    }
  }

  private void entferneUeberzaehligeStimmzettel(Set<Stimmzettel> ueberzaehligeStimmzettel) {
    for (Stimmzettel stimmzettelZuEntfernen : ueberzaehligeStimmzettel) {
      LOG.info("Entferne Stimmzettel: " + stimmzettelZuEntfernen);
      stimmzettelDAO.remove(stimmzettelZuEntfernen);
    }
  }

  private boolean isAktualisiereStimmzettelInDb(Gebiet gebiet, WesStimmzettelDaten wesStimmzettelDaten, StimmzettelTeam stimmzettelTeam,
                                                Map<StimmzettelTK, Stimmzettel> stimmzettelListInDb, Set<Stimmzettel> ueberzaehligeStimmzettel) {

    boolean stimmzettelInDbNeuer = false;
    for (Stimmzettel stimmzettel : wesStimmzettelDaten.getStimmzettelList()) {

      if (!stimmzettelTeam.isHauptteam() && !stimmzettelTeam.equals(stimmzettel.getStimmzettelTeam())) {
        throw new IllegalStateException(
            "Stimmzettel anderer Teams können nicht importiert werden! " + stimmzettelTeam + " <> " + stimmzettel.getStimmzettelTeam());
      }

      stimmzettel.setGebiet(gebiet);
      Stimmzettel stimmzettel1InDb = stimmzettelListInDb.get(stimmzettel.getStimmzettelTK());
      if (stimmzettel1InDb == null) {
        stimmzettelDAO.persist(stimmzettel);
        stimmzettelListInDb.put(stimmzettel.getStimmzettelTK(), stimmzettel);
      } else {
        if (!stimmzettel1InDb.getStimmzettelTeam().equals(stimmzettel.getStimmzettelTeam())) {
          throw new IllegalStateException(
              "Stimmzettelnummer von anderem Team bereits erfasst: " + stimmzettel1InDb + ", " + stimmzettel + ", " + stimmzettel1InDb
                  .getStimmzettelTeam() + ", " + stimmzettel
                  .getStimmzettelTeam());
        }
        ueberzaehligeStimmzettel.remove(stimmzettel1InDb);
        if (stimmzettel1InDb.getVersionsnummer() < stimmzettel.getVersionsnummer()) {
          stimmzettel.copyEntityAttributesUsingSetter(stimmzettel1InDb);
          stimmzettel1InDb.setErfasssteDaten(stimmzettel.getErfassteDaten());
        } else {
          if (stimmzettel1InDb.getVersionsnummer() > stimmzettel.getVersionsnummer()) {
            stimmzettelInDbNeuer = true;
            LOG.info("Stimmzettel nicht aktualisiert: " + stimmzettel + ", " + stimmzettel.getVersionsnummer() + ", " + stimmzettel1InDb.getVersionsnummer());
          }
        }
      }
    }
    return stimmzettelInDbNeuer;
  }

  private boolean isDatenInDbNeuer(boolean stimmzettelInDbNeuer, StimmzettelTeam stimmzettelTeam, Gebiet gebiet, Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb, List<StimmzettelKopfdatum> stimmzettelKopfdatenInDatei) {
    if (stimmzettelInDbNeuer) {
      return true;
    }

    if (isVersionInDbNeuer(stimmzettelTeam.name(), bestimmeVersionsnummer(stimmzettelKopfdatenInDatei),
        bestimmeVersionsnummer(stimmzettelKopfdatenInDb, gebiet, stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer()))) {
      return true;
    }

    if (!stimmzettelTeam.isHilfsteam()) {
      for (StimmzettelTeam stimmzettelTeamOther : StimmzettelTeam.values()) {
        if (stimmzettelTeamOther.isHilfsteam()) {
          Long versionInDatei = bestimmeVersionsnummer(stimmzettelKopfdatenInDatei, stimmzettelTeamOther.getStimmzettelKopfdatumVersionsnummer());
          Long versionInDB = bestimmeVersionsnummer(stimmzettelKopfdatenInDb, gebiet, stimmzettelTeamOther.getStimmzettelKopfdatumVersionsnummer());
          if (versionInDB != null) {
            if (isVersionInDbNeuer(stimmzettelTeamOther.name(), versionInDatei, versionInDB)) {
              return true;
            }
          }
        }
      }
    }

    return false;
  }

  private boolean isVersionInDbNeuer(String team, Long versionsnummerInDatei, Long versionInDB) {
    LOG.info("team = " + team + ", versionsnummerInDatei = " + versionsnummerInDatei + ", versionInDB = " + versionInDB);
    return versionInDB == null || versionsnummerInDatei == null || versionInDB > versionsnummerInDatei;
  }

  @Nullable
  private Long bestimmeVersionsnummer(Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb, Gebiet gebiet) {
    return bestimmeVersionsnummer(stimmzettelKopfdatenInDb, gebiet, StimmzettelKopfdatumArt.VERSIONSNUMMER);
  }

  @Nullable
  private Long bestimmeVersionsnummer(Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb, Gebiet gebiet, StimmzettelKopfdatumArt versionKopfdatum) {
    StimmzettelKopfdatum stimmzettelKopfdatum = stimmzettelKopfdatenInDb
        .get(new StimmzettelKopfdatumTK(versionKopfdatum.getKey(), gebiet.getGebietTK(), null));

    if (stimmzettelKopfdatum == null || StringUtils.isBlank(stimmzettelKopfdatum.getWert())) {
      return null;
    }
    return Long.parseLong(stimmzettelKopfdatum.getWert());
  }

  @Nullable
  private Long bestimmeVersionsnummer(List<StimmzettelKopfdatum> stimmzettelKopfdatenInDatei) {
    return bestimmeVersionsnummer(stimmzettelKopfdatenInDatei, StimmzettelKopfdatumArt.VERSIONSNUMMER);
  }

  private Long bestimmeVersionsnummer(List<StimmzettelKopfdatum> stimmzettelKopfdatenInDatei, StimmzettelKopfdatumArt stimmzettelKopfdatumArt) {
    return stimmzettelKopfdatenInDatei.stream()
        .filter(stimmzettelKopfdatum -> {
          return stimmzettelKopfdatumArt.equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt());
        }).findFirst()
        .map(AbstractStimmzettelKopfdatum::parseWertAsLong).orElse(null);
  }

  public void importiereFuerHilfsteam(Gebiet gebiet, WesStimmzettelDaten wesStimmzettelDaten, StimmzettelTeam stimmzettelTeam) {

    Map<StimmzettelTK, Stimmzettel> stimmzettelListInDb = stimmzettelDAO.findByGebiet(gebiet).stream()
        .collect(Collectors.toMap(Stimmzettel::getStimmzettelTK, stimmzettel -> stimmzettel, (a, b) -> b));
    Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb = stimmzettelKopfdatumDAO.findByGebiet(gebiet).stream()
        .collect(Collectors.toMap(StimmzettelKopfdatum::getStimmzettelKopfdatumTK, stimmzettelKopfdatum -> stimmzettelKopfdatum, (a, b) -> b));

    Set<Stimmzettel> ueberzaehligeStimmzettel = new HashSet<>(stimmzettelListInDb.values());
    ueberzaehligeStimmzettel.removeIf(s -> !stimmzettelTeam.equals(s.getStimmzettelTeam()));

    List<StimmzettelKopfdatum> stimmzettelKopfdatenInDatei = wesStimmzettelDaten.getStimmzettelKopfdaten();

    Long versionsnummerInDatei = bestimmeVersionsnummer(stimmzettelKopfdatenInDatei);
    LOG.info("importiereFuerHilfsteam - versionsnummerInDatei=" + versionsnummerInDatei);
    Long versionsnummerInDb = bestimmeVersionsnummer(stimmzettelKopfdatenInDb, gebiet);
    LOG.info("importiereFuerHilfsteam - versionsnummerInDb=" + versionsnummerInDb);

    if (versionsnummerInDb != null && versionsnummerInDatei != null) {
      LOG.info("importiereFuerHilfsteam - aktualisiere Stimmzettel");
      isAktualisiereStimmzettelInDb(gebiet, wesStimmzettelDaten, stimmzettelTeam, stimmzettelListInDb, ueberzaehligeStimmzettel);
      if (versionsnummerInDatei > versionsnummerInDb) {
        LOG.info("importiereFuerHilfsteam - versionInDb veraltet, entferne ueberzaehlige Stimmzettel: " + ueberzaehligeStimmzettel);
        entferneUeberzaehligeStimmzettel(ueberzaehligeStimmzettel);
        setzeVersion(stimmzettelKopfdatenInDb, gebiet, versionsnummerInDatei);
      } else {
        LOG.info("importiereFuerHilfsteam - versionInDb neuer als online");
      }
    }
    pruefeSignatur(gebiet, wesStimmzettelDaten, stimmzettelTeam);

  }

  private void setzeVersion(Map<StimmzettelKopfdatumTK, StimmzettelKopfdatum> stimmzettelKopfdatenInDb, Gebiet gebiet, Long versionsnummerInDatei) {

    StimmzettelKopfdatum stimmzettelKopfdatum = stimmzettelKopfdatenInDb
        .get(new StimmzettelKopfdatumTK(StimmzettelKopfdatumArt.VERSIONSNUMMER.getKey(), gebiet.getGebietTK(), null));

    if (stimmzettelKopfdatum != null) {
      stimmzettelKopfdatum.setWert(Long.toString(versionsnummerInDatei));
    }
  }
}
