package de.ivu.elect.was.sitzberechnung.control;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.inject.Inject;

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

import de.ivu.elect.business.ergebnis.entity.ErgebnisKonfliktArt;
import de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyArtikelnummer;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyErgebnisKonfliktArt;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyKonfliktCustomData;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyKonfliktLoesung;
import de.ivu.elect.business.sitzberechnung.entity.OsvLegacyStel;
import de.ivu.elect.business.sitzberechnung.entity.P2Liste;
import de.ivu.elect.business.wahl.entity.WahlartSitzberechnungsalgorithmus;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.sitzberechnung.SitzberechnungEntity;
import de.ivu.elect.sitzberechnung.osv.exchange.konflikt.OSVLegacyKonflikt;
import de.ivu.elect.sitzberechnung.osv.exchange.konflikt.OSVLegacyKonfliktType;
import de.ivu.elect.was.sitzberechnung.entity.AbstractElectSitzberechnungEntity;
import de.ivu.elect.was.sitzberechnung.entity.AbstractElectSitzberechnungKandidatur;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungEntities;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungGruppe;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListe;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListenkandidatur;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungStel;

public class OsvLegacyKonfliktMapper implements ElectErgebnisKonfliktMapper {

  private static final Logger LOG = LoggerFactory.getLogger(OsvLegacyKonfliktMapper.class);
  @Inject
  transient Messages messages;

  @Override
  public de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapExtern(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities, ErgebnisKonflikt ergebnisKonflikt) {
    de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt result = mapExternInternal(sitzberechnungEntities, ergebnisKonflikt);

    if (result instanceof de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) {
      ((de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) result).setErgebnisKonfliktPersistent(ergebnisKonflikt);
    }

    return result;
  }

  private de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt mapExternInternal(ElectSitzberechnungEntities sitzberechnungEntities, ErgebnisKonflikt ergebnisKonflikt) {
    OsvLegacyKonfliktCustomData customData = ergebnisKonflikt.getCustomData(OsvLegacyKonfliktCustomData.class);
    OSVLegacyKonfliktType osvLegacyKonfliktType = mappeOsvLegacyErgebnisKonfliktArt(customData.getOsvLegacyErgebnisKonfliktArt());

    List<SitzberechnungEntity> sitzberechnungEntitaetenZurAuswahl = new ArrayList<>();

    if (osvLegacyKonfliktType.isKandidatenEntscheidung()) {
      mappeListenkandidaturenZurAuswahl(sitzberechnungEntities.getKandidaturen(), sitzberechnungEntitaetenZurAuswahl, customData.getListenkandidaturenZurAuswahl());
    } else if (osvLegacyKonfliktType.isP2ListenEntscheidung()) {
      mappeP2ListenZurAuswahl(sitzberechnungEntities.getListen(), sitzberechnungEntitaetenZurAuswahl, customData.getP2ListeZurAuswahl());
    } else if (osvLegacyKonfliktType.isP3ListenEntscheidung()) {
      mappeP3ListenZurAuswahl(sitzberechnungEntities.getGruppen(), sitzberechnungEntitaetenZurAuswahl, customData.getGruppenZurAuswahl());
    }

    OSVLegacyKonflikt osvLegacyKonflikt = OSVLegacyKonflikt.createUngeloestenOSVLegacyKonflikt(customData.getPosition(),
            sitzberechnungEntitaetenZurAuswahl, osvLegacyKonfliktType);

    if (ergebnisKonflikt.isGeloest()) {
      SitzberechnungEntity geloesteEntitaet = mappeGeloesteEntitaet(customData.getKonfliktLoesung(), sitzberechnungEntitaetenZurAuswahl);
      osvLegacyKonflikt = OSVLegacyKonflikt.createGelostenOSVLegacyKonflikt(osvLegacyKonflikt, geloesteEntitaet);
    }

    return osvLegacyKonflikt;
  }

  @Override
  public ErgebnisKonflikt mapIntern(ElectSitzberechnungContext sitzberechnungContext, ElectSitzberechnungEntities sitzberechnungEntities, de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt ergebnisKonflikt) {
    if (ergebnisKonflikt instanceof de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) {
      Object ergebnisKonfliktPersistent = ((de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt) ergebnisKonflikt).getErgebnisKonfliktPersistent();

      if (ergebnisKonfliktPersistent instanceof ErgebnisKonflikt) {
        return (ErgebnisKonflikt) ergebnisKonfliktPersistent;
      }
    }

    return mapInternInternal(sitzberechnungContext, ergebnisKonflikt);
  }

  private ErgebnisKonflikt mapInternInternal(ElectSitzberechnungContext sitzberechnungContext, de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt ergebnisKonflikt) {
    OSVLegacyKonflikt osvLegacyKonflikt = (OSVLegacyKonflikt) ergebnisKonflikt;
    OSVLegacyKonfliktType osvLegacyKonfliktType = osvLegacyKonflikt.getOSVLegacyKonfliktType();
    OsvLegacyKonfliktCustomData customData = new OsvLegacyKonfliktCustomData(mappeOsvLegacyErgebnisKonfliktArt(osvLegacyKonfliktType),
            osvLegacyKonflikt.getPosition(), getArtikelnummer(osvLegacyKonfliktType));


    List<SitzberechnungEntity> entitaetenZurAuswahl = osvLegacyKonflikt.getEntitaetenZurAuswahl();

    if (osvLegacyKonfliktType.isP3ListenEntscheidung()) {
      addGruppenZuCustomData(customData, entitaetenZurAuswahl, osvLegacyKonflikt.getGeloesteEntitaet());
    } else if (osvLegacyKonfliktType.isP2ListenEntscheidung()) {
      addP2ListenZuCustomData(customData, entitaetenZurAuswahl, osvLegacyKonflikt.getGeloesteEntitaet());
    } else if (osvLegacyKonfliktType.isKandidatenEntscheidung()) {
      addKandidaturenZuCustomData(customData, entitaetenZurAuswahl, osvLegacyKonflikt.getGeloesteEntitaet());
    }

    ErgebnisKonflikt zielKonflikt = new ErgebnisKonflikt(customData);
    zielKonflikt.setErgebnisKonfliktArt(ErgebnisKonfliktArt.NL_CUSTOM);
    zielKonflikt.setKonfliktBeschreibung(getKonfliktBeschreibung(customData.getOsvLegacyErgebnisKonfliktArt(), sitzberechnungContext.getWahlartSitzberechnungsalgorithmus()));

    return zielKonflikt;
  }

  protected OsvLegacyArtikelnummer getArtikelnummer(OSVLegacyKonfliktType osvLegacyKonfliktType) {
    return OsvLegacyArtikelnummer.findeArtikelnummer(osvLegacyKonfliktType.getArtikelnummer());
  }

  String getKonfliktBeschreibung(OsvLegacyErgebnisKonfliktArt osvLegacyErgebnisKonfliktArt, WahlartSitzberechnungsalgorithmus wahlartSitzberechnungsalgorithmus) {
    String message = messages.getMessageForMessageKey(osvLegacyErgebnisKonfliktArt.name() + "_TEXT");
    if (OsvLegacyErgebnisKonfliktArt.CANDIDATES.equals(osvLegacyErgebnisKonfliktArt)) {
      if (wahlartSitzberechnungsalgorithmus.equals(WahlartSitzberechnungsalgorithmus.NL_EP)) {
        message += "\n" + messages.getMessageForMessageKey("CANDIDATES_TEXT_APPENDIX_EP");
      } else if (WahlartSitzberechnungsalgorithmus.NL_GR1.equals(wahlartSitzberechnungsalgorithmus)) {
        message += "\n" + messages.getMessageForMessageKey("CANDIDATES_TEXT_APPENDIX_GR1");
      }
    }
    message += "\n" + messages.getMessageForMessageKey(osvLegacyErgebnisKonfliktArt.name() + "_MESSAGE");
    return message;
  }

  private void addGruppenZuCustomData(OsvLegacyKonfliktCustomData customData, List<SitzberechnungEntity> entitaetenZurAuswahl, SitzberechnungEntity geloesteEntitaet) {
    entitaetenZurAuswahl
            .forEach(e -> customData.addGruppeZurAuswahl(((ElectSitzberechnungGruppe) e).getEntity().getPk()));
    if (geloesteEntitaet instanceof ElectSitzberechnungGruppe) {
      customData.setKonfliktLoesung(new OsvLegacyKonfliktLoesung(((ElectSitzberechnungGruppe) geloesteEntitaet).getEntity().getPk()));
    }
  }

  private void addKandidaturenZuCustomData(OsvLegacyKonfliktCustomData customData, List<SitzberechnungEntity> entitaetenZurAuswahl, SitzberechnungEntity geloesteEntitaet) {
    entitaetenZurAuswahl.forEach(e -> customData.addListenkandidaturZurAuswahl(((ElectSitzberechnungListenkandidatur) e).getEntity().getPk()));
    if (geloesteEntitaet instanceof ElectSitzberechnungListenkandidatur) {
      customData.setKonfliktLoesung(new OsvLegacyKonfliktLoesung(((ElectSitzberechnungListenkandidatur) geloesteEntitaet).getEntity().getPk()));
    }
  }

  private void addP2ListenZuCustomData(OsvLegacyKonfliktCustomData customData, List<SitzberechnungEntity> entitaetenZurAuswahl, SitzberechnungEntity geloesteEntitaet) {
    Map<String, OsvLegacyStel> osvLegacyStelMap = new HashMap<>();
    for (SitzberechnungEntity sitzberechnungEntity : entitaetenZurAuswahl) {
      if (sitzberechnungEntity instanceof SitzberechnungStel) {
        SitzberechnungStel stel = (SitzberechnungStel) sitzberechnungEntity;
        OsvLegacyStel osvLegacyStel = new OsvLegacyStel(stel.getListenDesStels().stream().map(Wahlvorschlag::getPk).collect(Collectors.toSet()));
        customData.addStelZurAuswahl(osvLegacyStel);
        osvLegacyStelMap.put(stel.getKey(), osvLegacyStel);
      } else if (sitzberechnungEntity instanceof ElectSitzberechnungListe) {
        customData.addListeZurAuswahl(((ElectSitzberechnungListe) sitzberechnungEntity).getEntity().getPk());
      }
    }

    if (geloesteEntitaet instanceof SitzberechnungStel) {
      customData.setKonfliktLoesung(new OsvLegacyKonfliktLoesung(osvLegacyStelMap.get(geloesteEntitaet.getKey())));
    } else if (geloesteEntitaet instanceof ElectSitzberechnungListe) {
      customData.setKonfliktLoesung(new OsvLegacyKonfliktLoesung(((ElectSitzberechnungListe) geloesteEntitaet).getEntity().getPk()));
    }

  }

  private void mappeP3ListenZurAuswahl(Collection<ElectSitzberechnungGruppe> gruppen, List<SitzberechnungEntity> sitzberechnungEntitaetenZurAuswahl, List<GruppePK> gruppenZurAuswahl) {
    sitzberechnungEntitaetenZurAuswahl.addAll(gruppenZurAuswahl.stream()
            .map(pk -> gruppen.stream()
                    .filter(g -> g.getKey().equals(pk.toString()))
                    .findAny().orElse(null))
            .collect(Collectors.toList()));
  }

  private void mappeP2ListenZurAuswahl(Collection<ElectSitzberechnungListe> listen, List<SitzberechnungEntity> sitzberechnungEntitaetenZurAuswahl, List<P2Liste> p2ListeZurAuswahl) {
    sitzberechnungEntitaetenZurAuswahl.addAll(p2ListeZurAuswahl.stream()
            .map(mappeP2Liste(listen))
            .collect(Collectors.toList()));
  }

  private void mappeListenkandidaturenZurAuswahl(Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturen, List<SitzberechnungEntity> sitzberechnungEntitaetenZurAuswahl, List<PersonendatenPK> listenkandidaturenZurAuswahl) {
    sitzberechnungEntitaetenZurAuswahl.addAll(listenkandidaturenZurAuswahl.stream()
            .map(pk -> kandidaturen.stream()
                    .filter(k -> k.getKey().equals(pk.toString()))
                    .findAny().orElse(null))
            .collect(Collectors.toList()));
  }


  private Function<P2Liste, SitzberechnungEntity> mappeP2Liste(Collection<ElectSitzberechnungListe> alleListen) {
    return p2Liste -> p2Liste.isStel() ?
            mappeSitzberechnungStel(alleListen, p2Liste.getStel()) : mappeSitzberechnungListe(alleListen, p2Liste.getListe());
  }

  private ElectSitzberechnungListe mappeSitzberechnungListe(Collection<ElectSitzberechnungListe> alleListen, WahlvorschlagPK pk) {
    return alleListen.stream()
            .filter(l -> l.getEntity().getPk().equals(pk))
            .findAny().orElseThrow(NullPointerException::new);
  }

  private SitzberechnungStel mappeSitzberechnungStel(Collection<ElectSitzberechnungListe> alleListen, OsvLegacyStel stel) {
    List<Liste> listenDesStels = stel.getListenDesStels().stream()
            .map(pk -> alleListen.stream()
                    .map(AbstractElectSitzberechnungEntity::getEntity)
                    .filter(l -> l.getPk().equals(pk))
                    .findAny().orElseThrow(NullPointerException::new))
            .collect(Collectors.toList());

    Liste listeDesStels = listenDesStels.iterator().next();

    return new SitzberechnungStel(listeDesStels.getGruppe(), NlListe.getStelNummerWas(listeDesStels).orElse(null), listenDesStels);
  }

  private SitzberechnungEntity mappeGeloesteEntitaet(OsvLegacyKonfliktLoesung konfliktLoesung, Collection<SitzberechnungEntity> entitatenZurAuswahl) {
    Optional<SitzberechnungEntity> geloesteEntitaet = Optional.empty();

    if (konfliktLoesung.isStelEntscheidung()) {
      geloesteEntitaet = entitatenZurAuswahl.stream()
              .filter(se -> se instanceof SitzberechnungStel && ((SitzberechnungStel) se).getListenDesStels().stream()
                      .anyMatch(liste -> konfliktLoesung.getStel().getListenDesStels().contains(liste.getPk())))
              .findAny();
    } else if (konfliktLoesung.isListeEntscheidung()) {
      geloesteEntitaet = entitatenZurAuswahl.stream()
              .filter(se -> se.getKey().equals(konfliktLoesung.getListe().toString()))
              .findAny();
    } else if (konfliktLoesung.isGruppeEntscheidung()) {
      geloesteEntitaet = entitatenZurAuswahl.stream()
              .filter(se -> se.getKey().equals(konfliktLoesung.getGruppe().toString()))
              .findAny();
    } else if (konfliktLoesung.isListenKandidaturEntscheidung()) {
      geloesteEntitaet = entitatenZurAuswahl.stream()
              .filter(se -> se.getKey().equals(konfliktLoesung.getListenkandidatur().toString()))
              .findAny();
    }

    if (geloesteEntitaet.isPresent()) {
      return geloesteEntitaet.get();
    }

    LOG.warn("Die gelöste Entität wurde in der Auswahl nicht gefunden! " + konfliktLoesung + entitatenZurAuswahl);
    return null;
  }


  protected OsvLegacyErgebnisKonfliktArt mappeOsvLegacyErgebnisKonfliktArt(OSVLegacyKonfliktType konfliktType) {
    try {
      return OsvLegacyErgebnisKonfliktArt.valueOf(konfliktType.name());
    } catch (IllegalArgumentException illegalArgumentException) {
      throw new IllegalStateException("Unbekannter KonfliktType: " + konfliktType);
    }
  }

  protected OSVLegacyKonfliktType mappeOsvLegacyErgebnisKonfliktArt(OsvLegacyErgebnisKonfliktArt konfliktArt) {
    try {
      return OSVLegacyKonfliktType.valueOf(konfliktArt.name());
    } catch (IllegalArgumentException illegalArgumentException) {
      throw new IllegalStateException("Unbekannter KonfliktType: " + konfliktArt);
    }
  }

}
