
package de.ivu.elect.business.personenregister.boundary;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import javax.inject.Inject;

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

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.anschrift.entity.AnschriftArt;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcher;
import de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute;
import de.ivu.elect.business.personenregister.entity.PersonenregisterAnschrift;
import de.ivu.elect.business.personenregister.entity.PersonenregisterEinwohnerstatus;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendaten;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendatenAbweichung;
import de.ivu.elect.jee.faces.Messages;

public class PersonenregisterPersonendatenAbweichungCollector {

  private static final Logger LOGGER = LoggerFactory.getLogger(PersonenregisterPersonendatenAbweichungCollector.class);

  @Inject
  private transient Messages messages;

  @Inject
  private transient PersonendatenDAO personendatenDao;

  @Inject
  private transient PersonenregisterSwitch personenregisterSwitch;

  @Inject
  private transient PersonenregisterByOrdnungsmerkmalSearchParameters personenregisterByOrdnungsmerkmalSearchParameters;

  @Inject
  private transient PersonendatenPersonenregisterPersonendatenMatcherResolver personendatenPersonenregisterPersonendatenMatcherResolver;

  public PersonenregisterPersonendatenAbweichungCollector() {
    super();
  }

  public <E extends Personendaten> Map<E, List<PersonenregisterPersonendatenAbweichung>> collectPersonenregisterPersonendatenAbweichungen(List<E> personendaten) {
    if (personendaten.isEmpty()) {
      return Collections.emptyMap();
    }

    Map<E, List<PersonenregisterPersonendatenAbweichung>> result = new HashMap<>();
    if (isPersonenregisterErreichbarAndSearchByOrdnungsmerkmalSupported(personendaten.get(0).getMandant())) {
      personendaten.forEach(person -> {
        E personendatenInDb = attach(person);

        findPersonenregisterPersonendaten(personendatenInDb).ifPresent(personendatenInPersonenregister -> {
          List<PersonenregisterPersonendatenAbweichung> personenregisterPersonendatenAbweichungen = collectPersonenregisterPersonendatenAbweichungen(personendatenInDb,
              personendatenInPersonenregister);

          if (!personenregisterPersonendatenAbweichungen.isEmpty()) {
            result.put(personendatenInDb, personenregisterPersonendatenAbweichungen);
          }
        });
      });
    }

    return result;
  }

  protected List<PersonenregisterPersonendatenAbweichung> collectPersonenregisterPersonendatenAbweichungen(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    List<PersonenregisterPersonendatenAbweichung> result = new ArrayList<>();

    PersonenregisterPersonendatenMatcher personendatenPersonenregisterPersonendatenMatcher = getPersonendatenPersonenregisterPersonendatenMatcher(personendaten);

    if (!personendatenPersonenregisterPersonendatenMatcher.matchTitel(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.TITEL, personendaten.getTitel(), personenregisterPersonendaten.getTitel()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchFamiliennameBestandteil(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.FAMILIENNAME_BESTANDTEIL, personendaten.getFamiliennameBestandteil(),
          personenregisterPersonendaten.getFamiliennameBestandteil()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchFamilienname(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(
          createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.FAMILIENNAME, personendaten.getFamilienname(), personenregisterPersonendaten.getFamilienname()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchVorname(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.VORNAME, personendaten.getRufname(), personenregisterPersonendaten.getRufname()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchKuenstlername(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.KUENSTLERNAME, personendaten.getKuenstlername(),
          personenregisterPersonendaten.getKuenstlername()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchOrdensname(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ORDENSNAME, personendaten.getOrdensname(), personenregisterPersonendaten.getOrdensname()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchGeburtsdatum(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(
          createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.GEBURTSDATUM, personendaten.getGeburtsdatum(), personenregisterPersonendaten.getGeburtsdatum()));
    }

    if (!personendatenPersonenregisterPersonendatenMatcher.matchGeschlecht(personendaten, personenregisterPersonendaten).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.GESCHLECHT, personendaten.getGeschlecht(), personenregisterPersonendaten.getGeschlecht()));
    }

    Anschrift anschrift = personendaten.getAnschrift();
    PersonenregisterAnschrift personenregisterAnschrift = personenregisterPersonendaten.getAnschrift();

    if (!personendatenPersonenregisterPersonendatenMatcher.matchAnschriftArt(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
      result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ANSCHRIFT_ART, anschrift.getAnschriftArt(),
          personenregisterAnschrift.getAnschriftArt()));
    }

    if (!AnschriftArt.ERREICHBARKEITSANSCHRIFT.equals(anschrift.getAnschriftArt())) {
      if (!personendatenPersonenregisterPersonendatenMatcher.matchStrasse(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_STRASSE, anschrift.getStrasse(), personenregisterAnschrift.getStrasse()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchHausnummer(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(
            createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_HAUSNUMMER, anschrift.getHausnummer(), personenregisterAnschrift.getHausnummer()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchHausnummerzusatz(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_HAUSNUMMERZUSATZ, anschrift.getHausnummerzusatz(),
            personenregisterAnschrift.getHausnummerzusatz()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchWohnungsnummer(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_WOHNUNGSNUMMER, anschrift.getWohnungsnummer(),
            personenregisterAnschrift.getWohnungsnummer()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchPlz(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_PLZ, anschrift.getPlz(), personenregisterAnschrift.getPlz()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchOrt(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ORT, anschrift.getOrt(), personenregisterAnschrift.getOrt()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchOrtsteil(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ORTSTEIL, anschrift.getOrtsteil(), personenregisterAnschrift.getOrtsteil()));
      }

      if (!personendatenPersonenregisterPersonendatenMatcher.matchZustellanweisung(personendaten, anschrift, personenregisterAnschrift).isValueIdentisch()) {
        result.add(createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ZUSTELLANWEISUNG, anschrift.getZustellanweisung(),
            personenregisterAnschrift.getZustellanweisung()));
      }
    }

    return result;
  }

  private PersonenregisterPersonendatenAbweichung createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute attribute, Enum< ? > attributeValueInDb,
      Enum< ? > attributeValueInPersonenregister) {
    return createPersonenregisterPersonendatenAbweichung(attribute, getMessage(attributeValueInDb), getMessage(attributeValueInPersonenregister));
  }

  private PersonenregisterPersonendatenAbweichung createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute attribute, Date attributeValueInDb,
      Date attributeValueInPersonenregister) {
    return new PersonenregisterPersonendatenAbweichung(attribute, attributeValueInDb, attributeValueInPersonenregister);
  }

  private PersonenregisterPersonendatenAbweichung createPersonenregisterPersonendatenAbweichung(PersonenregisterPersonendatenMatcherAttribute attribute, String attributeValueInDb,
      String attributeValueInPersonenregister) {
    return new PersonenregisterPersonendatenAbweichung(attribute, attributeValueInDb, attributeValueInPersonenregister);
  }

  private boolean isPersonenregisterErreichbarAndSearchByOrdnungsmerkmalSupported(Mandant mandant) {
    return personenregisterSwitch.isPersonenregisterAvailable(mandant) && getPersonenregister(mandant).isErreichbar(mandant)
        && getPersonenregister(mandant).isSearchByOrdnungsmerkmalAutomatischSupported(mandant);
  }

  private Personenregister< ? > getPersonenregister(Mandant mandant) {
    return personenregisterSwitch.getPersonenregister(mandant);
  }

  protected PersonenregisterPersonendatenMatcher getPersonendatenPersonenregisterPersonendatenMatcher(Personendaten personendaten) {
    return personendatenPersonenregisterPersonendatenMatcherResolver.getMatcher(personendaten);
  }

  private Optional<PersonenregisterPersonendaten> findPersonenregisterPersonendaten(Personendaten personendaten) {
    if (personendaten.hasOrdnungsmerkmal()) {
      Mandant mandant = personendaten.getMandant();
      UUID requestUUID = UUID.randomUUID();

      personenregisterByOrdnungsmerkmalSearchParameters.setWahltermin(personendaten.getWahltermin());
      personenregisterByOrdnungsmerkmalSearchParameters.setEinwohnerstatus(PersonenregisterEinwohnerstatus.AKTIV);
      personenregisterByOrdnungsmerkmalSearchParameters.setOrdnungsmerkmal(personendaten.getOrdnungsmerkmal());

      PersonenregisterSearchResult< ? > personenregisterSearchResult = getPersonenregister(mandant).search(mandant, requestUUID, personenregisterByOrdnungsmerkmalSearchParameters);

      if (personenregisterSearchResult.hasException()) {
        LOGGER.warn("Fehler beim Zugriff auf das Personenregister [mandant = " + mandant + ", requestUUID = " + requestUUID + ", personendaten = " + personendaten + "]",
            personenregisterSearchResult.getException());
      } else if (personenregisterSearchResult.hasUniquePersonenregisterPersonendaten()) {
        return Optional.ofNullable(personenregisterSearchResult.getUniquePersonenregisterPersonendaten());
      }
    }

    return Optional.empty();
  }

  private <E extends Personendaten> E attach(E persondaten) {
    return (E) personendatenDao.attach(persondaten);
  }

  private String getMessage(Enum< ? > value) {
    return getMessages().lokalisiere(value);
  }

  protected Messages getMessages() {
    return messages;
  }
}
