package com.alexanderlogistics;

import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.enterprise.context.ConversationScoped;
import javax.enterprise.event.Observes;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.WorkflowKernel;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.faces.data.WorkflowController;
import org.imixs.workflow.faces.data.WorkflowEvent;

/**
 * Der OPListeController dient dazu eine liste aller offenen Ausgangsrechnungen
 * zu bilden
 * <p>
 * Zusätzlich bietet er die funktionen zum auszifferne. Dies bedeutet, der
 * controller ermittelt zunächst alle offenen Rechnungen zu einem Debitor. Diese
 * Rechnungen werden in eine ChildItems Collection überführt. Der User kann nun
 * im Interface einzelne Rechnungen selektieren. Wird das Workitem gespeichert,
 * errechnet der Controller die selectieren Invoices und verknpüft diese final
 * mit dem Zahlungseingang.
 *
 * 
 * @author rsoika
 *
 */
@Named("opListController")
@ConversationScoped
public class OPListController implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final int TASK_ERSTELLUNG = 1000;

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

    private String lastDbtrNumber = null;

    private String currencyFilter = "-";
    private String lastCurrencyFilter = null;

    private List<ItemCollection> invoiceList = null;

    @Inject
    protected WorkflowController workflowController;

    @Inject
    protected WorkflowService workflowService;

    @Inject
    ZahlungseingangService zahlungseingangService;

    @Inject
    protected DocumentService documentService;

    /**
     * Diese Methode wird für Zahlungseingänge und Mahnläufe verwendet.
     * <p>
     * Die Methode liefert die Rechnungen zur gegebenen dbtrNumber. Die Methode
     * nutzt
     * einen lokalen cache um die Zugriffe zu beschleunigen.
     * <p>
     * Befindet sich ein Zahlungseingang in Erstellung (Task=1000) werden alle
     * offenen Rechnungen geholt.
     * <p>
     * Befindet sich ein Zahlungseingang nicht mehr in erstellung werden nur die
     * Rechnugnen aus $workitemRef geholt!
     * <p>
     * Bei verwendung im Mahnlauf werden zusätzlich auch alle diese Rechnungen
     * vorselektiert (Anforderung von Mary Mwangi). D.h. es werden alle Checkboxen
     * in der Maske 'selection_opliste_Mahnluaf' angewählt. D.h auch, dass man ggf.
     * Rechnungen manuell delselktieren muss, wenn man diese nicht im Mahnlauf haben
     * will.
     * <p>
     * Beim Mahnlauf werden Inkasso Rechnungen ausgenommen.
     * 
     * 
     * @param _dbtrNumber
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<ItemCollection> getInvoices(String _dbtrNumber) {
        if (_dbtrNumber == null) {
            return new ArrayList<ItemCollection>();
        }

        // Compute all open invoices if TASK_ERSTELLUNG....
        if (workflowController.getWorkitem().getTaskID() == 0
                || workflowController.getWorkitem().getTaskID() == TASK_ERSTELLUNG) {

            // return empty list if dbtr is not set
            if (_dbtrNumber == null || _dbtrNumber.isEmpty()) {
                invoiceList = new ArrayList<ItemCollection>();
                return invoiceList;
            }

            // if last dbtr number is equal then return the current list....
            if (invoiceList != null && //
                    lastDbtrNumber != null && lastDbtrNumber.equals(_dbtrNumber) && //
                    lastCurrencyFilter != null && lastCurrencyFilter.equals(currencyFilter) //
            ) {
                return invoiceList;
            }

            // load new invoice list....
            lastDbtrNumber = _dbtrNumber;
            lastCurrencyFilter = currencyFilter;
            invoiceList = new ArrayList<ItemCollection>();
            logger.fine("....loading open Invoices for " + _dbtrNumber);

            // Aus der dbtrNummer muss das führendde D entfernt werden
            // wir speichern dieses zwar im Zahlungseingangs Workflow, aber eine
            // Ausganksrechnung kennt das nicht
            // Das ganze ist ein übles Relikt aus Cargosoft und kann nicht vermieden werden
            if (_dbtrNumber.startsWith("D")) {
                _dbtrNumber = _dbtrNumber.substring(1);
            }

            try {
                String query = "(type:workitem) AND ($modelversion:rechnungsausgang-*) AND (dbtr.number:" + _dbtrNumber
                        + ")";
                List<ItemCollection> invoiceListTemp = documentService.find(query, 999, 0, "invoice.number", false);

                // Jetzt nur noch die Rechnungen nehmen die zum ausgewählten Währungsfilter
                // passen
                invoiceList = new ArrayList<ItemCollection>();
                for (ItemCollection _invoice : invoiceListTemp) {
                    if (currencyFilter == null || currencyFilter.isEmpty() || currencyFilter.equals("-")) {
                        invoiceList.add(_invoice);
                    } else {
                        if (currencyFilter.equals(_invoice.getItemValueString("invoice.currency"))) {
                            invoiceList.add(_invoice);
                        }
                    }
                }
            } catch (QueryException e) {
                logger.severe("Failed to get op liste:" + e.getMessage());
            }

            /*
             * Zahlugnseingang?
             * 
             * Wir berechnen den saldo.
             */
            if (workflowController.getWorkitem().getModelVersion().startsWith("zahlungseingang")) {
                // berechnung des Basisumsatzes bei Fremdwährungen
                for (ItemCollection invoice : invoiceList) {
                    float rate = invoice.getItemValueFloat("invoice.rate");
                    float maxSaldo = invoice.getItemValueFloat("invoice.saldo");
                    if (rate != 0) {
                        maxSaldo = maxSaldo / rate;
                        invoice.setItemValue("invoice.saldo.byrate", maxSaldo);
                    }
                }
            }

            /*
             * Mahnlauf ?
             *
             * Bei einem Mahnlauf selektieren wir alle Rechnungne immer vor. Das ist eine
             * Anforderung von AGL Der Benutzer kann dann einzelne Rechnungen wieder
             * abwählen.
             * 
             * Eine Ausnahme ist hier nur wenn jemand auf Speichern oder mahnen drückt
             * (Event = 10|20). Dann ändern wir die Liste nicht!
             * 
             * Hinweis: in der Liste befinden sich keine Inkasso Rechnungen, da man diese
             * nicht anwählen kann.
             */
            if (workflowController.getWorkitem().getModelVersion().startsWith("mahnlauf")) {

                // wir selektieren hier immer alle fälligen Rechnungen vor falls wir aus
                // einer Rechnung mit dem Event 100 (Mahnlauf erstellen) kommen, oder
                // wenn der Mahnlauf von außen gerate eben neu erzeugt wurde
                if (workflowController.getWorkitem().getItemValueInteger("$lastevent") == 100
                        ||
                        workflowController.getWorkitem().getItemValueInteger("$lastevent") == 0) {
                    List<String> intialSelection = new ArrayList<String>();
                    for (ItemCollection invoice : invoiceList) {
                        // ist die Rechnung fällig?
                        if ("workitem".equals(invoice.getType()) && invoice.getTaskID() >= 5100) {
                            intialSelection.add(invoice.getUniqueID());
                        }
                    }
                    workflowController.getWorkitem().setItemValue("$workitemref", intialSelection);
                }
            }
        } else {
            // We are no longer in TASK_ERSTELLUNG, so
            // fetch only selected invoices
            invoiceList = new ArrayList<ItemCollection>();
            if (!workflowController.getWorkitem().isItemEmpty("$workitemref")) {
                List<String> selection = workflowController.getWorkitem().getItemValue("$workitemref");
                for (String id : selection) {
                    if (id != null && !id.isEmpty()) {
                        invoiceList.add(documentService.load(id));
                    }
                }
            }
        }

        // finally we set the 'selection' item depending on the $workitemList
        // and the payment amount form the 'payment.details'
        if (invoiceList != null) {
            List<String> selection = workflowController.getWorkitem().getItemValue("$workitemref");
            List<ItemCollection> paymentList = zahlungseingangService
                    .loadPaymentDetails(workflowController.getWorkitem());
            for (ItemCollection invoice : invoiceList) {
                if (selection.contains(invoice.getUniqueID())) {
                    invoice.setItemValue("selected", true);
                    // set amount
                    for (ItemCollection payment : paymentList) {
                        if (invoice.getUniqueID().equals(payment.getUniqueID())) {
                            invoice.setItemValue("payment.amount", payment.getItemValue("payment.amount"));
                            break;
                        }
                    }
                }
            }
        } else {
            // empty list
            invoiceList = new ArrayList<ItemCollection>();
        }

        return invoiceList;
    }

    /**
     * On Before Process we store the selected invoices in $worktiemRef
     * 
     * @param workflowEvent
     */
    public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) {
        if (workflowEvent == null || workflowEvent.getWorkitem() == null) {
            return;
        }
        // store a collection of child items if you are in a Zahlungseingang
        if (workflowEvent.getWorkitem().getModelVersion().startsWith("zahlungseingang")
                && workflowEvent.getEventType() == WorkflowEvent.WORKITEM_BEFORE_PROCESS) {
            updateChildList(workflowEvent.getWorkitem());
        }
    }

    public ItemCollection loadInvoice(String id) {
        return documentService.load(id);
    }

    /**
     * Diese Methode aktualisiert das item ChildWOrkitems mit allen selektierten
     * Invoices udn berechnet den payment total
     */
    @SuppressWarnings("rawtypes")
    public void updateChildList(ItemCollection workitem) {
        double paymentTotal = 0;
        List<Map> mapInvoiceItems = new ArrayList<Map>();
        logger.fine("Convert child items into Map...");
        // iterate over all order items..
        List<String> selectionList = new ArrayList<String>();
        for (ItemCollection invoice : invoiceList) {
            if (invoice.getItemValueBoolean("selected")) {
                logger.fine("calculate - " + invoice.getUniqueID());
                selectionList.add(invoice.getUniqueID());
                paymentTotal = paymentTotal + invoice.getItemValueDouble("payment.amount");
                ItemCollection invoiceStub = new ItemCollection();
                invoiceStub.setItemValue(WorkflowKernel.UNIQUEID, invoice.getUniqueID());
                invoiceStub.setItemValue("payment.amount", invoice.getItemValue("payment.amount"));
                mapInvoiceItems.add(invoiceStub.getAllItems());

            }
        }

        // rond with 2 digits
        workitem.replaceItemValue("payment.total", InvoiceUtil.round(paymentTotal));
        // update childitems
        workitem.replaceItemValue(ZahlungseingangService.ITEM_PAYMENT_DETAILS, mapInvoiceItems);
        // Update $workitemref
        workitem.setItemValue("$workitemref", selectionList);
    }

    /**
     * This JSF backing method is called by cargosft-debitor-serach.xhtml after the
     * user has selected a new debitor.
     * <p>
     * The new dbtrNumber is slected form the curren ajax request param 'dbtrNumber'
     * <p>
     * The method just updates the dbtr.number of the current workitem. The form
     * will than rerender the opList section.
     */
    public void updateDbtrNumber() {
        FacesContext fc = FacesContext.getCurrentInstance();
        String dbtrNumber = fc.getExternalContext().getRequestParameterMap().get("dbtrNumber");
        logger.fine(".........dbtrNumber = " + dbtrNumber);
        workflowController.getWorkitem().setItemValue("dbtr.number", dbtrNumber);
    }

    /**
     * Hilfsmethode delegates to ZahlungseingangServcie
     * 
     * @param payment
     * @return
     */
    public List<ItemCollection> loadPaymentDetails(ItemCollection payment) {
        return zahlungseingangService.loadPaymentDetails(payment);
    }

    /**
     * Berechnet den gesammten OP Saldo
     * 
     * @return
     */
    public double calculateInvoiceTotal() {
        double result = 0;
        for (ItemCollection invoice : invoiceList) {
            result = result + invoice.getItemValueDouble("invoice.total");
        }
        // rond with 2 digits
        return InvoiceUtil.round(result);

    }

    public double calculateInvoiceTotal(String currency) {
        double result = 0;
        for (ItemCollection invoice : invoiceList) {
            if (currency.equals(invoice.getItemValueString("invoice.currency"))) {
                result = result + invoice.getItemValueDouble("invoice.total");
            }
        }
        // rond with 2 digits
        return InvoiceUtil.round(result);

    }

    /**
     * Berechnet den gesammten OP Saldo
     * 
     * @return
     */
    public double calculateInvoiceSaldo() {
        double result = 0;
        if (invoiceList != null) {
            for (ItemCollection invoice : invoiceList) {
                result = result + invoice.getItemValueDouble("invoice.saldo");
            }
        }
        // rond with 2 digits
        return InvoiceUtil.round(result);

    }

    /**
     * Berechnet den noch offenen Differenz vom Zahlbetrag
     * 
     * @return
     */
    public double calculatePaymentDifference() {
        double paymentAmount = workflowController.getWorkitem().getItemValueDouble("payment.amount");
        double paymentDifference = workflowController.getWorkitem().getItemValueDouble("payment.difference");

        double paymentTotal = workflowController.getWorkitem().getItemValueDouble("payment.total");
        // paymentAmount - calculateInvoiceSaldo();
        double result = (paymentAmount + paymentDifference) - paymentTotal;

        logger.fine("...new PaymentDifference=" + result);
        // rond with 2 digits
        return InvoiceUtil.round(result);
    }

    /**
     * Berechnet den Zahlbetrag in EUR bei Fremdwärungen
     * 
     * @return
     */
    public double calculatePaymentSaldoEUR() {
        double result = 0;
        if (invoiceList != null) {
            for (ItemCollection invoice : invoiceList) {
                double rate = invoice.getItemValueDouble("invoice.rate");
                double pay = invoice.getItemValueDouble("payment.amount");
                if (rate != 0) {
                    result = result + pay / rate;
                } else {
                    result = result + pay;
                }
            }
        }
        // rond with 2 digits
        return InvoiceUtil.round(result);
    }

    /**
     * Returns true if the reminder date is today or in the past
     * 
     * @param uniqueid
     * @return
     */
    public boolean isInDue(String uniqueid) {
        ItemCollection invoice = workflowService.getWorkItem(uniqueid);
        LocalDate date = invoice.getItemValueLocalDate("invoice.reminder");
        return date.compareTo(LocalDate.now()) <= 0;
    }

    public String getCurrencyFilter() {
        return currencyFilter;
    }

    public void setCurrencyFilter(String currencyFilter) {
        this.currencyFilter = currencyFilter;
    }
}
