/*******************************************************************************
 *  Imixs Workflow 
 *  Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,  
 *  http://www.imixs.com
 *  
 *  This program is free software; you can redistribute it and/or 
 *  modify it under the terms of the GNU General Public License 
 *  as published by the Free Software Foundation; either version 2 
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful, 
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 *  General Public License for more details.
 *  
 *  You can receive a copy of the GNU General Public
 *  License at http://www.gnu.org/licenses/gpl.html
 *  
 *  Project: 
 *  	http://www.imixs.org
 *  	http://java.net/projects/imixs-workflow
 *  
 *  Contributors:  
 *  	Imixs Software Solutions GmbH - initial API and implementation
 *  	Ralph Soika - Software Developer
 *******************************************************************************/

package com.alexanderlogistics.api;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.exceptions.QueryException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.xml.CargosoftXMLInvoiceImportService;

import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
 ** Dieser Service korrigiert falsch importierte Cargosoft daten.
 * Ausgangsrechnungen die ein falsches Jahr haben.
 * 
 * Oder DAten die über die XML Schnittstelle falsch reinkamen...
 * 
 * @author rsoika
 * @version 1.1
 */
@Stateless
@Produces({ MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON,
        MediaType.TEXT_XML })
@Path("/cargosoft")
public class CargosoftMigrationRestService implements Serializable {

    private static final long serialVersionUID = 1L;
    String log = "";
    int errors = 0;
    int count = 0;
    public static String _QUERY = "($modelversion:rechnungsausgang-de-1.0)\n" +
            "AND $created:[20240604 TO 20240701]";
    // public static String _QUERY = "($modelversion:rechnungsausgang-dwc-1.0)\n" +
    // //
    // "AND $created:[20240604 TO 20240619]";

    @Inject
    DocumentService documentService;

    @Inject
    WorkflowService workflowService;

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

    public CargosoftMigrationRestService() {
        super();
    }

    /**
     * This method loads taxonomy data for a workflow group within a given process
     * and builds a ChartJS data structure in JSON format
     * 
     * @param workflowgroup
     * @param task
     * @return
     * @throws QueryException
     */
    @GET
    @Path("/test")
    @Produces({ MediaType.TEXT_PLAIN })
    public String testeFehlerhafteCargosoftDaten() throws QueryException {
        int falsch = 0;
        logger.info("query=" + _QUERY);
        log = "Query\n" + _QUERY;
        List<ItemCollection> result = documentService.find(_QUERY, 9999, 0);

        log = log + "\n\nCount=" + result.size();

        if (result.size() > 9999) {
            log = log + "\n\nACHTUNG ES GIBT MEHR ALS 10000 RECHNUNGEN !";
        }

        for (ItemCollection workitem : result) {
            String type = workitem.getItemValueString("invoice.type");
            if ("G".equals(type) || "SR".equals(type)) {
                double total = workitem.getItemValueDouble("invoice.saldo");
                if (total >= 0) {
                    logger.info("---Falsch: " + workitem.getUniqueID());
                    falsch++;
                    workitem.setItemValue("invoice.saldo", -total);
                    // documentService.save(workitem);
                }
            }

        }

        log = log + "\n\n";
        log = log + "" + result.size() + " rechnungen geprüft";
        log = log + "" + falsch + " davon falsch";
        log = log + "\n\n";
        return log;
    }

    /**
     * This method fixes the wrong amount of a imported xml invoice.
     * 
     * @param workflowgroup
     * @param task
     * @return
     * @throws QueryException
     * @throws ModelException
     * @throws PluginException
     * @throws ProcessingErrorException
     * @throws AccessDeniedException
     */
    public String fixMissingDueDate()
            throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {

        logger.info("query=" + _QUERY);
        log = "Query\n\n" + _QUERY;

        List<ItemCollection> result = documentService.find(_QUERY, 9999, 0);

        log = log + "\n\nSelection Count=" + result.size();

        log = log + "\n\nFixes\n\n";

        for (ItemCollection workitem : result) {

            if (!workitem.hasItem("invoice.reminder")) {
                Date dueDate = workitem.getItemValueDate("invoice.duedate");
                workitem.setItemValue("invoice.reminder", dueDate);
                count++;
                // save only
                documentService.save(workitem);
            }
        }

        log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n";
        return log;
    }

    @GET
    @Path("/fix")
    @Produces({ MediaType.TEXT_PLAIN })
    public String fixMissingRate()
            throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {

        logger.info("query=" + _QUERY);
        log = "Query\n\n" + _QUERY;

        List<ItemCollection> result = documentService.find(_QUERY, 9999, 0);

        log = log + "\n\nSelection Count=" + result.size();

        log = log + "\n\nFixes\n\n";

        for (ItemCollection workitem : result) {

            if (!workitem.hasItem("invoice.rate")) {
                // Fetch frist datev.kurs von _childitems

                List<ItemCollection> childs = InvoiceUtil.explodeChildList(workitem);
                if (childs != null && childs.size() > 0) {
                    ItemCollection child = childs.get(0);
                    double rate = child.getItemValueDouble("datev.kurs");
                    logger.info("..fix missing rate = " + rate);
                    workitem.setItemValue("invoice.rate", rate);
                    // save only
                    documentService.save(workitem);
                }

            }
        }

        log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n";
        return log;
    }

    public String fixFehlerhafteCargosoftDatenMitSnapshot()
            throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {

        logger.info("query=" + _QUERY);
        log = "Query\n\n" + _QUERY;

        List<ItemCollection> result = documentService.find(_QUERY, 9999, 0);

        log = log + "\n\nSelection Count=" + result.size();

        log = log + "\n\nFixes\n\n";

        for (ItemCollection workitem : result) {
            ItemCollection snapshot = null;
            // load snapshot
            String snapshotID = workitem.getItemValueString("$snapshotid");
            if (!snapshotID.isEmpty()) {
                snapshot = documentService.load(snapshotID);
            }

            if (snapshot == null) {
                logger.warning("Unable to load snapshot for document " + workitem.getUniqueID());
                log = log + "Unable to load snapshot for document " + workitem.getUniqueID() + " \n";
                errors++;
                continue;
            }

            // Get raw data....
            // find the XML Attachment
            String xmlFileName = null;
            List<String> fileNames = snapshot.getFileNames();
            for (String fileName : fileNames) {
                if (fileName.endsWith(".xml")) {
                    xmlFileName = fileName;
                    break;
                }
            }
            if (xmlFileName == null) {
                logger.warning("Unable to load XML from document " + workitem.getUniqueID());
                log = log + "Unable to load XML from document " + workitem.getUniqueID() + " \n";
                errors++;
                continue;
            }

            FileData xmlFileData = snapshot.getFileData(xmlFileName);
            Document xmlDoc = createXMLTree(xmlFileData.getContent());

            migrateDocument(workitem, xmlDoc);
        }

        log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n";
        return log;
    }

    /**
     * Diese Methode korrigiert den Saldo um die Netto summe....
     * 
     * @param workitem
     * @param xmlDoc
     * @throws ModelException
     * @throws PluginException
     * @throws ProcessingErrorException
     * @throws AccessDeniedException
     */
    private void migrateDocument(ItemCollection workitem, Document doc)
            throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException {

        // Haben wir schon das neue Feld 'invoice.total.net' mit dem Netto Betrag?
        if (workitem.hasItem("invoice.total.tax")) {
            // no migration needed
            return;
        }

        // Net und Tax ermitteln
        readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
                workitem, "invoice.total.net", Double.class);
        readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VATAmount/Amount/Value",
                workitem, "invoice.total.tax", Double.class);

        // Der Total entspricht nun dem Net Amount.
        // Wenn wir eine Tax haben, dann müssen wir den Total sowie den Saldo um
        // diese Tax erhöhen.
        // Damit ist dann die Migration abgeschlossen.
        double tax = workitem.getItemValueDouble("invoice.total.tax");
        if (tax > 0) {
            log = log + "...Migriere " + workitem.getItemValueString("invoice.number") + " Korrektur Betrag " +
                    tax + "\n";
            double total = workitem.getItemValueDouble("invoice.total");
            double saldo = workitem.getItemValueDouble("invoice.saldo");
            // Gutschrift?
            if (total < 0) {
                total = total - tax;
                saldo = saldo - tax;

            } else {
                // Rechnung
                total = total + tax;
                saldo = saldo + tax;
            }
            workitem.setItemValue("invoice.total", total);
            workitem.setItemValue("invoice.saldo", saldo);
            workitem.setItemValue("cargosoft.fix.xmlerror", tax);
            log = log + "....... total neu=" + total + "   saldo neu=" + saldo + "\n";

            if (5900 == workitem.getTaskID()) {
                workitem.setEventID(942);
                workflowService.processWorkItem(workitem);

            } else {
                // save only
                documentService.save(workitem);
            }
            count++;
        } else {
            log = log + "...Keine Migration notwendig  " + workitem.getItemValueString("invoice.number") +
                    " tax=" + tax + "\n";
        }
    }

    /**
     * Erzeugt einen XML Baum aus dem XML Raw Daten
     * 
     * @param snapshot
     * @return
     */
    private Document createXMLTree(byte[] rawData) {
        Document doc = null;
        InputStream inputStream = new ByteArrayInputStream(rawData);
        InputSource inputSource = new InputSource(inputStream);

        DocumentBuilder documentBuilder;
        try {
            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            doc = documentBuilder.parse(inputSource);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            log = log + "Unable to load XML tree: " + e.getMessage() + " \n";
            return null;
        }

        return doc;
    }

    /**
     * Reads a tag value from the xml tree and set the value into the given
     * workitem.
     * 
     * /Invoices/Invoice/InvoiceHeader/Client/Code
     * 
     * Beispiel Datum:
     * <InvoiceDate>2024-05-13T00:00:00+02:00</InvoiceDate>
     * 
     * @param doc        - xml doc
     * @param expression - xpath expression
     * @param workitem
     * @param itemName
     * @param itemType
     */
    private <T> void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName,
            Class<T> itemType) {
        // create XPath...
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        XPathExpression xPathExpression;
        try {
            xPathExpression = xpath.compile(expression);

            // extract node value
            Node valueNode = (Node) xPathExpression.evaluate(doc, XPathConstants.NODE);
            if (valueNode != null) {
                String value = valueNode.getTextContent();

                if (itemType == Date.class) {
                    // 2024-05-13T00:00:00+02:00
                    SimpleDateFormat formatter = new SimpleDateFormat(CargosoftXMLInvoiceImportService.DATE_FORMAT);
                    formatter.setTimeZone(TimeZone.getTimeZone("CET"));
                    try {
                        workitem.setItemValue(itemName, formatter.parse(value));
                    } catch (ParseException e) {
                        logger.warning("Invalid Date Format");
                    }
                    return;
                }
                if (itemType == Double.class && value != null && !value.isEmpty()) {
                    workitem.setItemValue(itemName, Double.parseDouble(value));
                    return;
                }
                if (itemType == Integer.class && value != null && !value.isEmpty()) {
                    workitem.setItemValue(itemName, Integer.parseInt(value));
                    return;
                }
                // Default String format
                workitem.setItemValue(itemName, value);

            }
        } catch (XPathExpressionException e) {
            logger.warning("Unable to read data field '" + expression + "' : " + e.getMessage());

        }
    }
}
