/***********************************************************
Copyright (C) 2024 VeriSign, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
***********************************************************/
package com.verisign.epp.interfaces;

// Log4j imports
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// EPP Imports
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPResult;
import com.verisign.epp.util.Environment;
import com.verisign.epp.util.TestThread;

// JUNIT Imports
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Is a unit test of the {@code EPPHttpSession} class. The unit test will
 * initialize a session with an EPP Server and end a session with an EPP Server.
 * The configuration file used by the unit test defaults to "epp.config", but
 * can be changed by passing the file path as the first command line argument.
 * The unit test can be run in multiple threads by setting the "threads" system
 * property. For example, the unit test can be run in 2 threads with the
 * configuration file "../../epp.config" with the following command:<br>
 * <br>
 * java com.verisign.epp.interfaces.EPPHttpSessionTst -Dthreads=2
 * ../../epp.config <br>
 * <br>
 * NOTE: The test will only work if there is at least one EPP Command Mapping
 * configured (i.e. Domain). Without one EPP Command Mapping, the XML Schema
 * validation will fail when the client parses the greeting message from the EPP
 * Server Stub.
 */
public class EPPHttpSessionTst extends TestCase {
	/**
	 * Handle to the Singleton EPP Application instance
	 * ({@code EPPApplicationSingle})
	 */
	private static EPPApplicationSingle app = EPPApplicationSingle.getInstance();

	/** Name of configuration file to use for test (default = epp.config). */
	private static String configFileName = "epp.config";

	/** Logging category */
	private static final Logger cat = LoggerFactory.getLogger(EPPHttpSessionTst.class);

	/** EPP Session associated with test */
	private EPPSession session = null;

	/**
	 * Allocates an {@code EPPHttpSessionTst} with a logical name. The
	 * constructor will initialize the base class {@code TestCase} with the
	 * logical name.
	 *
	 * @param name
	 *           Logical name of the test
	 */
	public EPPHttpSessionTst(String name) {
		super(name);
	}

	/**
	 * JUNIT test method to implement the {@code EPPHttpSessionTst
	 * TestCase}. Each sub-test will be invoked in order to satisfy testing the
	 * initialization and ending of an EPP Session with an EPP Server.
	 */
	public void testSession() {
		initSession();
		doHello();
		doPoll();
		doPollConsume();
		endSession();
	}

	/**
	 * Unit test of {@code EPPSession.initSession}. The session attribute is
	 * initialized with the attributes defined in the EPP sample files.
	 */
	private void initSession() {
		printStart("initSession");

		// Set attributes for initSession
		this.session.setClientID(Environment.getProperty("EPP.Test.clientId", "ClientX"));
		this.session.setPassword(Environment.getProperty("EPP.Test.password", "foo-BAR2"));
		this.session.setTransId("ABC-12345-XYZ");
		this.session.setVersion("1.0");
		this.session.setLang("en");

		// Initialize the session
		try {
			this.session.initSession();
		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		printEnd("initSession");
	}

	/**
	 * Unit test of {@code EPPSession.sendHello} command. The session attribute
	 * is initialized with the attributes defined in the EPP sample files.
	 */
	private void doHello() {
		printStart("Session Hello");

		try {
			this.session.hello();
		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		printEnd("Session Hello");
	}

	/**
	 * Unit test of {@code EPPSession.sendPoll} command.
	 */
	private void doPoll() {
		printStart("doPoll");

		EPPResponse response = null;
		Long numMsgs = null;

		try {
			this.session.setTransId("ABC-12345-XYZ");
			this.session.setPollOp(EPPSession.OP_REQ);
			response = this.session.sendPoll();

			// -- Output all of the response attributes
			System.out.println("doPoll: Response = [" + response + "]\n\n");

			numMsgs = response.getMsgQueueCount();

			if (numMsgs != null) {
				System.out.println("doPoll: # messages = " + numMsgs);
			}
		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		try {
			this.session.setTransId("ABC-12345-XYZ");
			this.session.setPollOp(EPPSession.OP_ACK);
			this.session.setMsgID("1234");
			response = this.session.sendPoll();

			// -- Output all of the response attributes
			System.out.println("doPoll: Response = [" + response + "]\n\n");
		}
		catch (EPPCommandException e) {
			// Expect error
			System.out.println("doPoll: Error Response = [" + e.getResponse() + "]\n\n");
		}

		printEnd("doPoll");
	}

	/**
	 * Read all of the poll queue messages from the poll queue via the
	 * {@code EPPSession.sendPoll} command.
	 */
	private void doPollConsume() {
		printStart("doPollConsume");

		EPPResponse response = null;

		try {
			this.session.setTransId("ABC-" + System.currentTimeMillis());
			this.session.setPollOp(EPPSession.OP_REQ);
			response = this.session.sendPoll();

			while (response.getResult().getCode() == EPPResult.SUCCESS_POLL_MSG) {

				System.out.println("doPollConsume: Message = [" + response + "]\n\n");

				// Acknowledge current message
				this.session.setTransId("ABC-" + System.currentTimeMillis());
				this.session.setPollOp(EPPSession.OP_ACK);
				this.session.setMsgID(response.getMsgQueue().getId());
				response = this.session.sendPoll();

				// Send next poll request
				this.session.setTransId("ABC-" + System.currentTimeMillis());
				this.session.setPollOp(EPPSession.OP_REQ);
				response = this.session.sendPoll();
			}

		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		printEnd("doPollConsume");
	}

	/**
	 * Unit test of {@code EPPSession.endSession}. The session with the EPP
	 * Server will be terminated.
	 */
	private void endSession() {
		printStart("endSession");

		this.session.setTransId("ABC-12345-XYZ");

		// End the session
		try {
			this.session.endSession();
		}
		catch (EPPCommandException e) {
			handleException(e);
		}

		printEnd("endSession");
	}

	/**
	 * JUNIT {@code setUp} method, which sets the default client Id to
	 * "theRegistrar".
	 */
	@Override
	protected void setUp() {
		try {
			String theSessionClassName = System.getProperty("EPP.SessionClass");

			if (theSessionClassName != null) {
				try {
					Class theSessionClass = Class.forName(theSessionClassName);

					if (!EPPSession.class.isAssignableFrom((theSessionClass))) {
						Assert.fail(theSessionClassName + " is not a subclass of EPPSession");
					}

					this.session = (EPPSession) theSessionClass.getDeclaredConstructor().newInstance();
				}
				catch (Exception ex) {
					Assert.fail("Exception instantiating EPP.SessionClass value " + theSessionClassName + ": " + ex);
				}
			}
			else {
				this.session = new EPPSession();
			}
		}
		catch (Exception e) {
			e.printStackTrace();
			Assert.fail("Error initializing the session: " + e);
		}
	}

	/**
	 * JUNIT {@code tearDown}, which currently does nothing.
	 */
	@Override
	protected void tearDown() {
	}

	/**
	 * JUNIT {@code suite} static method, which returns the tests associated with
	 * {@code EPPHttpSessionTst}.
	 *
	 * @return DOCUMENT ME!
	 */
	public static Test suite() {
		TestSuite suite = new TestSuite(EPPHttpSessionTst.class);

		String theConfigFileName = System.getProperty("EPP.ConfigFile");
		if (theConfigFileName != null) {
			configFileName = theConfigFileName;
		}

		try {
			app.initialize(configFileName);
		}
		catch (EPPCommandException e) {
			e.printStackTrace();
			Assert.fail("Error initializing the EPP Application: " + e);
		}

		return suite;
	}

	/**
	 * Unit test main, which accepts the following system property options:<br>
	 *
	 * <ul>
	 * <li>iterations Number of unit test iterations to run</li>
	 * <li>validate Turn XML validation on ({@code true}) or off ({@code false}).
	 * If validate is not specified, validation will be off.</li>
	 * </ul>
	 *
	 *
	 * @param args
	 *           Program arguments
	 */
	public static void main(String[] args) {
		// Override the default configuration file name?
		if (args.length > 0) {
			configFileName = args[0];
		}

		// Number of Threads
		int numThreads = 1;
		String threadsStr = System.getProperty("threads");

		if (threadsStr != null) {
			numThreads = Integer.parseInt(threadsStr);
		}

		// Run test suite in multiple threads?
		if (numThreads > 1) {
			// Spawn each thread passing in the Test Suite
			for (int i = 0; i < numThreads; i++) {
				TestThread thread = new TestThread("EPPHttpSessionTst Thread " + i, EPPHttpSessionTst.suite());
				thread.start();
			}
		}
		else { // Single threaded mode.
			junit.textui.TestRunner.run(EPPHttpSessionTst.suite());
		}

		try {
			app.endApplication();
		}
		catch (EPPCommandException e) {
			e.printStackTrace();
			Assert.fail("Error ending the EPP Application: " + e);
		}
	}

	/**
	 * Handle an {@code EPPCommandException}, which can be either a server
	 * generated error or a general exception. If the exception was caused by a
	 * server error, "Server Error : &lt;Response XML&gt;" will be specified. If
	 * the exception was caused by a general algorithm error, "General Error :
	 * &lt;Exception Description&gt;" will be specified.
	 *
	 * @param aException
	 *           Exception thrown during test
	 */
	public void handleException(EPPCommandException aException) {
		EPPResponse response = this.session.getResponse();

		// Is a server specified error?
		if ((response != null) && (!response.isSuccess())) {
			Assert.fail("Server Error : " + response);
		}
		else {
			aException.printStackTrace();
			Assert.fail("General Error : " + aException);
		}
	}

	/**
	 * Print the start of a test with the {@code Thread} name if the current
	 * thread is a {@code TestThread}.
	 *
	 * @param aTest
	 *           name for the test
	 */
	public static void printStart(String aTest) {
		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ": ");
			cat.info(Thread.currentThread().getName() + ": " + aTest + " Start");
		}

		System.out.println("Start of " + aTest);
		System.out.println("****************************************************************\n");
	}

	/**
	 * Print the end of a test with the {@code Thread} name if the current thread
	 * is a {@code TestThread}.
	 *
	 * @param aTest
	 *           name for the test
	 */
	public static void printEnd(String aTest) {
		System.out.println("****************************************************************");

		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ": ");
			cat.info(Thread.currentThread().getName() + ": " + aTest + " End");
		}

		System.out.println("End of " + aTest);
		System.out.println("\n");
	}
}
