
package de.ivu.elect.wus.exchange.personenpruefung;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.kandidatur.boundary.NlOrdnungsmerkmalPruefer;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.interfaces.core.Listenkandidatur;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.nl.interfaces.gbav.mapper.GbavDateMapper;
import de.ivu.elect.nl.interfaces.gbav.mapper.GbavGenderMapper;
import de.ivu.elect.nl.interfaces.gbav.mapper.Geschlecht;

public class NlGbavBatchExportMapperHelper {

  /*
   * Vorlage: de.ivu.wahl.wus.gbaclient.service.GbavBatchExporter (L5.backup)
   */

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

  static final int INTERNAL_ID_LENGTH = 15;
  static final int BSN_LENGTH = 9;
  static final int GIVEN_NAME_LENGTH = 60;
  static final int LAST_NAME_LENGTH = 30;
  static final int DATE_OF_BIRTH_LENGTH = 8;
  static final int GENDER_LENGTH = 1;
  static final int HOUSE_NUMBER_LENGTH = 5;
  static final int POSTCODE_LENGTH = 6;
  static final int A_NUMMER_LENGTH = 10;

  @Inject
  private transient Messages messages;

  @Inject
  private transient PersonendatenDAO personendatenDAO;

  @Inject
  private transient NlOrdnungsmerkmalPruefer nlOrdnungsmerkmalPruefer;

  public NlGbavBatchExportMapperHelper() {
    super();
  }

  public void serialize(List<Listenkandidatur> listenkandidaturen, OutputStream os) throws IOException, MappingException {
    validateKandidaturen(listenkandidaturen);

    try (OutputStreamWriter outputWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8 /* ELECTVAPP-1088 */)) {
      Set<Long> gesehene = new HashSet<>();

      for (Listenkandidatur kandidat : listenkandidaturen) {
        if (!gesehene.contains(kandidat.getId())) {
          gesehene.addAll(getListenkandidaturIdsDesKandidats(kandidat));

          createEintragFuerKandidat(outputWriter, kandidat);
        }
      }
    }
  }

  private void createEintragFuerKandidat(OutputStreamWriter outputWriter, Listenkandidatur kandidat) throws IOException {
    appendValue(outputWriter, kandidat.getId().toString(), INTERNAL_ID_LENGTH);

    String ordnungsmerkmal = kandidat.getExternesOrdnungsmerkmal();

    String familienname = kandidat.getFamilienname();
    Date geburtsdatum = kandidat.getGeburtsdatum();
    String geburtsdatumErfasst = kandidat.getStimmbezirkSchluessel(); // Cf. NlPersonendaten#geburtsdatumErfasst!
    de.ivu.elect.interfaces.core.Geschlecht geschlecht = kandidat.getGeschlecht();

    if (nlOrdnungsmerkmalPruefer.isValid(ordnungsmerkmal)) {
      appendValue(outputWriter, ordnungsmerkmal, BSN_LENGTH);
      appendValue(outputWriter, "", GIVEN_NAME_LENGTH);
      appendValue(outputWriter, "", LAST_NAME_LENGTH);
      appendValue(outputWriter, "", DATE_OF_BIRTH_LENGTH);
      appendValue(outputWriter, "", GENDER_LENGTH);
      appendValue(outputWriter, "", HOUSE_NUMBER_LENGTH);
      appendValue(outputWriter, "", POSTCODE_LENGTH);
    } else {
      appendValue(outputWriter, "", BSN_LENGTH);
      appendValue(outputWriter, "", GIVEN_NAME_LENGTH);
      appendValue(outputWriter, familienname, LAST_NAME_LENGTH, true);
      appendValue(outputWriter, formatGeburtsdatum(geburtsdatum, geburtsdatumErfasst), DATE_OF_BIRTH_LENGTH);
      appendValue(outputWriter, formatGeschlecht(geschlecht), GENDER_LENGTH);
      appendValue(outputWriter, "", HOUSE_NUMBER_LENGTH);
      appendValue(outputWriter, "", POSTCODE_LENGTH);
    }

    appendValue(outputWriter, "", A_NUMMER_LENGTH);

    outputWriter.append('\n');
  }

  protected String formatGeburtsdatum(Date geburtsdatum, String geburtsdatumErfasst) {
    String geburtsdatumDDMMYYYY = geburtsdatumErfasst;

    if (isBlank(geburtsdatumDDMMYYYY) && geburtsdatum != null) {
      try {
        geburtsdatumDDMMYYYY = new SimpleDateFormat("dd-MM-yyyy").format(geburtsdatum);
      } catch (Exception ignored) {
      }
    }

    return new GbavDateMapper().mapDateToGbavDate(geburtsdatumDDMMYYYY);
  }

  protected String formatGeschlecht(de.ivu.elect.interfaces.core.Geschlecht geschlecht) {
    Geschlecht geschlechtGbav = null;

    try {
      geschlechtGbav = Geschlecht.valueOf(geschlecht.name());
    } catch (Exception ignored) {
    }

    return GbavGenderMapper.mapGenderToGeslachtsaanduiding(geschlechtGbav);
  }

  private Set<Long> getListenkandidaturIdsDesKandidats(Listenkandidatur kandidat) {
    Set<Long> result = new HashSet<>();
    result.add(kandidat.getId());

    de.ivu.elect.business.kandidatur.entity.Listenkandidatur listenkandidatur = personendatenDAO.findByPrimaryKey(new PersonendatenPK(kandidat.getId()))
        .castTo(de.ivu.elect.business.kandidatur.entity.Listenkandidatur.class).getEntity();
    listenkandidatur.getIdentischVerknuepfteListenkandidaturen().stream().map(Personendaten::getId).forEach(result::add);

    return result;
  }

  private void validateKandidaturen(List<Listenkandidatur> listenkandidaturen) throws MappingException {
    Set<String> kandidaturenInvalidFormatted = formatKandidaturenInvalid(nlOrdnungsmerkmalPruefer, listenkandidaturen);

    if (!kandidaturenInvalidFormatted.isEmpty()) {
      LOG.error("Ein gültiges Ordnungsmerkmal oder Personendaten (Familienname, Geburtsdatum, und Geschlecht) müssen für folgenden Kandidaten vorhanden sein: \n\t{}", kandidaturenInvalidFormatted);

      throw new MappingException(messages.getMessageForMessageKey("wahl.export.gbavbatch.error"));
    }
  }

  protected Set<String> formatKandidaturenInvalid(NlOrdnungsmerkmalPruefer nlOrdnungsmerkmalPruefer, List<Listenkandidatur> listenkandidaturen) {
    return listenkandidaturen.stream() //
        .filter(lk -> !nlOrdnungsmerkmalPruefer.isValid(lk.getExternesOrdnungsmerkmal())
            && (isBlank(lk.getFamilienname()) || lk.getGeburtsdatum() == null && isBlank(lk.getStimmbezirkSchluessel()) /* a.k.a "Geburtsdatum erfasst" */ || lk.getGeschlecht() == null)) //
        .map(lk -> "[ordnungsmerkmal = " + lk.getExternesOrdnungsmerkmal() + ", familienname = " + lk.getFamilienname() + ", geburtsdatum = " + lk.getGeburtsdatum() + ", geburtsdatum erfasst = "
            + lk.getStimmbezirkSchluessel() + ", geschlecht = " + lk.getGeschlecht() + "]") //
        .collect(toSet());
  }

  private void appendValue(OutputStreamWriter outputWriter, String value, int requestedLength) throws IOException {
    appendValue(outputWriter, value, requestedLength, false);
  }

  private void appendValue(OutputStreamWriter outputWriter, String value, int requestedLength, boolean toLowerCase) throws IOException {
    String string = value;

    if (string == null) {
      string = "";
    }

    if (toLowerCase) {
      string = string.toLowerCase();
    }

    int valueLength = string.length();

    for (char c : string.toCharArray()) {
      outputWriter.append(c);
    }

    for (int i = valueLength; i < requestedLength; i++) {
      outputWriter.append(' ');
    }
  }
}
