package routines;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;

import routines.LcagStringTools.StringType;

class LcagStringToolsTest {
	private static final String CSVText = "A;B;C\nOne;Two;Tree\nFour;Five;Six\n";

	@Test
	void testGetStringType() throws IOException {
		String text;
		StringType result;

		text = LcagFileTools.readResourceFile("TIBCO_EBX.html");
		result = LcagStringTools.getStringType(text);
		assertEquals(StringType.HTML, result);

		text = LcagFileTools.readResourceFile("EmailBodyTemplate.html");
		result = LcagStringTools.getStringType(text);
		assertEquals(StringType.HTML, result);

		text = LcagFileTools.readResourceFile("MDS_City.xml");
		result = LcagStringTools.getStringType(text);
		assertEquals(StringType.XML, result);

		text = LcagFileTools.readResourceFile("SendMailEmbeddedAttachments.json");
		result = LcagStringTools.getStringType(text);
		assertEquals(StringType.JSON, result);

	}

	@Test
	void testIsEmpty() {
		String s = String.format("%s", null);

		assertTrue(LcagStringTools.isEmpty(null));
		assertTrue(LcagStringTools.isEmpty(""));
		assertTrue(LcagStringTools.isEmpty("   "));
		assertTrue(LcagStringTools.isEmpty("(null)"));
		assertTrue(LcagStringTools.isEmpty(s));

		assertFalse(LcagStringTools.isEmpty("Hi "));
		assertFalse(LcagStringTools.isEmpty(2));
		assertFalse(LcagStringTools.isEmpty((Integer) 28));
	}

	@Test
	void testIsEqualTo() {
		Date aDate = new Date();
		String aDateString = new SimpleDateFormat("yyyy-mm-dd").format(aDate);
		assertTrue(LcagStringTools.isEqualTo("abc", "abc"));
		assertTrue(LcagStringTools.isEqualTo("23", new Integer(23)));
		assertTrue(LcagStringTools.isEqualTo("-2222", new Long(-2222L)));
		assertTrue(LcagStringTools.isEqualTo(null, null));
		assertTrue(LcagStringTools.isEqualTo("true", new Boolean(true)));

		assertFalse(LcagStringTools.isEqualTo("a", null));
		assertFalse(LcagStringTools.isEqualTo(null, "a"));
		assertFalse(LcagStringTools.isEqualTo("abc", "ABC"));
		assertFalse(LcagStringTools.isEqualTo("2222", new Long(3333L)));

		// Not supported yet: Cause rounding problems
		// assertFalse(LcagStringTools.isEqualTo("1.23", new Double(1.23f)) );
		// assertFalse(LcagStringTools.isEqualTo("2.3", new Float(2.31f)));
		// assertTrue(LcagStringTools.isEqualTo(aDateString, aDate) ); // Date
		// LcagStringTools.toString() returns very long date format
	}

	@Test
	void testIsEqualToIgnoreCase() {
		Date aDate = new Date();
		String aDateString = new SimpleDateFormat("yyyy-mm-dd").format(aDate);
		assertTrue(LcagStringTools.isEqualToIgnoreCase("abc", "abc"));
		assertTrue(LcagStringTools.isEqualToIgnoreCase("abc", "ABC"));
		assertTrue(LcagStringTools.isEqualToIgnoreCase("AbC", "ABC"));
		assertTrue(LcagStringTools.isEqualToIgnoreCase("23", new Integer(23)));
		assertTrue(LcagStringTools.isEqualToIgnoreCase("-2222", new Long(-2222L)));
		assertTrue(LcagStringTools.isEqualToIgnoreCase(null, null));
		assertTrue(LcagStringTools.isEqualToIgnoreCase("true", new Boolean(true)));

		assertFalse(LcagStringTools.isEqualToIgnoreCase("a", null));
		assertFalse(LcagStringTools.isEqualToIgnoreCase(null, "a"));
		assertFalse(LcagStringTools.isEqualToIgnoreCase("abcd", "ABC"));
		assertFalse(LcagStringTools.isEqualToIgnoreCase("2222", new Long(3333L)));

		// Not supported yet: Cause rounding problems
		// assertFalse(LcagStringTools.isEqualToIgnoreCase("1.23", new Double(1.23f)) );
		// assertFalse(LcagStringTools.isEqualToIgnoreCase("2.3", new Float(2.31f)));
		// assertTrue(LcagStringTools.isEqualToIgnoreCase(aDateString, aDate) ); // Date
		// LcagStringTools.toString() returns very long date format
	}

	@Test
	void testJoinIgnoreNull() {
		String[] testInputArray = { "a", "b", "c" };
		List<String> testInputList = Arrays.asList(testInputArray);

		assertEquals("", LcagStringTools.joinIgnoreNull(", ", null));
		assertEquals("a", LcagStringTools.joinIgnoreNull(", ", "a"));
		assertEquals("a, b, c", LcagStringTools.joinIgnoreNull(", ", "a", "b", "c"));
		assertEquals("a, b, c", LcagStringTools.joinIgnoreNull(", ", "a", "b", null, "c"));
		assertEquals("a, b, c", LcagStringTools.joinIgnoreNull(", ", testInputArray));
		assertEquals("a, b, c", LcagStringTools.joinIgnoreNull(", ", testInputList));

		Integer i = 28;
		Double d = 1.2;
		Boolean b = true;
		assertEquals("a, 1.2, 28, true", LcagStringTools.joinIgnoreNull(", ", "a", 1.2, 28, true));
		assertEquals("a, 1.2, 28, true", LcagStringTools.joinIgnoreNull(", ", "a", d, i, b));

		assertFalse("x, a, b, c".equals(LcagStringTools.joinIgnoreNull(", ", "X", testInputArray)),
				"Mixed simple variables and Lists are not supported");
	}

	@Test
	void testJoinAll() {
		String[] testInputArray = { "a", "b", "c" };
		List<String> testInputList = Arrays.asList(testInputArray);

		assertEquals("a, n/a", LcagStringTools.joinAll(", ", "n/a", "a", null));
		assertEquals("a", LcagStringTools.joinAll(", ", "n/a", "a"));
		assertEquals("a, b, c", LcagStringTools.joinAll(", ", "n/a", "a", "b", "c"));
		assertEquals("a, b, n/a, c", LcagStringTools.joinAll(", ", "n/a", "a", "b", null, "c"));
		assertEquals("a, b, c", LcagStringTools.joinAll(", ", "n/a", testInputArray));
		assertEquals("a, b, c", LcagStringTools.joinAll(", ", "n/a", testInputList));

		Integer i = 28;
		Double d = 1.2;
		Boolean b = true;
		assertEquals("a, 1.2, 28, true", LcagStringTools.joinAll(", ", "n/a", "a", 1.2, 28, true));
		assertEquals("a, 1.2, 28, true", LcagStringTools.joinAll(", ", "n/a", "a", d, i, b));

		assertFalse("x, a, b, c".equals(LcagStringTools.joinAll(", ", "n/a", "X", testInputArray)),
				"Mixed simple variables and Lists are not supported");
	}

	@Test
	void testToString() {
		assertEquals("", LcagStringTools.toString(null));
		assertEquals("Hello", LcagStringTools.toString("Hello"));
		assertEquals("9.9", LcagStringTools.toString(Double.valueOf(9.9)));
		assertEquals("9", LcagStringTools.toString(Integer.valueOf(9)));
		assertEquals("true", LcagStringTools.toString(Boolean.valueOf(true)));
	}

	@Test
	void testAsList() {
		String[] expected = { "A", "B", "C" };

		assertArrayEquals(LcagStringTools.asList("A;B;C", ";").toArray(), expected, "Most common Use Case");
		assertArrayEquals(LcagStringTools.asList("A;B,C", "[;,]").toArray(), expected, "test with multiple seperators");

		String[] expected2 = { "A", "B+B", "C#C", "D" };
		assertArrayEquals(LcagStringTools.asList("A##B+B##C#C##D", "##").toArray(), expected2,
				"test with multiple char seperator");
		assertArrayEquals(LcagStringTools.asList("A##B+B//C#C##D", "##|//").toArray(), expected2,
				"test with multiple char seperators ##  and //");

		String[] expectedSame = { "A;B;C" };
		assertArrayEquals(LcagStringTools.asList("A;B;C", "#").toArray(), expectedSame,
				"Result should be input string (not matching regex seperator");

	}

	@Test
	void testFillBlanksUpcase() {
		assertEquals("ABC  ", LcagStringTools.fillBlanksUpcase("ABC", 5));
		assertEquals("ABC  ", LcagStringTools.fillBlanksUpcase("abc", 5));
		assertNull(LcagStringTools.fillBlanksUpcase(null, 5));
	}

	@Test
	void testResolveVariables() throws IOException, ParseException {
		Map<String, Object> globalMap = new HashMap<String, Object>();
		Date today = new Date();

		String todayString = LcagDateTime.DATE_FORMAT_ISO8601.format(today);
		String tomorrowString = LcagDateTime.DATE_FORMAT_ISO8601.format(LcagDateTime.addDays(today, 1));
		String yesterdayString = LcagDateTime.DATE_FORMAT_ISO8601.format(LcagDateTime.addDays(today, -1));
		String txt;
		String result, expected;

		// test round brackets that are not a variable
		expected = "Dear colleague(s),";
		result = LcagStringTools.resolveVariables(expected);
		assertEquals(expected, result);

		// test round brackets:
		result = LcagStringTools.resolveVariables("$(today)");
		assertEquals(todayString, result);

		result = LcagStringTools.resolveVariables("V:/EBX/data/01_Outbound/MDM_Light/${now}");
		assertTrue(result.endsWith("Z")); // Expected default timestamp format with UTC timestamp indicated by 'Z' at
											// the end

		result = LcagStringTools.resolveVariables("V:/EBX/data/01_Outbound/${now#yyyy-MM-dd}/MDM_Light");
		expected = "V:/EBX/data/01_Outbound/" + todayString + "/MDM_Light";
		assertEquals(expected, result);

		// Test that ${today} yields same result as ${now#yyyy-MM-dd}:
		result = LcagStringTools.resolveVariables("V:/EBX/data/01_Outbound/${today}/MDM_Light");
		assertEquals(expected, result);

		result = LcagStringTools.resolveVariables("V:/EBX/data/01_Outbound/${today}/MDM_Light_${now#HH-mm}");
		assertTrue(result.startsWith(expected));

		result = LcagStringTools.resolveVariables("${tomorrow}");
		assertEquals(tomorrowString, result);

		result = LcagStringTools.resolveVariables("${yesterday}");
		assertEquals(yesterdayString, result);

		// provide some other custom variables:
		globalMap.put("MDM_Cargo_MDM_DataModel", "LCAG_MDM_4_TST1_REG");
		globalMap.put("MDM_Cargo_MDM_Container", "LCAG_MDM_4_TST1_REG");
		globalMap.put("env_jobTimestamp", today);
		globalMap.put("EntityType", "Station");
		globalMap.put("Code", "PAD");

		result = LcagStringTools.resolveVariables("MDM entity code is ${Code}", globalMap);
		expected = "MDM entity code is PAD";
		assertEquals(expected, result);

		txt = LcagFileTools.readTextFile("src/test/resources/Empty_MDM_Station_TalendUpdateReportUI.xml");
		result = LcagStringTools.resolveVariables(txt, globalMap);

		assertFalse(result.contains("${"), "Not all variables are resolved");

		// Test JSON data extraction by JSON Path
		txt = LcagFileTools.readResourceFile("MDM-EBX-WorkflowData.json");
		globalMap.put("jsonData", txt);
		result = LcagStringTools.resolveVariables("${jsonData/ChangedTable[*]/TableName}", globalMap);
		assertEquals("BE_AircraftLoadability, BE_AircraftLoadability2", result);

		// HTML formatting tests:
		// ==========================================================================

		// Test Passing a CSV String:
		globalMap.put("myTable", CSVText);
		result = LcagStringTools.resolveVariables("Hello,\n\n ${myTable#HTML}\n\nGood Bye!", globalMap);

		assertEquals(3, StringUtils.countMatches(result, "</th>"), "HTML table shall have 3 columns");
		assertEquals(6, StringUtils.countMatches(result, "</td>"), "HTML table shall have 6 cells");

		// Test what happens if we pass a file name that does not exist (should return
		// the file name)
		String fileNameThatDoesNotExist = "FileDoesNotExist.csv";
		String formatStr = "Hello,\n\n %s\n\nGood Bye!";
		String strToResolve = String.format(formatStr, "${myTable#HTML}");

		globalMap.put("myTable", fileNameThatDoesNotExist);
		expected = String.format(formatStr, fileNameThatDoesNotExist);
		result = LcagStringTools.resolveVariables(strToResolve, globalMap);
		assertEquals(expected, result);

	}

	@Test
	void testResolveEnvironmentVariables() {
		Map<String, String> tc = new HashMap<String, String>();

		tc.put("${OS}", "Windows_NT");
		tc.put("xyz${OS}-${OS}=123", "xyzWindows_NT-Windows_NT=123");

		tc.put("Hello ${not-exists} World", "Hello ${not-exists} World");

		for (Entry<String, String> s : tc.entrySet()) {
			String txt = s.getKey();
			String expected = s.getValue();

			String result = LcagStringTools.resolveEnvironmentVariables(txt);

			assertEquals(expected, result);
		}

	}

	@Test
	void testSplitString() {
		assertNull(LcagStringTools.splitString(null, ";", 2));

		// return first value after split:
		assertEquals("B", LcagStringTools.splitString("A;B;C", ";", 1));
		// return second value after split:
		assertEquals("C", LcagStringTools.splitString("A;B;C", ";", 2));

		// return null because no third value exists
		assertNull(LcagStringTools.splitString("A;B;C", ";", 3));
		// return null because no exact splitting
		assertNull(LcagStringTools.splitString("A;B;C", "; ", 3));
		// return null because no exact splitting delimiter does not match
		assertNull(LcagStringTools.splitString("A;B;C", ",", 3));
	}

	@Test
	void testSelectCaseSensitiveIfEqual() {
		String result;

		result = LcagStringTools.selectCaseSensitiveIfEqual("Hello", "HELLO");
		assertEquals("Hello", result);

		result = LcagStringTools.selectCaseSensitiveIfEqual("HELLO", "Hello");
		assertEquals("Hello", result);

		result = LcagStringTools.selectCaseSensitiveIfEqual("HELLO Lisa", "Hello");
		assertEquals("HELLO Lisa", result, "Should return first argument if different");

		result = LcagStringTools.selectCaseSensitiveIfEqual("HELLO", "Hello Lisa");
		assertEquals("HELLO", result, "Should return first argument if different");
	}

	@Test
	void testSelectBestOf() {
		String result;

		result = LcagStringTools.selectBestOf(null, "HELLO");
		assertEquals("HELLO", result, "Rule 1: take given value");

		result = LcagStringTools.selectBestOf("HELLO Lisa", "Hello");
		assertEquals("HELLO Lisa", result, "Rule 2: Should return the longer one if both start the same");

		result = LcagStringTools.selectBestOf("HELLO", "12345");
		assertEquals("HELLO", result, "Rule 3: Should prefer first if both have same length but differ");

		result = LcagStringTools.selectBestOf("HELLO Lisa", "HELLO Wilhelm");
		assertEquals("HELLO Lisa", result, "Rule 3: Should always prefer first if both are very different");

		result = LcagStringTools.selectBestOf("HELLO Wilhelm", "HELLO Lisa");
		assertEquals("HELLO Wilhelm", result, "Rule 3: Should always prefer first if both are very different");

		result = LcagStringTools.selectBestOf("HELLO", "Hello Lisa");
		assertEquals("Hello Lisa", result, "Rule 2: Should return the longer one if both start the same");

		result = LcagStringTools.selectBestOf("Hello", "HELLO");
		assertEquals("Hello", result, "Rule 4: Should prefer case-sensitive if equal except case");

		result = LcagStringTools.selectBestOf("HELLO", "Hello");
		assertEquals("Hello", result, "Rule 4: Should prefer case-sensitive if equal except case");

	}

	@Test
	void testApplyChangeRule() {
		String result;

		String tcCase = "prefer case-sensitive";
		result = LcagStringTools.applyChangeRule("Hello", "HELLO", tcCase);
		assertEquals("Hello", result);

		result = LcagStringTools.applyChangeRule("HELLO", "Hello", tcCase);
		assertEquals("Hello", result);

		tcCase = "to-upper-case";
		result = LcagStringTools.applyChangeRule("hello", "Hello", tcCase);
		assertEquals("HELLO", result, String.format("'%s' test should always return first arg in UPPER case", tcCase));

		result = LcagStringTools.applyChangeRule("hello frank", "Hello bettina", tcCase);
		assertEquals("HELLO FRANK", result,
				String.format("'%s' test should always return first arg in UPPER case", tcCase));

		tcCase = "to-lower-case";
		result = LcagStringTools.applyChangeRule("HELLO", "Hello", tcCase);
		assertEquals("hello", result, String.format("'%s' test should always return first arg in lower case", tcCase));

		tcCase = "select best-of";
		result = LcagStringTools.applyChangeRule("hello frank", "Hello bettina", tcCase);
		assertEquals("hello frank", result,
				String.format("'%s' test should always return arg because it differs to second", tcCase));

	}

	@Test
	void testConvertToObject() throws ParseException {
		String result;

		result = LcagDateTime.MDM_DATETIME_FORMAT.format(
				LcagStringTools.convertToObject("Date", "07-Mar-2023 16:30:11.412", "dd-MMM-yyyy HH:mm:ss.SSS"));
		assertEquals("2023-03-07T16:30:11", result);
	}

}
