/***********************************************************
Copyright (C) 2022 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.eai.v1_0;

import java.util.Random;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.verisign.epp.codec.contact.EPPContactAddress;
import com.verisign.epp.codec.contact.EPPContactInfoResp;
import com.verisign.epp.codec.contact.EPPContactPostalDefinition;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPResult;
import com.verisign.epp.interfaces.EPPApplicationSingle;
import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.interfaces.EPPContact;
import com.verisign.epp.interfaces.EPPSession;
import com.verisign.epp.util.Environment;
import com.verisign.epp.util.TestThread;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Is a unit test of the com.verisign.epp.codec.contact package with
 * implementing the Internationalized Email Addresses in the Extensible
 * Provisioning Protocol (EPP).
 */
public class EPPEAITst 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 Logger cat = LoggerFactory.getLogger(EPPEAITst.class);

	/**
	 * EPP Domain associated with test
	 */
	private EPPContact contact = null;

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

	/**
	 * Current test iteration
	 */
	private int iteration = 0;

	/**
	 * Random instance for the generation of unique objects (hosts, IP addresses,
	 * etc.).
	 */
	private Random rd = new Random(System.currentTimeMillis());

	/**
	 * Allocates an {@code EPPEAITst} 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 EPPEAITst(String name) {
		super(name);
	}

	/**
	 * JUNIT test method to implement the {@code EPPEAITst TestCase}. Each
	 * sub-test will be invoked in order to satisfy testing the EPPDomain
	 * interface.
	 */
	public void testContact() {
		int numIterations = 1;

		String iterationsStr = System.getProperty("iterations");

		if (iterationsStr != null) {
			numIterations = Integer.parseInt(iterationsStr);
		}

		for (iteration = 0; (numIterations == 0) || (iteration < numIterations); iteration++) {
			printStart("Test Suite");

			contactCreate();

			contactUpdate();

			contactInfo();

			printEnd("Test Suite");
		}
	}

	/**
	 * Unit test of {@code EPPContact.sendCreate} for a contact with an EAI
	 * address. There are two create commands sent:
	 * <ol>
	 * <li>Send a contact create command for a contact with the generated ID
	 * starting with "EAI". The expectation is for the server to successfully
	 * process the create command.</li>
	 * <li>Send a contact create command for a pre-defined contact ID
	 * "EAI-notsupported" to trigger the server to handle the command as if the
	 * client doesn't support EAI. In this case the email element sent
	 * contains an EAI value, and it's expected that a 2308 EPP error will be
	 * returned.</li>
	 * </ol>
	 */
	public void contactCreate() {
		printStart("contactCreate");

		EPPResponse response;

		String theName = this.makeContactName();

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("contactCreate: Contact create for " + theName);
			contact.setTransId("ABC-12345-XYZ");
			contact.setAuthorizationId("ClientXYZ");
			contact.addContactId(theName);
			contact.setVoicePhone("+1.7035555555");
			contact.setVoiceExt("123");
			contact.setFaxNumber("+1.7035555556");
			contact.setFaxExt("456");
			contact.setEmail("DörteSörensen@example.com");

			// Streets
			Vector streets = new Vector();
			streets.addElement("123 Example Dr.");
			streets.addElement("Suite 100");
			streets.addElement("This is third line");

			EPPContactAddress address = new EPPContactAddress();

			address.setStreets(streets);
			address.setCity("Dulles");
			address.setStateProvince("VA");
			address.setPostalCode("20166-6503");
			address.setCountry("US");

			EPPContactPostalDefinition name = new EPPContactPostalDefinition(EPPContactPostalDefinition.ATTR_TYPE_LOC);

			name.setName("Dörte Sörense");
			name.setOrg("Example Inc.");
			name.setAddress(address);

			contact.addPostalInfo(name);

			response = contact.sendCreate();

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

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("contactCreate: Contact create of unsupported EAI contact \"EAI-notsupported\"");
			contact.setTransId("ABC-12345-XYZ");
			contact.setAuthorizationId("ClientXYZ");
			contact.addContactId("EAI-notsupported");
			contact.setEmail("DörteSörensen@example.com");

			// Streets
			Vector streets = new Vector();
			streets.addElement("123 Example Dr.");

			EPPContactAddress address = new EPPContactAddress();

			address.setStreets(streets);
			address.setCity("Dulles");
			address.setStateProvince("VA");
			address.setPostalCode("20166-6503");
			address.setCountry("US");

			EPPContactPostalDefinition name = new EPPContactPostalDefinition(EPPContactPostalDefinition.ATTR_TYPE_LOC);

			name.setName("Dörte Sörense");
			name.setAddress(address);

			contact.addPostalInfo(name);

			response = contact.sendCreate();

			Assert.fail("contactCreate: Unexpected successful response = [" + response + "]");
		}
		catch (EPPCommandException e) {
			EPPResponse theResponse = e.getResponse();

			if (theResponse != null) {
				Assert.assertEquals(EPPResult.DATA_MGT_POLICY_VIOLATION, theResponse.getResult().getCode());
				System.out.println("contactCreate: Expected Error Response = [" + theResponse + "]\n\n");

			}
			else {
				handleException(e);
			}

		}

		printEnd("contactCreate");
	}

	/**
	 * Unit test of {@code EPPContact.sendUpdate} for a contact with an EAI
	 * address. There are two update commands sent:
	 * <ol>
	 * <li>Send a contact update command for a contact with the generated ID
	 * starting with "EAI". The expectation is for the server to successfully
	 * process the update command.</li>
	 * <li>Send a contact update command for a pre-defined contact ID
	 * "EAI-notsupported" to trigger the server to handle the command as if the
	 * client doesn't support EAI. In this case the email element sent
	 * contains an EAI value, and it's expected that a 2308 EPP error will be
	 * returned.</li>
	 * </ol>
	 */
	public void contactUpdate() {
		printStart("contactUpdate");

		EPPResponse response;

		String theName = this.makeContactName();

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("\ncontactUpdate: Contact update for " + theName);
			contact.setTransId("ABC-12345-XYZ");
			contact.addContactId(theName);

			contact.setEmail("DörteSörensen2@example.com");

			response = contact.sendUpdate();

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

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("\ncontactUpdate: Contact update of unsupported EAI contact \"EAI-notsupported\"");
			contact.setTransId("ABC-12345-XYZ");
			contact.addContactId("EAI-notsupported");

			contact.setEmail("DörteSörensen2@example.com");

			response = contact.sendUpdate();

			Assert.fail("contactUpdate: Unexpected successful response = [" + response + "]");
		}
		catch (EPPCommandException e) {
			EPPResponse theResponse = e.getResponse();

			if (theResponse != null) {
				Assert.assertEquals(EPPResult.DATA_MGT_POLICY_VIOLATION, theResponse.getResult().getCode());
				System.out.println("contactUpdate: Expected Error Response = [" + theResponse + "]\n\n");

			}
			else {
				handleException(e);
			}
		}

		printEnd("contactUpdate");
	}

	/**
	 * Unit test of {@code EPPContact.sendInfo} for a contact with an EAI
	 * address. There are two info commands sent:
	 * <ol>
	 * <li>Send a contact info command for a contact with the generated ID
	 * starting with "EAI". The expectation is for the server to return an e-mail
	 * address containing an EAI value.</li>
	 * <li>Send a contact info command for a pre-defined contact ID
	 * "EAI-notsupported" to trigger the server to handle the command as if the
	 * client doesn't support EAI. In this case, it's expected that a 2308 EPP
	 * error will be returned, since the email element is required and it
	 * contains an EAI value.</li>
	 * </ol>
	 */
	public void contactInfo() {
		printStart("contactInfo");

		EPPContactInfoResp response;

		String theName = this.makeContactName();

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("\ncontactInfo: Contact info for " + theName);
			contact.setTransId("ABC-12345-XYZ");
			contact.addContactId(theName);

			response = contact.sendInfo();

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

			// Contact Id
			System.out.println("contactInfo: id = " + response.getId());

			Assert.assertNotNull(response.getEmail());

			// Contact E-mail
			System.out.println("contactInfo: email = " + response.getEmail());

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

		try {
			System.out.println("\n----------------------------------------------------------------");
			System.out.println("\ncontactInfo: Contact info of unsupported EAI contact \"EAI-notsupported\"");
			contact.setTransId("ABC-12345-XYZ");
			contact.addContactId("EAI-notsupported");

			response = contact.sendInfo();

			Assert.fail("contactInfo: Unexpected successful response = [" + response + "]");
		}
		catch (EPPCommandException e) {
			EPPResponse theResponse = e.getResponse();

			if (theResponse != null) {
				Assert.assertEquals(EPPResult.DATA_MGT_POLICY_VIOLATION, theResponse.getResult().getCode());
				System.out.println("contactInfo: Expected Error Response = [" + theResponse + "]\n\n");

			}
			else {
				handleException(e);
			}
		}

		printEnd("contactInfo");
	}

	/**
	 * 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
		session.setTransId("ABC-12345-XYZ");

		session.setVersion("1.0");

		session.setLang("en");

		// Initialize the session
		try {
			session.initSession();
		}

		catch (EPPCommandException e) {
			EPPResponse response = session.getResponse();

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

				Assert.fail("initSession Error : " + e);
			}
		}

		printEnd("initSession");
	}

	// End EPPEAITst.initSession()

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

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

		// End the session
		try {
			session.endSession();
		}

		catch (EPPCommandException e) {
			EPPResponse response = session.getResponse();

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

			else // Other error
			{
				e.printStackTrace();

				Assert.fail("initSession Error : " + e);
			}
		}

		printEnd("endSession");
	}

	// End EPPEAITst.endSession()

	/**
	 * JUNIT {@code setUp} method, which sets the default client Id to
	 * "theRegistrar".
	 */
	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");
					}

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

			session.setClientID(Environment.getProperty("EPP.Test.clientId", "ClientX"));
			session.setPassword(Environment.getProperty("EPP.Test.password", "foo-BAR2"));
		}

		catch (Exception e) {
			e.printStackTrace();

			Assert.fail("Error initializing the session: " + e);
		}

		initSession();

		// System.out.println("out init");
		contact = new EPPContact(session);
	}

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

	/**
	 * JUNIT {@code suite} static method, which returns the tests associated with
	 * {@code EPPEAITst}.
	 *
	 * @return Test suite
	 */
	public static Test suite() {
		TestSuite suite = new TestSuite(EPPEAITst.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;
	}

	/**
	 * 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(Exception aException) {
		EPPResponse theResponse = null;
		if (aException instanceof EPPCommandException) {
			theResponse = ((EPPCommandException) aException).getResponse();
		}

		aException.printStackTrace();

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

		else {
			Assert.fail("General Error : " + aException);
		}
	}

	/**
	 * 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
	 *           Command line 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("EPPSessionTst Thread " + i, EPPEAITst.suite());

				thread.start();
			}
		}

		else { // Single threaded mode.
			junit.textui.TestRunner.run(EPPEAITst.suite());
		}

		try {
			app.endApplication();
		}

		catch (EPPCommandException e) {
			e.printStackTrace();

			Assert.fail("Error ending the EPP Application: " + e);
		}
	}

	/**
	 * Makes a unique contact name using the current time.
	 *
	 * @return Unique contact name {@code String}
	 */
	public String makeContactName() {
		long tm = System.currentTimeMillis();

		return new String("EAI" + String.valueOf(tm + rd.nextInt(5)).substring(7));
	}

	/**
	 * 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
	 */
	private void printStart(String aTest) {
		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ", iteration " + iteration + ": ");

			cat.info(Thread.currentThread().getName() + ", iteration " + iteration + ": " + 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
	 */
	private void printEnd(String aTest) {
		System.out.println("****************************************************************");

		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ", iteration " + iteration + ": ");

			cat.info(Thread.currentThread().getName() + ", iteration " + iteration + ": " + aTest + " End");
		}

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

		System.out.println("\n");
	}

	/**
	 * Print message
	 *
	 * @param aMsg
	 *           message to print
	 */
	private void printMsg(String aMsg) {
		if (Thread.currentThread() instanceof TestThread) {
			System.out.println(Thread.currentThread().getName() + ", iteration " + iteration + ": " + aMsg);

			cat.info(Thread.currentThread().getName() + ", iteration " + iteration + ": " + aMsg);
		}

		else {
			System.out.println(aMsg);

			cat.info(aMsg);
		}
	}

	/**
	 * Print error message
	 *
	 * @param aMsg
	 *           errpr message to print
	 */
	private void printError(String aMsg) {
		if (Thread.currentThread() instanceof TestThread) {
			System.err.println(Thread.currentThread().getName() + ", iteration " + iteration + ": " + aMsg);

			cat.error(Thread.currentThread().getName() + ", iteration " + iteration + ": " + aMsg);
		}

		else {
			System.err.println(aMsg);

			cat.error(aMsg);
		}
	}

}