
package de.ivu.elect.was.exchange.ergebnis.eml;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Comparator;
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 java.util.zip.ZipOutputStream;

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

import de.ivu.elect.EmlExportMapperHelper;
import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportObject;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.NlWasListenkandidatur;
import de.ivu.elect.business.person.entity.NlPersonendaten;
import de.ivu.elect.business.security.boundary.EmlSignKeyService;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.readable.Eml520CandidateWrapper;
import de.ivu.elect.nl.eml.readable.Eml520ContestWrapper;
import de.ivu.elect.nl.eml.readable.Eml520Wrapper;
import de.ivu.elect.nl.eml.readable.ManagingAuthorityWrapper;
import de.ivu.elect.wus.exchange.wahlvorschlag.Eml230Mapper;

public class Eml520ErgebnisExportMapper extends AbstractEml5xxErgebnisExportMapper {

  private static final long serialVersionUID = 1L;

  public static final FileFormat FILE_FORMAT = new FileFormat("eml.520.file.format", FileType.ZIP);

  @Inject
  private transient Eml230Mapper eml230Mapper;

  @Inject
  private transient EmlExportMapperHelper emlExportMapperHelper;

  @Inject
  private transient EmlSignKeyService emlSignKeyService;

  @Inject
  private transient CurrentCustomer currentCustomer;

  public Eml520ErgebnisExportMapper() {
    super();
  }

  @Override
  public void serialize(ErgebnisExportObject ergebnisExportObject, OutputStream os, ErgebnisExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    EmlWrapper eml = EmlWrapper.createEml("520");

    // ELECTVAPP-780
    ErgebnisDaten ergebnisDaten = ergebnisExportObject.getErgebnisDaten();
    List<NlPersonendaten> alleKandidaturen = ergebnisDaten.getKandidaturen().stream().map(NlPersonendaten::new).collect(Collectors.toList());
    Map<String, String> shortCodeNachExterneId = new HashMap<>();

    eml.getContent().setIssueDate(null); // ELECTVAPP-491

    Eml520Wrapper eml520Wrapper = new Eml520Wrapper(eml.getContent());

    mappeWahldaten(ergebnisExportObject, eml520Wrapper, ergebnisDaten.getWurzelgebiet(), () -> {
      List list = ergebnisDaten.getGebiete();

      return list;
    });

    // ELECTVAPP-399
    fillManagingAuthority(eml520Wrapper.getManagingAuthority(), ergebnisExportObject, context);

    List<Gewaehlt> gewaehlte = ergebnisDaten.getGewaehlte();

    Map<PolitischeGruppe, List<Listenkandidatur>> gewaehlteKandidaturenProGruppe = bestimmeGewaehlteKandidatuenProGruppe(gewaehlte, ergebnisDaten.getEntityAufStimmzettelDaten());

    Eml520ContestWrapper eml520ContestWrapper = eml520Wrapper.addContest();

    if (ergebnisDaten.getWurzelgebiet().isListenwahlgebiet()) {
      eml520ContestWrapper.setContestId("geen");
    } else {
      eml520ContestWrapper.setContestId("alle");
    }

    gewaehlteKandidaturenProGruppe.entrySet().stream().sorted(Comparator.comparing(u -> u.getKey().getRelativePosition())).forEach(e -> {
      PolitischeGruppe politischeGruppe = e.getKey();

      eml520ContestWrapper.addElectedParty(Long.toString(politischeGruppe.getRelativePosition()), politischeGruppe.getName());

      List<Listenkandidatur> listenkandidaturen = e.getValue();

      for (Listenkandidatur listenkandidatur : listenkandidaturen) {
        NlWasListenkandidatur nlListenkandidatur = new NlWasListenkandidatur(listenkandidatur);

        Eml520CandidateWrapper candidateWrapper = eml520ContestWrapper.addElectedCandidate(getRanking(nlListenkandidatur));
        candidateWrapper.setId(Long.toString(notNull(nlListenkandidatur.getStimmzettelPosition(ergebnisDaten.getEntityAufStimmzettelDaten()).getPosition())));
        candidateWrapper.setShortCode(nlListenkandidatur.getShortCode(alleKandidaturen, shortCodeNachExterneId, currentCustomer.getCustomer()));
        candidateWrapper.setInitials(nlListenkandidatur.getInitialen()); // ELECT-9564
        candidateWrapper.setFirstName(nlListenkandidatur.getVorname());// ELECT-9564
        candidateWrapper.setLastName(nlListenkandidatur.getFamilienname());
        candidateWrapper.setNamePrefix(nlListenkandidatur.getFamiliennameBestandteil());

        if (NlListe.isPublishGender(nlListenkandidatur.getListe())) {
          candidateWrapper.setGender(eml230Mapper.mappeGeschlecht(nlListenkandidatur.getGeschlecht()));
        }

        candidateWrapper.setLocalityName(nlListenkandidatur.getWohnanschrift().getOrt()); // ELECT-9796
      }
    });

    try (ZipOutputStream zos = new ZipOutputStream(os)) {
      Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

      emlExportMapperHelper.writeEml(eml, zos, createFileName(ergebnisExportObject, context), ergebnisDaten.getWurzelgebiet(), context,
          retrievePrivateKey(wahldurchfuehrung, context, emlSignKeyService), retrievePublicKey(wahldurchfuehrung, context, emlSignKeyService),
          retrieveNlX509Metadata(wahldurchfuehrung, context, emlSignKeyService));

      zos.finish();
    } catch (IOException e) {
      throw new MappingException(e.getMessage(), e);
    }
  }

  private BigInteger getRanking(NlWasListenkandidatur nlListenkandidatur) {
    // Semantik zu klären
    if (isUeberVorzugGewaehlt(nlListenkandidatur)) {
      return BigInteger.ONE;
    }

    return BigInteger.valueOf(2);
  }

  private boolean isUeberVorzugGewaehlt(NlWasListenkandidatur nlListenkandidatur) {
    return nlListenkandidatur.isUeberVorzugGewaehlt();
  }

  private Map<PolitischeGruppe, List<Listenkandidatur>> bestimmeGewaehlteKandidatuenProGruppe(List<Gewaehlt> gewaehlte, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {

    Map<PolitischeGruppe, List<Listenkandidatur>> result = new HashMap<>();

    Map<PolitischeGruppe, Set<Listenkandidatur>> listenkandidaturenProGruppe = new HashMap<>();

    for (Gewaehlt gewaehlt : gewaehlte) {
      if (gewaehlt.getGewaehltStatus().isGewaehlt()) {
        listenkandidaturenProGruppe.computeIfAbsent(gewaehlt.getKandidatur().getGruppe(), x -> new HashSet<>()).add(gewaehlt.getKandidatur().castTo(Listenkandidatur.class).getEntity());
      }
    }

    for (Map.Entry<PolitischeGruppe, Set<Listenkandidatur>> entry : listenkandidaturenProGruppe.entrySet()) {
      PolitischeGruppe politischeGruppe = entry.getKey();

      Set<Listenkandidatur> kandidaten = new HashSet<>();
      for (Listenkandidatur listenkandidatur : entry.getValue()) {
        if (!isVerknupefteKandidaturenSchonVorhanden(listenkandidatur, kandidaten)) {
          kandidaten.add(listenkandidatur);
        }
      }
      List<Listenkandidatur> listenkandidaturen = kandidaten.stream().sorted(Comparator.comparing(u -> notNull(u.getStimmzettelPosition(entityAufStimmzettelDaten).getPosition())))
          .collect(Collectors.toList());

      result.put(politischeGruppe, listenkandidaturen);

    }

    return result;

  }

  private Long notNull(Long position) {
    if (position == null) {
      return 0L;
    }
    return position;
  }

  private boolean isVerknupefteKandidaturenSchonVorhanden(Listenkandidatur listenkandidatur, Set<Listenkandidatur> kandidaten) {
    return listenkandidatur.getIdentischVerknuepfteKandidaturen().stream().anyMatch(kandidaten::contains);
  }

  private String createFileName(ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    String filename = getMessage("eml.resultat.prefix");
    filename += "_";
    filename += ergebnisExportObject.getErgebnisDaten().getWahl().getKennzeichen();
    return filename + EML_FILE_EXTENSION;
  }

  @Override
  protected boolean isErgebnisNotwendig() {
    return true;
  }

  @Override
  protected boolean isPsbNotwendig() {
    return false;
  }

  @Override
  protected boolean isHsbNotwendig() {
    return false;
  }

  @Override
  protected boolean isCsbNotwendig() {
    return true;
  }

  @Override
  protected boolean isGebietAuswahlNotwendig() {
    return false;
  }

  @Override
  protected boolean isErfassungsgebietAuswahlNotwendig() {
    return false;
  }

  @Nullable
  @Override
  protected Wahldurchfuehrungsgebiet bestimmeGebietFuerDaten(ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    return bestimmeOberstesGebiet(ergebnisExportObject, context);
  }

  @Override
  public FileFormat getFileFormat() {
    return FILE_FORMAT;
  }

  @Override
  public boolean isSignEml(Wahl wahl) {
    return false; // nicht notwendig siehe https://confluence.ivu.de/pages/viewpage.action?spaceKey=VAPP&title=EML-Import+validation
  }

  @Override
  protected void fillManagingAuthority(ManagingAuthorityWrapper managingAuthority, ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    super.fillManagingAuthority(managingAuthority, ergebnisExportObject, context);
    managingAuthority.setAuthorityId("CSB"); // ELECTVAPP-474
  }

  @Override
  protected String getAuthorityIdPrefix(ErgebnisExportObject ergebnisExportObject) {
    return ""; // siehe oben, wird überschrieben
  }

  @Override
  protected String createdByAuthorityIdPrefix(ErgebnisExportObject ergebnisExportObject) {
    return "";
  }
}
