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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

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

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

import de.ivu.elect.EmlImportMapper;
import de.ivu.elect.EmlImportMapperHelper;
import de.ivu.elect.EmlValidation;
import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.FileType;
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.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietCustomData;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietSchluessel;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImportContext;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImporterZulassungsVerhalten;
import de.ivu.elect.business.wahl.entity.NlWahlCustomData;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.nl.eml.EmlValidator;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.readable.Eml230Wrapper;


public abstract class AbstractEml230WahlvorschlagImportMapper implements WahlvorschlagImportMapper, Serializable, EmlImportMapper {

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

  private static final long serialVersionUID = 1L;

  private static final String EML_KEY = "Eml230bcWahlvorschlagImportMapper-EML";

  private static final String Eml230Filename = "Eml230Filename";

  @Inject
  private transient Eml230Mapper eml230Mapper;

  @Inject
  private transient EmlImportMapperHelper emlImportMapperHelper;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient CurrentCustomer currentCustomer;

  @Override
  public List<Wahlvorschlag> deserialize(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) throws MappingException {

    EmlWrapper emlWrapper = readEml(is, context);

    Eml230Wrapper eml230Wrapper = new Eml230Wrapper(emlWrapper.getContent());

    context.getWahl().getCustomData(NlWahlCustomData.class).setEml230Filename(context.get(Eml230Filename, String.class));
    context.getWahl().getCustomData(NlWahlCustomData.class).setEml230Date(emlWrapper.getCreationDate());
    context.getWahl().getCustomData(NlWahlCustomData.class).setEml230HashCode(context.get(EmlImportMapperHelper.EMLHASHCODE, String.class));

    // wahrscheinlich immer richtig...
    context.setZulassungsVerhalten(WahlvorschlagImporterZulassungsVerhalten.ALLE_WAHLVORSCHLAEGE_ZULASSEN);

    return eml230Mapper.mappe(eml230Wrapper, context.getListenwahlgebiete());
  }

  private EmlWrapper readEml(InputStream is, WahlvorschlagImportContext context) {
    EmlWrapper emlWrapper = context.get(EML_KEY, EmlWrapper.class);

    if (emlWrapper == null) {
      emlWrapper = emlImportMapperHelper.readEmlNoHash(is);
      context.put(EML_KEY, emlWrapper);
    }
    return emlWrapper;
  }

  @Override
  public Violations validateInputFormat(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) {

    Violations result = new Violations();
    try {
      byte[] readContent = IOUtils.toByteArray(is);

      // ELECTVAPP-1037
      try {
        EmlValidator.validateEml230(new ByteArrayInputStream(readContent));
      } catch (Exception e) {
        LOG.warn("EML invalid: " + e.getMessage(), e);
        result.addWarning("invalid.eml.file");
      }

      EmlWrapper emlWrapper = readEml(new ByteArrayInputStream(readContent), context);

      Eml230Wrapper eml230Wrapper = new Eml230Wrapper(emlWrapper.getContent());

      if (!getExpectedEmlId().equalsIgnoreCase(eml230Wrapper.getContent().getId())) {
        result.addError(getWrongEmlIdMessageKey(), eml230Wrapper.getContent().getId());
        return result;
      }

      if (StringUtils.isNotBlank(context.getWahl().getKennzeichen())) {
        if (!context.getWahl().getKennzeichen().equalsIgnoreCase(eml230Wrapper.getElectionId())) {
          result.addError("eml.import.wahl.id.stimmt.nicht.ueberein", context.getWahl().getKennzeichen(), eml230Wrapper.getElectionId());
        }
      }

      String expectedWahldatum = new SimpleDateFormat("yyyy-MM-dd").format(context.getWahl().getWahldatum());
      if (!expectedWahldatum.equals(eml230Wrapper.getElectionDate())) {
        result.addError("eml.import.wahl.datum.stimmt.nicht.ueberein", expectedWahldatum, eml230Wrapper.getElectionDate());
      }

      Wahl wahl = context.getWahl();

      // Workaround/Hack/ELECT-11333
      List<Gebiet> wahlkreiseInEml = bestimmeWahlgebiete(eml230Wrapper, wahl);
      if (NlWahlart.TWEEDE_KAMER.equalsWahlart(wahl)) {
        if (NlWahlart.isWasPSB(wahl, currentCustomer.getCustomer()) || NlWahlart.isWasHSB(wahl, currentCustomer.getCustomer())) {
          if (wahlkreiseInEml.isEmpty()) {
            result.addWarning("eml.import.eml230.wahlgebiete.nicht.gefunden");
          }
        }
      }

      Gebiet wurzelgebiet = gebietDAO.attach(context.getWurzelgebiet());
      if (!isWahlgebieteSindUntergeordnet(wahlkreiseInEml, wurzelgebiet) && /**/ !NlWahlart.isWasCSB(wahl, currentCustomer.getCustomer())) {
        result.addError("eml.import.eml230.wahlgebiete.nicht.passend");
        LOG.warn("wahlkreiseInEml: " + wahlkreiseInEml.stream().map(this::toLog).collect(Collectors.joining(", ")));
        LOG.warn("wurzelgebiet: " + toLog(wurzelgebiet));
        LOG.warn("uebergeordneteGebieteInMasterElectionTree: " + wurzelgebiet.getCustomData(NlGebietCustomData.class).getUebergeordneteGebieteInMasterElectionTree());
      }

    } catch (IOException e) {
      LOG.warn("Error reading eml: " + e.getMessage(), e);
      result.addError("exception.unknown.message");
    }

    return result;
  }

  private String toLog(Gebiet name) {
    return name.toString() + "_"+name.getName();
  }

  protected boolean isWahlgebieteSindUntergeordnet(List<Gebiet> wahlkreiseInEml, Gebiet wurzelgebiet) {
    // ELECTVAPP-923 nach ELECTVAPP-1317
    return wahlkreiseInEml.stream().anyMatch(wahlkreis -> isUebergeordnet(wahlkreis, wurzelgebiet));

//    Wahl wahl = wurzelgebiet.getWahl();
//
//    // ELECTVAPP-923 works somehow...for  TK
//    if (NlWahlart.TWEEDE_KAMER.equalsWahlart(wahl)) {
//      MasterElectTreeReader masterElectTreeReader = new MasterElectTreeReader();
//      RegionFiltered holland = masterElectTreeReader.findeGebieteZurAuswahl(ElectionCategoryType.TK, CommitteeCategoryType.CSB).get(0);
//
//      if (NlWahlart.isWasPSB(wahl, currentCustomer.getCustomer())) {
//        // wurzelgebiet = gemeinde
//        // wahlkreiseInEml.size() == 1
//        for (Gebiet wahlkreis : wahlkreiseInEml) {
//          RegionFiltered wahlkreisInMasterElectionTree = findeWahlkreisInMasterElectionTree(holland, wahlkreis);
//          if (!gemeindeInChildren(wahlkreisInMasterElectionTree, wurzelgebiet)) {
//            return false;
//          }
//        }
//      }
//      if (NlWahlart.isWasHSB(wahl, currentCustomer.getCustomer())) {
//        // wurzelgebiet = wahlkreis
//        // wahlkreiseInEml.size() == 1
//        for (Gebiet wahlkreis : wahlkreiseInEml) {
//          RegionFiltered wahlkreisInMasterElectionTree = findeWahlkreisInMasterElectionTree(holland, wahlkreis);
//          if (!isStimmtUeberein(wahlkreisInMasterElectionTree, wurzelgebiet)) {
//            return false;
//          }
//        }
//      }
//    }
//
//    return true; // erstmal ist alles gut

  }

  protected boolean isUebergeordnet(Gebiet wahlkreis, Gebiet wurzelgebiet) {
    return isNameNummerGleich(wahlkreis, wurzelgebiet) || isInMasterElectionTreeVorhanden(wahlkreis, wurzelgebiet);

  }

  private boolean isInMasterElectionTreeVorhanden(Gebiet wahlkreis, Gebiet wurzelgebiet) {
    return wurzelgebiet.getCustomData(NlGebietCustomData.class).getUebergeordneteGebieteInMasterElectionTree().stream()
        .anyMatch(nlGebietSchluessel -> isNameNummerGleich(wahlkreis, nlGebietSchluessel));
  }

  private boolean isNameNummerGleich(Gebiet wahlkreis, NlGebietSchluessel nlGebietSchluessel) {
    return isGleich(wahlkreis.getName(), nlGebietSchluessel.getName()) && (isGleich(wahlkreis.getNummer(), nlGebietSchluessel.getNummer()) || isLongValueGleich(wahlkreis.getNummer(), nlGebietSchluessel.getNummer()));
  }

  private boolean isNameNummerGleich(Gebiet wahlkreis, Gebiet wurzelgebiet) {
    return isGleich(wahlkreis.getName(), wurzelgebiet.getName()) && (isGleich(wahlkreis.getNummer(), wurzelgebiet.getNummer()) || isLongValueGleich(wahlkreis.getNummer(), wurzelgebiet.getNummer()) );
  }

//  private boolean gemeindeInChildren(RegionFiltered wahlkreisInMasterElectionTree, Gebiet wurzelgebiet) {
//    if (wahlkreisInMasterElectionTree == null) {
//      return false;
//    }
//    return wahlkreisInMasterElectionTree.getChildren().stream().anyMatch(child -> isStimmtUeberein(child, wurzelgebiet));
//  }
//
//  private boolean isStimmtUeberein(RegionFiltered wahlkreisInMasterElectionTree, Gebiet gebiet) {
//    if (wahlkreisInMasterElectionTree == null) {
//      return false;
//    }
//    return isGleich(wahlkreisInMasterElectionTree.getName(), gebiet.getName()) && isNummerGleich(wahlkreisInMasterElectionTree.getNummer(), gebiet.getNummer());
//  }

  private boolean isNummerGleich(Short one, String other) {
    if (one == null || other == null) {
      return false;
    }
    if (isLongValueGleich(one.toString(), other)) {
      return true;
    }
    return other.equals(one.toString());
  }

  private boolean isLongValueGleich(String nummerA, String nummerB) {
    if (StringUtils.isBlank(nummerA) || StringUtils.isBlank(nummerB)) {
      return false;
    }
    try {
      return Long.parseLong(nummerA) == Long.parseLong(nummerB);
    } catch (Exception e) {
      return false;
    }
  }

  private boolean isGleich(String one, String other) {
    return Objects.equals(one, other);
  }

//  @Nullable
//  protected RegionFiltered findeWahlkreisInMasterElectionTree(RegionFiltered holland, Gebiet wahlkreis) {
//    return holland.getChildren().stream().filter(wahlkreisInMet -> isStimmtUeberein(wahlkreisInMet, wahlkreis)).findFirst().orElse(null);
//
//  }

  private List<Gebiet> bestimmeWahlgebiete(Eml230Wrapper eml230Wrapper, Wahl wahl) {
    Gebiet oberstersGebiet = eml230Mapper.mappeOberstesGebiet(eml230Wrapper.getOrCreateElectionDomain(), wahl);
    if (isGueltigerWahlkreis(oberstersGebiet)) {
      return Collections.singletonList(oberstersGebiet);
    }

    return eml230Wrapper.getContest().stream().map(eml230ContestWrapper -> eml230Mapper.mappeGebiet(wahl, eml230ContestWrapper))
        .filter(this::isGueltigerWahlkreis)
        .collect(Collectors.toList());

  }

  private boolean isGueltigerWahlkreis(Gebiet oberstersGebiet) {
    return oberstersGebiet != null && !"geen".equalsIgnoreCase(oberstersGebiet.getNummer()) && !"alle".equalsIgnoreCase(oberstersGebiet.getNummer());
  }

  protected abstract String getExpectedEmlId();

  protected abstract String getWrongEmlIdMessageKey();

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(getFormatName(), FileType.XML);
  }

  protected abstract String getFormatName();

  @Override
  public boolean isAcceptContext(WahlvorschlagImportContext context) {
    return context.getWahl() != null && isAcceptInternal(context.getWahl()) && !isBackupRestoreEnabled(context.getWahl());
  }

  protected abstract boolean isAcceptInternal(Wahl wahl);

  @Override
  public void addProperties(FormBuilder formBuilder, WahlvorschlagImportContext context, @Nullable Datei datei) {
    if (datei != null) {
      try {
        try (InputStream is = emlImportMapperHelper.getInputStream(datei)) {
          EmlWrapper emlWrapper = emlImportMapperHelper.readEmlHash(is, emlImportMapperHelper.getHashCodeProperty(formBuilder, context,
              eml230Mapper -> bestimmeEmlValidation(context, eml230Mapper)));
          context.put(EML_KEY, emlWrapper);
        }
      } catch (Exception e) {
        getLog().warn("Fehler beim Lesen der Datei: " + datei + ", " + e.getMessage());
      }
    }
  }

  protected abstract EmlValidation bestimmeEmlValidation(WahlvorschlagImportContext context, EmlWrapper eml230Mapper);

  protected abstract Logger getLog();

}
