package de.hsadmin.remote;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class DomainTest {

	private static final String MODULE = "domain";
	
	private XmlRpcClient client;
	private RemoteCASHelper cas;

	@Before
	public void setUp() throws Exception {
		client = RemoteTestHelper.getClient();
		cas = new RemoteCASHelper();
	}

	@After
	public void tearDown() throws Exception {
		client = null;
		cas = null;
	}

	@Test
	public void testSearchAllAsPacAdmin() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> whereParams = new HashMap<String, String>();
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				whereParams };
		try {
			Object execute = client.execute(MODULE + ".search", params);
			Object[] result = (Object[]) execute;
			assertEquals(2, result.length);
			for (Object o : result) {
				if (o instanceof Map<?, ?>) {
					Map<?, ?> row = (Map<?, ?>) o;
					assertTrue("aaa00".equals(row.get("user")) || "aaa00-admin".equals(row.get("user")));
				} else {
					fail("map expected");
				}
			}
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
	}

	@Test
	public void testUpdate() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> setParams = new HashMap<String, String>();
		Map<String, String> whereParams = new HashMap<String, String>();
		setParams.put("user", "aaa00");
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams, whereParams };
		try {
			Object execute = client.execute(MODULE + ".update", params);
			assertNotNull(execute);
			fail("exception expected");
		} catch (XmlRpcException e) {
		}
	}

	@Test
	public void testCreate() throws UnknownHostException, IOException {
		int count = getDomsCount();
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> setParams = new HashMap<String, String>();
		setParams.put("name", "f6n.de");
		setParams.put("user", "aaa00-admin");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams };
		try {
			Object execute = client.execute(MODULE + ".add", params);
			assertTrue(execute instanceof Map<?, ?>);
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
		assertEquals(count + 1, getDomsCount());
		// TODO die beiden folgenden Tests sind etwas spezifisch für Version 2.2
		assertEquals("neue Domain ist nicht kompatibel zum Bisher",5,getDomOptionsCount());
//		testGreylistingOnOff(true, "sollte bei neuer Domain eingeschaltet sein");
//		testHtdocsfallbackOnOff(true, "sollte bei neuer Domain eingeschaltet sein");
//		testIncludesOnOff(true, "sollte bei neuer Domain eingeschaltet sein");
//		testIndexesOnOff(true, "sollte bei neuer Domain eingeschaltet sein");
//		testMultiviewsOnOff(true, "sollte bei neuer Domain eingeschaltet sein");
//		testBackupMxForExternamlMxOnOff(false, "sollte bei neuer Domain ausgeschaltet sein");
	}

	@Test
	public void testDelete() {
		int count = getDomsCount();
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> whereParams = new HashMap<String, String>();
		whereParams.put("name", "f6n.de");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				whereParams };
		try {
			Object execute = client.execute(MODULE + ".delete", params);
			assertNull(execute);
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
		assertEquals(count - 1, getDomsCount());
	}

	@Test
	public void testDomainOption() {
		// these tests build upon each other
		testZeroOptions();
		testOneValidOption();
		testTwoValidOptions();
		testZeroOptions();
	}

	@Test
	public void testInvalidOption() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, Object> setParams = new HashMap<String, Object>();
		Map<String, String> whereParams = new HashMap<String, String>();
		List<String> optionslist = new ArrayList<String>();
		optionslist.add("invalide");
		setParams.put("domainoptions", optionslist);
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams, whereParams };
		try {
			Object execute = client.execute(MODULE + ".update", params);
			assertNotNull(execute);
			fail("exception expected");
		} catch (XmlRpcException e) {
			assertEquals(getDomOptionsCount(),0);
		}
	}

	public void testOneValidOption() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, Object> setParams = new HashMap<String, Object>();
		Map<String, String> whereParams = new HashMap<String, String>();
		List<String> optionslist = new ArrayList<String>();
		optionslist.add("greylisting");
		setParams.put("domainoptions", optionslist);
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams, whereParams };
		try {
			Object execute = client.execute(MODULE + ".update", params);
			assertNotNull(execute);
			assertEquals(1, getDomOptionsCount());
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
	}

	public void testTwoValidOptions() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, Object> setParams = new HashMap<String, Object>();
		Map<String, String> whereParams = new HashMap<String, String>();
		List<String> optionslist = new ArrayList<String>();
		optionslist.add("htdocsfallback");
		optionslist.add("greylisting");
		setParams.put("domainoptions", optionslist);
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams, whereParams };
		try {
			Object execute = client.execute(MODULE + ".update", params);
			assertNotNull(execute);
			assertEquals(2, getDomOptionsCount());
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
	}

	public void testZeroOptions() {
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, Object> setParams = new HashMap<String, Object>();
		Map<String, String> whereParams = new HashMap<String, String>();
		List<String> optionslist = new ArrayList<String>();
		setParams.put("domainoptions", optionslist);
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				setParams, whereParams };
		try {
			Object execute = client.execute(MODULE + ".update", params);
			assertNotNull(execute);
			assertEquals(0, getDomOptionsCount());
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
	}

//	@Test
	public void testBackupMxForExternamlMx() throws UnknownHostException, IOException {
		testBackupMxForExternamlMxOnOff(true, "zuerst an");
		testBackupMxForExternamlMxOnOff(false, "als zweites aus");
		testBackupMxForExternamlMxOnOff(true, "zuletzt wieder an");
	}

	// TODO Fix this @Test
	public void testGreylisting() throws UnknownHostException, IOException {
		testGreylistingOnOff(true, "zuerst an");
		testGreylistingOnOff(false, "als zweites aus");
		testGreylistingOnOff(true, "zuletzt wieder an");
	}

	// TODO Fix this @Test
	public void testHtdocsfallback() throws UnknownHostException, IOException {
		testHtdocsfallbackOnOff(false, "zuerst aus");
		testHtdocsfallbackOnOff(true, "als zweites an");
		testHtdocsfallbackOnOff(false, "zuletzt wieder aus");
	}

//	@Test
	public void testIncludes() throws UnknownHostException, IOException {
		testIncludesOnOff(false, "zuerst aus");
		testIncludesOnOff(true, "als zweites an");
		testIncludesOnOff(false, "zuletzt wieder aus");
	}

//	@Test
	public void testIndexes() throws UnknownHostException, IOException {
		testIndexesOnOff(false, "zuerst aus");
		testIndexesOnOff(true, "als zweites an");
		testIndexesOnOff(false, "zuletzt wieder aus");
	}

//	@Test
	public void testMultiviews() throws UnknownHostException, IOException {
		testMultiviewsOnOff(true, "zuerst an");
		testMultiviewsOnOff(false, "als zweites aus");
		testMultiviewsOnOff(true, "zuletzt wieder an");
	}
	
	private void testBackupMxForExternamlMxOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "backupmxforexternalmx";
			}

			@Override
			public boolean isOptionConfigured(String domain)
					throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				return host == "";	// noch kein sinnvoller test
			}
		});
	}

	private void testGreylistingOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "greylisting";
			}

			@Override
			public boolean isOptionConfigured(String domain)
					throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				String[] query = {
							"HELO " + domain + "\n" ,
							"MAIL FROM: hsadmin-testing@" + domain + "\n" ,
							"RCPT TO: postmaster@" + domain + "\n" ,
							"DATA\n" ,
							".\n" ,
							"QUIT\n" } ;
				String[] answer = socketQuery(host, 25, query ) ;
				return answer[3].contains("450") && answer[3].contains("reylisting");
			}
		});
	}
		
	private void testHtdocsfallbackOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "htdocsfallback";
			}

			@Override
			public boolean isOptionConfigured(String domain)
					throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				String[] answer = socketQuery(host, 80, "GET / HTTP/1.1\n"
					+ "Host: x.y.z." + domain
					+ "User-Agent: hsAdmin Test\n"
					+ "Connection: close\n");
			//	answer = answer.substring(0, answer.indexOf("\n"));
				return answer[1].contains("404");
			}
		});
	}
				
	private void testIncludesOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "includes";
			}
			
			@Override
			public boolean isOptionConfigured(String domain)
				throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				// TODO dieser Test ist Humbug
				String[] answer = socketQuery(host, 80, "GET / HTTP/1.1\n"
						+ "Host: " + domain
						+ "User-Agent: hsAdmin Test\n"
						+ "Connection: close\n");
		//		answer = answer.substring(0, answer.indexOf("\n"));
				return answer[1].contains("404");
			}
		});
	}
				
	private void testIndexesOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "indexes";
			}
			
			@Override
			public boolean isOptionConfigured(String domain)
				throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				// TODO dieser Test ist Humbug
				String[] answer = socketQuery(host, 80, "GET / HTTP/1.1\n"
						+ "Host: " + domain
						+ "User-Agent: hsAdmin Test\n"
						+ "Connection: close\n");
			//	answer = answer.substring(0, answer.indexOf("\n"));
				return answer[1].contains("404");
			}
		});
	}
				
	private void testMultiviewsOnOff(boolean onoff, String message)
			throws UnknownHostException, IOException {
		testDomainptionOnOf(onoff, message, new DomainOptionTester() {

			@Override
			public String getOptionName() {
				return "multiviews";
			}
			
			@Override
			public boolean isOptionConfigured(String domain)
				throws UnknownHostException, IOException {
				String host = "test-h99.hostsharing.net";
				// TODO dieser Test ist Humbug
				String[] answer = socketQuery(host, 80, "GET / HTTP/1.1\n"
						+ "Host: " + domain
						+ "User-Agent: hsAdmin Test\n"
						+ "Connection: close\n");
			//	answer = answer.substring(0, answer.indexOf("\n"));
				return answer[1].contains("404");
			}
		});
	}

	private void testDomainptionOnOf( boolean onoff, String message, DomainOptionTester tester) throws UnknownHostException, IOException {
			String user = "aaa00";
			String domain = "example01.org";
			String grantingTicketURL = cas.getGrantingTicketURL(user);
			Map<String, Object> setParams = new HashMap<String, Object>();
			Map<String, String> whereParams = new HashMap<String, String>();
			List<String> optionslist = new ArrayList<String>();
			if(onoff) {
				optionslist.add(tester.getOptionName());
			}
			setParams.put("domainoptions", optionslist);
			whereParams.put("name", domain);
			Object[] params = new Object[] { user, 
					cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
					setParams, whereParams };
			try {
				Object execute = client.execute(MODULE + ".update", params);
				assertNotNull(execute);
				assertEquals(tester.getOptionName()+": "+message, onoff, tester.isOptionConfigured(domain));
			} catch (XmlRpcException e) {
			}
		}

	private int getDomsCount() {
		int count = 0;
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> whereParams = new HashMap<String, String>();
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				whereParams };
		try {
			Object execute = client.execute(MODULE + ".search", params);
			Object[] result = (Object[]) execute;
			count = result.length;
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
		return count;
	}

	private int getDomOptionsCount() {
		int count = 0;
		String user = "aaa00";
		String grantingTicketURL = cas.getGrantingTicketURL(user);
		Map<String, String> whereParams = new HashMap<String, String>();
		whereParams.put("name", "example01.org");
		Object[] params = new Object[] { user, 
				cas.getServiceTicket(grantingTicketURL, RemoteTestHelper.getBackendURL()), 
				whereParams };
		try {
			Object execute = client.execute(MODULE + ".search", params);
			Object[] result = (Object[]) execute;
			assertEquals(result.length, 1);
			assertTrue(result[0] instanceof Map<?, ?>);
			Map<?, ?> domainMap = (Map<?, ?>) result[0];
			assertNotNull(domainMap);
			Object[] optsList = (Object[])domainMap.get("domainoptions");
			if (optsList == null) {
				count = 0;
			} else {
				count = optsList.length;
			}
		} catch (XmlRpcException e) {
			fail(e.getMessage());
		}
		return count;
	}

	private static String[] socketQuery( String host, int port, String command) throws UnknownHostException, IOException{
		String[] query = { command } ;
		return socketQuery( host , port , query );
	}

	private static String[] socketQuery( String host, int port, String[] query) throws UnknownHostException, IOException{
		Socket socket = new Socket( host, port );
		String[] retval = socketQuery( socket, query );
		socket.close();
		return retval;
	}

	private static String[] socketQuery( Socket socket, String command) throws UnknownHostException, IOException{
		String[] query = { command } ;
		return socketQuery( socket , query );
	}

	private static String[] socketQuery( Socket socket, String[] query) throws UnknownHostException, IOException{
		int l = query.length;
		String[] retval = new String[l];
		retval[0] = socketQueryReader(socket);
		for(int i=0; i<l; ){
			socketQueryWriter( socket, query[i] );
			retval[i++] = socketQueryReader(socket);
		}
		return retval;
	}
	
	private static void socketQueryWriter( Socket socket, String buffer) throws IOException{
		PrintWriter printWriter =
			new PrintWriter(
				new OutputStreamWriter(
					socket.getOutputStream()));
		printWriter.print(buffer);
		printWriter.flush();
	}
	
	private static String socketQueryReader( Socket socket ) throws IOException{
		int maxbufferbytecount = 4099;
		BufferedReader bufferedReader =
			new BufferedReader(
				new InputStreamReader(
					socket.getInputStream()));
		char[] buffer = new char[maxbufferbytecount];
		int bufferbytecount = bufferedReader.read(buffer, 0, maxbufferbytecount); // blocks til end of transmission
		return new String(buffer, 0, bufferbytecount);
	}
}
