/***********************************************************************
 * This file is part of iDempiere ERP Open Source                      *
 * http://www.idempiere.org                                            *
 *                                                                     *
 * Copyright (C) Contributors                                          *
 *                                                                     *
 * 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 should have received a copy of the GNU General Public License   *
 * along with this program; if not, write to the Free Software         *
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
 * MA 02110-1301, USA.                                                 *
 *                                                                     *
 * Contributors:                                                       *
 * - hengsin                         								   *
 **********************************************************************/
package org.idempiere.test.model;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;

import org.adempiere.base.Core;
import org.compiere.model.MBPartner;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutConfirm;
import org.compiere.model.MInOutLine;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MPInstance;
import org.compiere.model.MPInstanceLog;
import org.compiere.model.MPInstancePara;
import org.compiere.model.MProcess;
import org.compiere.model.MProduct;
import org.compiere.model.Query;
import org.compiere.model.SystemIDs;
import org.compiere.model.X_AD_PInstance_Log;
import org.compiere.process.DocAction;
import org.compiere.process.ProcessCall;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ServerProcessCtl;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.compiere.util.Util;
import org.compiere.wf.MWFNode;
import org.compiere.wf.MWFNodeNext;
import org.compiere.wf.MWFNodePara;
import org.compiere.wf.MWorkflow;
import org.idempiere.test.AbstractTestCase;
import org.idempiere.test.DictionaryIDs;
import org.junit.jupiter.api.Test;

/**
 * @author hengsin
 *
 */
public class ProcessTest extends AbstractTestCase {

	/**
	 * 
	 */
	public ProcessTest() {
	}

	@Test
	public void testWorkflowProcess() {
		//first test, using MProcess.processIt
		MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
		//Joe Block
		order.setBPartner(MBPartner.get(Env.getCtx(), DictionaryIDs.C_BPartner.JOE_BLOCK.id));
		order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
		order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
		order.setDocStatus(DocAction.STATUS_Drafted);
		order.setDocAction(DocAction.ACTION_Complete);
		Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
		order.setDateOrdered(today);
		order.setDatePromised(today);
		order.saveEx();
		
		MOrderLine line1 = new MOrderLine(order);
		line1.setLine(10);
		//Azalea Bush
		line1.setProduct(MProduct.get(Env.getCtx(), DictionaryIDs.M_Product.AZALEA_BUSH.id));
		line1.setQty(new BigDecimal("1"));
		line1.setDatePromised(today);
		line1.saveEx();
		
		int Process_Order=SystemIDs.PROCESS_C_ORDER_PROCESS;
		MProcess process = MProcess.get(Env.getCtx(), Process_Order);
		ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
		pi.setAD_Client_ID(getAD_Client_ID());
		pi.setAD_User_ID(getAD_User_ID());
		pi.setRecord_ID(order.get_ID());
		pi.setTransactionName(getTrxName());
		
		boolean ok = process.processIt(pi, getTrx(), false);
		if (!ok || pi.isError()) {
			fail("Error running Process_Order workflow process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
		
		order.load(getTrxName());
		assertTrue(order.getDocStatus().equals(DocAction.STATUS_Completed), "Order not completed");
		
		//second test, using MWorkflow.runDocumentActionWorkflow
		pi = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_ReActivate);
		if (pi.isError()) {
			fail("Failed to reactivate order" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
		
		order.load(getTrxName());
		assertTrue(order.getDocStatus().equals(DocAction.STATUS_InProgress), "Order not reactivated");
	}
	
	@Test
	public void testJavaProcess() {
		int Verify_BOM=SystemIDs.PROCESS_PP_PRODUCT_BOM;
		int Patio_Chair=DictionaryIDs.M_Product.P_CHAIR.id;
		
		//first, test MProcess.processIt
		MProcess process = MProcess.get(Env.getCtx(), Verify_BOM);
		ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
		pi.setAD_Client_ID(getAD_Client_ID());
		pi.setAD_User_ID(getAD_User_ID());
		pi.setRecord_ID(Patio_Chair);
		pi.setTransactionName(getTrxName());
		
		boolean ok = process.processIt(pi, getTrx(), false);
		if (!ok || pi.isError()) {
			fail("Error running Verify BOM process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
		
		//second, test ServerProcessCtl.process
		pi = new ProcessInfo(process.getName(), process.get_ID());
		pi.setAD_Client_ID(getAD_Client_ID());
		pi.setAD_User_ID(getAD_User_ID());
		pi.setRecord_ID(Patio_Chair);
		pi.setTransactionName(getTrxName());
		if(process.getAD_PrintFormat_ID() > 0)
			pi.setTransientObject(process.getAD_PrintFormat());
		ServerProcessCtl.process(pi, getTrx());
		if (pi.isError()) {
			fail("Error running Verify BOM process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
	}
	
	@Test
	public void testChainWorkflow() {
		MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
		//Joe Block
		order.setBPartner(MBPartner.get(Env.getCtx(), 118));
		order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
		order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
		order.setDocStatus(DocAction.STATUS_Drafted);
		order.setDocAction(DocAction.ACTION_Complete);
		Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
		order.setDateOrdered(today);
		order.setDatePromised(today);
		order.saveEx();
		
		MOrderLine line1 = new MOrderLine(order);
		line1.setLine(10);
		//Azalea Bush
		line1.setProduct(MProduct.get(Env.getCtx(), 128));
		line1.setQty(new BigDecimal("1"));
		line1.setDatePromised(today);
		line1.saveEx();
		
		ProcessInfo pi = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
		if (pi.isError()) {
			fail("Failed to complete order" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
		order.load(getTrxName());
		assertEquals(DocAction.STATUS_Completed, order.getDocStatus(), "Expected Completed Status for Order");
		
		//dummy order for dummy shipment below
		MOrder order1 = new MOrder(Env.getCtx(), 0, getTrxName());
		//Joe Block
		order1.setBPartner(MBPartner.get(Env.getCtx(), 118));
		order1.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
		order1.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
		order1.setDocStatus(DocAction.STATUS_Drafted);
		order1.setDocAction(DocAction.ACTION_Complete);
		order1.setDateOrdered(today);
		order1.setDatePromised(today);
		order1.saveEx();
		
		MOrderLine line2 = new MOrderLine(order1);
		line2.setLine(10);
		//Azalea Bush
		line2.setProduct(MProduct.get(Env.getCtx(), 128));
		line2.setQty(new BigDecimal("1"));
		line2.setDatePromised(today);
		line2.saveEx();
		
		pi = MWorkflow.runDocumentActionWorkflow(order1, DocAction.ACTION_Complete);
		if (pi.isError()) {
			fail("Failed to complete order" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
			return;
		}
		order1.load(getTrxName());
		assertEquals(DocAction.STATUS_Completed, order1.getDocStatus(), "Expected Completed Status for Order");
		
		int Process_InOutConfirmation = 124;
		MWorkflow wf = new MWorkflow(Env.getCtx(), Process_InOutConfirmation, null);
		MWFNode processNode = new MWFNode(wf, "Process_InOut", "Process_InOut");
		processNode.set_ValueNoCheck("AD_Client_ID", getAD_Client_ID());
		processNode.setAction(MWFNode.ACTION_AppsProcess);
		processNode.setAD_Process_ID(109); //M_InOut Process
		processNode.setEntityType(MWFNode.ENTITYTYPE_UserMaintained);
		processNode.saveEx();
		MWFNodePara processNodePara = new MWFNodePara(Env.getCtx(), 0, null);
		processNodePara.set_ValueNoCheck("AD_Org_ID", 0);
		processNodePara.setAD_WF_Node_ID(processNode.getAD_WF_Node_ID());
		processNodePara.setAttributeName("Record_ID");
		processNodePara.setAttributeValue("@M_InOut_ID@");
		processNodePara.setEntityType(MWFNodePara.ENTITYTYPE_UserMaintained);
		processNodePara.saveEx();
		MWFNode docCompleteNode = new MWFNode(Env.getCtx(), 219, null);
		MWFNodeNext docCompleteNodeNext = new MWFNodeNext(docCompleteNode, processNode.getAD_WF_Node_ID());		
		docCompleteNodeNext.set_ValueNoCheck("AD_Client_ID", getAD_Client_ID());
		docCompleteNodeNext.setEntityType(MWFNodeNext.ENTITYTYPE_UserMaintained);
		docCompleteNodeNext.saveEx();
		
		try {
			//dummy shipment to make sure next m_inout_id != m_inoutconfirm_id
			int MM_Shipment=120;
			MInOut inout1 = new MInOut(order1, MM_Shipment, order1.getDateOrdered());
			inout1.setDocStatus(DocAction.STATUS_Drafted);
			inout1.setDocAction(DocAction.STATUS_Completed);
			inout1.saveEx();
			
			int MM_Shipment_With_Pick=148;
			MInOut inout = new MInOut(order, MM_Shipment_With_Pick, order.getDateOrdered());
			inout.setDocStatus(DocAction.STATUS_Drafted);
			inout.setDocAction(DocAction.STATUS_Completed);
			inout.saveEx();
			
			MInOutLine il = new MInOutLine(inout);
			il.setOrderLine(line1, 0, new BigDecimal("1"));
			il.saveEx();
			pi = MWorkflow.runDocumentActionWorkflow(inout, DocAction.ACTION_Complete);
			if (pi.isError()) {
				fail("Failed to complete shipment" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
				return;
			}
			inout.load(getTrxName());
			assertEquals(DocAction.STATUS_InProgress, inout.getDocStatus(), "Expected In Progress Status for Shipment");
			
			MInOutConfirm[] confirmations = inout.getConfirmations(true);
			assertEquals(1, confirmations.length, "Expected 1 Shipment Confirmation Document");
			
			assertTrue(confirmations[0].getM_InOutConfirm_ID() != inout.getM_InOut_ID(), "Test should run with M_InOut_ID != M_InOutConfirm_ID");
			
			pi = MWorkflow.runDocumentActionWorkflow(confirmations[0], DocAction.ACTION_Complete);
			if (pi.isError()) {
				fail("Failed to complete shipment confirmation" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
				return;
			}
			confirmations[0].load(getTrxName());
			assertEquals(DocAction.STATUS_Completed, confirmations[0].getDocStatus(), "Expected Completed Status for Shipment Confirmation");
			
			inout.load(getTrxName());
			assertEquals(DocAction.STATUS_Completed, inout.getDocStatus(), "Expected Completed Status for Shipment");
		} finally {		
			rollback();
			docCompleteNodeNext.deleteEx(true);
			processNodePara.deleteEx(true);
			processNode.deleteEx(true);
		}
	}

	@Test
	public void testCoreJavaProcessMapping() {
		Query query = new Query(Env.getCtx(), MProcess.Table_Name, "AD_Process_ID < 1000000 AND ClassName IS NOT NULL "
				+ " AND EXISTS (select 1 from ad_menu where isactive='Y' and ad_process_id=ad_process.ad_process_id)", getTrxName());
		List<MProcess> processes = query.setOnlyActiveRecords(true).list();
		for (MProcess process : processes) {
			ProcessCall pc = Core.getProcess(process.getClassname());
			assertNotNull(pc, "Failed to load ProcessCall instance for " + process.toString() + ", " + process.getClassname());
		}
	}	
	
	@Test
	public void testGetInstanceLog() {
		// Run the validate business partner process
		MProcess process = MProcess.get(SystemIDs.PROCESS_C_BPARTNER_VALIDATE);
		MPInstance pinstance = new MPInstance(process, 0, 0, null);
		MPInstancePara[] paras = pinstance.getParameters();
		for (MPInstancePara para : paras) {
			if (para.getParameterName().equals("C_BPartner_ID")) {
				para.setP_Number(DictionaryIDs.C_BPartner.JOE_BLOCK.id);
				para.saveEx();
				break;
			}
		}
		
		ProcessInfo pi = new ProcessInfo(process.getName(), SystemIDs.PROCESS_C_BPARTNER_VALIDATE);
		pi.setAD_PInstance_ID(pinstance.getAD_PInstance_ID());
		//use local process trx to create pinstance log immediately
		process.processIt(pi, null, true);
		assertTrue(!pi.isError(), pi.getSummary());
		
		MPInstanceLog[] logs = pinstance.getLog();
		assertTrue(logs != null && logs.length > 0, "Failed to retrieve process instance logs");
		assertEquals(pinstance.getAD_PInstance_ID(), logs[0].getAD_PInstance_ID(), "Invalid MPInstanceLog.AD_PInstance_ID value");
		assertTrue(logs[0].getAD_PInstance_Log_UU() != null && logs[0].getAD_PInstance_Log_UU().length() == 36, "Invalid MPInstanceLog.AD_PInstance_Log_UU value");
		assertEquals(X_AD_PInstance_Log.PINSTANCELOGTYPE_Result, logs[0].getPInstanceLogType(), "Invalid MPInstanceLog.PInstanceLogType value");
	}
}
