package com.alexanderlogistics;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.plugins.AbstractPlugin;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;

/**
 * Das InvoicePlugin prüft die Eingaben auf gültigkeit, so dass diese problemlos
 * nach Cargosoft expoertier werden können.
 * <p>
 * Konkret geht es darum, das im Status 5200 in den ChildItems keine leeren
 * Zeilen vorkommen dürfen.
 * <p>
 * Im Status 5000 muss immer der payment.type ausgewählt werden. Dies wird vom
 * Plugin explizit geprüft
 * <p>
 * Das Plugin prüft auch auf doppelte Rechnungsnummern. Dabei wird Systemweit
 * geprüft ob die Rechnungsnummer schon einmal vorkam. Falls ja, wird eine
 * Warnung ausgegeben. Der Benutzer kann diese dann skippen.
 * <p>
 * 2.2.22 - Das Plugin erzeugt auch das Feld _img welches die Images von
 * Sofortüberweisung, Mahnung und Ablehnung anzeigt.
 * 
 * 
 * @author rsoika
 * @version 1.0
 * 
 */
public class InvoicePlugin extends AbstractPlugin {

	public static final int TASK_ERFASSUNG = 5001;
	public static final int TASK_SACHPRUEFUNG = 5200;
	public static final int TASK_SACHPRUEFUNG_DELEGATION = 5210;
	public static final int EVENT_FREIGEBEN = 20;
	public static final String ITEM_INVOICE_PERIOD = "invoice.period";

	public static final String CHILD_ITEM_PROPERTY = "_ChildItems";
	public static final String ERROR_MISSING_DATA = "MISSING_DATA";
	public static final String ERROR_DUPPLICATE_INVOICE_NUMBER = "DUPPLICATE_INVOICE_NUMBER";
	public static final String ERROR_NEW_IBANBIC = "NEW_IBANBIC";

	// XX-YYY-####-###
	public static final String REGEX_POSNUMER = "([A-Z]{2}-[A-Z]{3}-[0-9]{4}-[0-9]{3})";

	private static Logger logger = Logger.getLogger(InvoicePlugin.class.getName());

	@Inject
	KreditorDebitorService kreditorService;

	/**
	 * Test childworkitems for empty lines
	 * 
	 * @throws PluginException - if data is missing
	 * 
	 **/
	@Override
	public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException {

		updateImg(workitem);

		// skip if validaten tag is required=false
		ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(event, "validation", workitem);
		if (evalItemCollection != null) {
			// evaluate the validation rules...
			if ("false".equalsIgnoreCase(evalItemCollection.getItemValueString("required"))) {
				return workitem;
			}
		}

		boolean isPublicEvent = !("0".equals(event.getItemValueString("keypublicresult")));

		// Payment.type muss immer eingetragne werden!
		if (isPublicEvent && workitem.getTaskID() >= TASK_ERFASSUNG && workitem.getEventID() < 900) {
			if (workitem.getItemValueString("payment.type").trim().isEmpty()) {
				// throw a plugin exception!
				throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
						"Eingabefehler - Bitte wählen Sie eine Zahlungsart aus!");
			}
			// doppelte Rechnungsnummer prüfen
			if (workitem.getTaskID() == TASK_ERFASSUNG) {
				validateInvoiceNumber(workitem);

				checkIBANNumber(workitem);

			}

			if (workitem.getTaskID() == TASK_ERFASSUNG || ((workitem.getTaskID() == TASK_SACHPRUEFUNG
					|| workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION)
					&& workitem.getEventID() == EVENT_FREIGEBEN)) {
				// Buchungsperiode auf plausi prüfen
				if (isCargoRechnung(workitem) && "workitem".equals(workitem.getType())) {
					validateBuchungsperiode(workitem.getItemValueString(ITEM_INVOICE_PERIOD));
				}
				// Cargosoft Kreditorennnummer prüfen
				if (isCargoRechnung(workitem)) {
					validateCargosoftCdtrNumber(workitem);
				}
			}
		}

		// die prüfung der positionsnummern und Category erfolgt nur im Status
		// Sachprüfung (5200) und nur beim Freigeben (20)!
		if (isCargoRechnung(workitem)
				&& ((workitem.getTaskID() == TASK_SACHPRUEFUNG || workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION)
						&& workitem.getEventID() == EVENT_FREIGEBEN)) {
			List<ItemCollection> childs = explodeChildList(workitem);
			for (ItemCollection posItem : childs) {
				if (posItem.getItemValueString("name").trim().isEmpty()) {
					// throw a plugin exception - because name is missing!
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Die Positionsnummer in Zeile " + posItem.getItemValueString("numpos")
									+ " muss ausgefüllt sein!");
				}

				// validate pos for regex pattern 'XX-YYY-####-###'
				if (!posItem.getItemValueString("name").matches(REGEX_POSNUMER)) {
					// throw a plugin exception - because name is missing!
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Die Positionsnummer in Zeile " + posItem.getItemValueString("numpos")
									+ " muss im Format 'XX-YYY-####-###' eingegeben werden!");
				}

				if (posItem.getItemValueString("category").trim().isEmpty()) {
					// throw a plugin exception - because name is missing!
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Die Leistungsart in Zeile " + posItem.getItemValueString("numpos")
									+ " muss ausgefüllt sein!");
				}

				if (posItem.getItemValueString("tax").trim().isEmpty()) {
					// throw a plugin exception - because name is missing!
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Die Steuer in Zeile " + posItem.getItemValueString("numpos")
									+ " muss ausgefüllt sein!");
				}

				if (posItem.getItemValueFloat("amount") == 0) {
					// throw a plugin exception - because name is missing!
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Der Nettobetrag in Zeile " + posItem.getItemValueString("numpos")
									+ " darf nicht 0 sein!");
				}

				// Buchunsperiode
				if (!posItem.getItemValueString(ITEM_INVOICE_PERIOD).trim().isEmpty()) {
					validateBuchungsperiode(posItem.getItemValueString(ITEM_INVOICE_PERIOD));

				}
			}

			// Ergaenzung 18.05.2021:
			// es kann vorkommen, das die Anwender abweichende Buchungsperioden eingeben so
			// das die Hauptbuchungsperiode gar ncht merh vorkommt. Das darf aber nicht der
			// fall sein.
			// Im folgenden überprüfen wir ob eine Buchungszeile vorkommt in der keine oder
			// die Hauptbuchunsperiode ausgewählt wurde. Ist das nicht der Fal gibt es eine
			// Fehlermeldung für den Anwendere
			if (isCargoRechnung(workitem) && childs.size() > 0) {
				String hauptBuchungsperiode = workitem.getItemValueString("invoice.period");
				boolean buchungsperiodenValid = false;
				for (ItemCollection posItem : childs) {
					String posBuchungsperiode = posItem.getItemValueString("invoice.period");
					if (posBuchungsperiode.isEmpty() || posBuchungsperiode.equals(hauptBuchungsperiode)) {
						// alles fein!
						buchungsperiodenValid = true;
					}
				}
				if (buchungsperiodenValid == false) {
					// fehlerhafte Buchungsperioden.
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
							"Eingabefehler - Bitte überprüfen Sie die Hauptbuchungsperiode mit den Buchungsperioden der einzelnen Positionen!");
				}
			}

		}

		return workitem;
	}

	/**
	 * Hilfsroutine die das _img item aktualisiert.
	 */
	private void updateImg(ItemCollection workitem) {

		// Update _img icon list
		String img = workitem.getItemValueString("_img");
		// 031=ablehnen
		// 100=sofortüberweichung
		// 074=mahnen
		// sofortüberweisung
		if (workitem.getEventID() == 96 && !img.contains("100.png")) {
			img = img + "<img title='Sofortüberweisung'  src=\"/layout/icons/icon100.png\">";
		}
		// mahnen
		if (workitem.getEventID() == 95 && !img.contains("103.png")) {
			img = img + "<img title='Mahnen' src=\"/layout/icons/icon103.png\">";

		}
		// ablehnen
		if (workitem.getTaskID() == 5100 && workitem.getEventID() == 90 && !img.contains("028.png")) {
			img = img + "<img  title='Abgelehnt'  src=\"/layout/icons/icon028.png\">";
		}
		if (workitem.getTaskID() == 5200 && workitem.getEventID() == 90 && !img.contains("028.png")) {
			img = img + "<img  title='Abgelehnt'  src=\"/layout/icons/icon028.png\">";
		}
		workitem.setItemValue("_img", img);

		/*
		 * for (String entry: eventLog) { // sofortüberweisung if (entry.contains(".96")
		 * && !img.contains("100.png")) {
		 * img=img+"<img src=\"/layout/icons/icon100.png\">"; } // mahnen if
		 * (entry.contains(".95") && !img.contains("074.png")) {
		 * img=img+"<img src=\"/layout/icons/icon074.png\">"; } // ablehnen if
		 * (entry.contains("5100.90") && !img.contains("031.png")) {
		 * img=img+"<img src=\"/layout/icons/icon031.png\">"; } }
		 * workitem.setItemValue("_img", img);
		 */
	}

	/**
	 * Diese Method prüft die Buchungsperiode auf Plausibilität
	 * 
	 * YYYY(+1)01-12
	 * 
	 * z.b. 202110 oder 202107 oder 202201
	 * 
	 * @param workitem
	 * @throws PluginException
	 */
	private void validateBuchungsperiode(String period) throws PluginException {

		// buchungsperionde nur prüfen wenn noch nicht archiviert
		if (!period.isEmpty()) {
			LocalDate localDate = LocalDate.now();
			int year = localDate.getYear();
			// build regex....
			String regex = "(" + year + "|" + (year + 1) + "|" + (year - 1) + ")(1[0-2]|0[1-9])";

			if (!period.matches(regex)) {
				// throw a plugin exception - because name is missing!
				throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
						"Eingabefehler - Bitte geben Sie eine gültige Buchungsperiode ein! Prüfen Sie auch die Spalte BP in der Positionstabelle.");
			}
		}
	}

	/**
	 * Diese Method prüft ob die "invoice.number" bereits einmal im
	 * Rechnugnsworkflow vergeben wurde. Falls ja wird eine PluginExcpetion
	 * ausgelöst.
	 * <p>
	 * Query Example:
	 * <p>
	 * <code>NOT $uniqueid:"9f72fa50-4845-41ea-b6b9-ccd518c353be" AND 
	         txtcooperatespace:"9dba107e-f8ef-4150-a832-040d75a6eda7" AND invoice.number:"45"</code>
	 * 
	 * <p>
	 * in case a duplicate invoice was detected the item invoice.number.duplicate is
	 * filled. This item is used for a conditional event. The case is displayed as a
	 * warning in the form.
	 * 
	 * @throws PluginException
	 * @throws QueryException
	 * 
	 */
	private void validateInvoiceNumber(ItemCollection workitem) throws PluginException {

		String invoiceNumber = workitem.getItemValueString("invoice.number");
		// strip
		String invoiceNumberStripped = invoiceNumber.replace(" ", "");
		workitem.setItemValue("invoice.number.stripped", invoiceNumberStripped);
		String invoiceNumberDuplicate = workitem.getItemValueString("invoice.number.duplicate");

		// wenn keine Rechnungsnummer eingegeben wurde gehts weiter!
		if (invoiceNumber.isEmpty()) {
			return;
		}

		// rechnungseingang only workitems...
		// Change 11.4.22 - we have now an additional field: invoice.number.stripped
		String query = "(type:workitem OR type:workitemarchive) AND ($modelversion:rechnungseingang*) AND NOT ($uniqueid:\""
				+ workitem.getUniqueID() + "\") AND ((invoice.number:\"" + invoiceNumberStripped
				+ "\") OR (invoice.number.stripped:\"" + invoiceNumberStripped + "\"))";
		try {
			int result = this.getWorkflowService().getDocumentService().count(query, 1);
			if (result > 0) {
				// wenn _invoicenumber_duplicate bereits gesetzt ist - dann geht es ohne prüfung
				// weiter
				if (!invoiceNumberDuplicate.isEmpty()) {
					logger.warning("...validateion skipped by user with duplicate invoice number: " + invoiceNumber);
				} else {

					// set _invoicenumber_duplicate - dadurch wird die warnmeldung ausgegeben und
					// der Vorgang nicht weitergeleitet
					workitem.replaceItemValue("invoice.number.duplicate", invoiceNumber);
					throw new PluginException(InvoicePlugin.class.getName(), ERROR_DUPPLICATE_INVOICE_NUMBER,
							"ACHTUNG: Die Rechnungsnummer wurde bereits gebucht. Bitte prüfen Sie den Beleg. Sollte die Belegnummer korrekt sein, wiederholen Sie die Aktion.");
				}
			} else {
				// clear !
				workitem.appendItemValue("invoice.number.duplicate.history",
						workitem.getItemValueString("invoice.number.duplicate"));
				workitem.replaceItemValue("invoice.number.duplicate", "");
			}
		} catch (QueryException e) {
			throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e);
		}
	}

	/**
	 * Diese Method prüft ob die eingegebene IBAN nummer bereits in dem ausgewälten
	 * Kreditor bekannt ist. Fallst nicht wird diese schnell mal in den Kreditoren
	 * Datensatz übertragen. Dadurch muss man nicht extra die Kreditoren Verwaltung
	 * aufrufen. Es ist ein art selbst-lernendes System.
	 * <p>
	 * Die Mehtode ruft eine Plugin Exception aus um den User zu fragen ob er das
	 * möchte.
	 * 
	 * @throws PluginException
	 * @throws QueryException
	 * 
	 */
	private void checkIBANNumber(ItemCollection workitem) throws PluginException {

		String cdtrNumber = workitem.getItemValueString("cdtr.number");
		String iban = workitem.getItemValueString("cdtr.iban");
		String bic = workitem.getItemValueString("cdtr.bic");
		String overtakeIBAN = workitem.getItemValueString("ibanbic.overtake");

		if (cdtrNumber.isEmpty() || iban.isEmpty() || bic.isEmpty()) {
			// no op
			return;
		}

		// wenn bereits eine Überneahme angedroht wurde dann übernehemen!
		if (!overtakeIBAN.isEmpty()) {
			kreditorService.addNewIBANBIC(cdtrNumber, iban, bic);
			workitem.setItemValue("ibanbic.overtake","");
			return;
		}

		// search the kreditor
		ItemCollection cdtr = kreditorService.findCreditorDebitor(cdtrNumber);
		if (cdtr != null) {
			// Prüfen ob wir die IBAN schon kennen
			if (iban.equals(cdtr.getItemValueString("cdtr.iban")) && bic.equals(cdtr.getItemValueString("cdtr.bic"))) {
				return;
			}
			if (iban.equals(cdtr.getItemValueString("cdtr.iban2"))
					&& bic.equals(cdtr.getItemValueString("cdtr.bic2"))) {
				return;
			}
			if (iban.equals(cdtr.getItemValueString("cdtr.iban3"))
					&& bic.equals(cdtr.getItemValueString("cdtr.bic3"))) {
				return;
			}
			if (iban.equals(cdtr.getItemValueString("cdtr.iban4"))
					&& bic.equals(cdtr.getItemValueString("cdtr.bic4"))) {
				return;
			}
			// OK - scheinbar ist diese IBAN/BIC nicht bekannt. Also fragen wir mal nach....
			workitem.setItemValue("ibanbic.overtake", iban + bic);
			throw new PluginException(InvoicePlugin.class.getName(), ERROR_NEW_IBANBIC,
					"Die von Ihnen erfasste IBAN/BIC ist noch nicht bekannt. Sollte die Eingabe korrekt sein, wiederholen Sie die Aktion.");
		}

	}

	

	/**
	 * Hilfsmethod prüft ob die cdtr.nummer mit einer nummer aus der Cargosoft
	 * Import Datei übereinstimmt.
	 * 
	 * @param workitem
	 * @throws PluginException
	 */
	private void validateCargosoftCdtrNumber(ItemCollection workitem) throws PluginException {
		String crdtrNumber = workitem.getItemValueString("cdtr.number");

		// wenn keine Nummer eingegeben wurde gehts weiter!
		if (crdtrNumber.isEmpty()) {
			return;
		}

		// search creditor number in cargosoft...
		// die cargosoft Kreditornummer beginnt seltsamerweise mit einem K.....
		try {
			String query = "(type:cargosoftkreditor) AND (name:K" + crdtrNumber + " OR name:" + crdtrNumber + ")";
			List<ItemCollection> result = this.getWorkflowService().getDocumentService().find(query, 1, 0);
			if (result == null || result.size() == 0) {
				throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA,
						"Die Kreditorennummer ist nicht gültig.");

			} else {
				// update cargosoft crediotr name
				ItemCollection cargo = result.get(0);
				workitem.setItemValue("cdtr.name.cargosoft", cargo.getItemValueString("_VENDOR_Name"));
			}
		} catch (QueryException e) {
			e.printStackTrace();
		}

	}

	/**
	 * converts the Map List of a workitem into a List of ItemCollectons
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected static List<ItemCollection> explodeChildList(ItemCollection workitem) {
		// convert current list of childItems into ItemCollection elements
		ArrayList<ItemCollection> childItems = new ArrayList<ItemCollection>();

		List<Object> mapOrderItems = workitem.getItemValue(CHILD_ITEM_PROPERTY);
		for (Object mapOderItem : mapOrderItems) {
			if (mapOderItem instanceof Map) {
				ItemCollection itemCol = new ItemCollection((Map) mapOderItem);
				childItems.add(itemCol);
			}
		}
		return childItems;
	}

	/**
	 * This method returns true if the current workitem is a Cargosoft Invoice.
	 * 
	 * This can be based on different model versions e.g. rechnungseingang-de-1.2,
	 * rechnungseingang-pl-1.0, ...
	 * 
	 * @param workitem
	 * @return
	 */
	public static boolean isCargoRechnung(ItemCollection workitem) {
		String REGEX_PATTERN = "rechnungseingang-([a-z]{2})-\\d.\\d";
		// Erstellen Sie ein Pattern-Objekt
		Pattern pattern = Pattern.compile(REGEX_PATTERN);
		Matcher matcher = pattern.matcher(workitem.getModelVersion());
		// Überprüfen, ob das Muster übereinstimmt
		return matcher.matches();
	}
}
