package com.enactor.pos.web;

import com.enactor.core.application.process.IApplicationProcessDataType;
import com.enactor.core.automation.AUT.AutomationException;
import com.enactor.core.automation.AUT.IAUTWebController;
import com.enactor.core.automation.component.IComponentAccessor;
import com.enactor.core.automation.selenium.SeleniumWebController;
import com.enactor.core.utilities.FileUtils;
import com.enactor.core.utilities.Logger;
import com.enactor.core.utilities.StringUtils;
import com.enactor.coreUI.selenium.SeleniumConfig;
import com.enactor.coreUI.selenium.WebDriverPool;
import com.enactor.pos.PosTestManager;
import com.enactor.pos.swing.ISwingAppStepsProvider;
import com.enactor.pos.swing.SwingAppStepsProvider;
import com.enactor.pos.test.BaseStepsProvider;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.WordUtils;
import org.openqa.selenium.By;
import org.testng.Assert;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WebPosStepsProvider extends BaseStepsProvider {

    private static final Logger logger = Logger.getLogger(WebPosStepsProvider.class);

    private static final IAUTWebController WEB_CONTROLLER;

    static {
        WEB_CONTROLLER = new SeleniumWebController();
        WEB_CONTROLLER.init();
    }

    private static final String BASKET_GRID_ROW_CLASS_NAME = "basket-grid-row";
    private static final String SIMPLE_SELECTION_ROW_CLASS_NAME = "selection-row";

    /* data type of the text input */
    private static final String TEXT_INPUT_DATA_TYPE_ATTRIBUTE_NAME = "data-type";

    /* date input data type */
    private static final String TEXT_INPUT_DATE_DATE_TYPE = "DATE";
    
    /* Checkbox input data type */
    private static final String CHECK_BOX = "checkbox";

    /* Inner text data type */
    private static final String INNER_TEXT = "innerText";
    /* Inner HTML data type */
    private static final String INNER_HTML = "innerHTML";

    /* percentage input data type */
    private static final String TEXT_INPUT_PERCENTAGE_DATA_TYPE = "PERCENTAGE";

    private static final String DEVICE_ID_INPUT_XPATH = "//input[@name='deviceId']";
    private static final String WEB_POS_HOST_NAME_INPUT_XPATH = "//input[@name='hostName']";
    private static final String DEVICE_ID_SUBMIT_BUTTON_XPATH = "//button[text() ='Submit']";

    private static final String XPATH_SELECTOR_FOR_ANY_ELEMENT = "//*[@test-component-id='%s']";
    private static final String XPATH_SELECTOR_FOR_RICH_TEXT_ELEMENT = "//*[@test-component-id='%s']//*[@rich-text-editor]";
    private static final String XPATH_SELECTOR_FOR_TEXT_INPUT = "//input[@test-component-id='%s']|//textarea[@test-component-id='%s']";

    private static final String XPATH_SELECTOR_BASKET_GRID_ROW_AND_HEADER_AREA = "//div[contains(@class, 'basket-grid-row')]|//*[@class = 'basket-ftr-row']|//*[@class = 'basket-core-row']|//*[@class = 'basket-header-area']";

    private static final String XPATH_SELECTOR_FOR_BASKET_GRID_ROW =
    		"//div[(contains(@class, 'basket-grid-row') or contains(@class, 'basket-core-row') or contains(@class, 'basket-ftr-row'))]//*[text() = '%s']";
    private static final String XPATH_SELECTOR_FOR_SELECTION_LIST_ITEM =
            "(//div[contains(@class, 'selection-row')][normalize-space() = '%s'])[1]" +
                    "|" + XPATH_SELECTOR_FOR_BASKET_GRID_ROW +
                    "|" + XPATH_SELECTOR_FOR_BASKET_GRID_ROW +
                    "|" + XPATH_SELECTOR_FOR_BASKET_GRID_ROW +
                    "|//div[contains(@class, 'basket-grid-row')]//*[contains(text(),'%s')]" +
                    "|//div[contains(@class, 'item-prodata') and contains(text(), '%s')]";
    private static final String XPATH_SELECTOR_FOR_COMBO_BOX =
            "//div[@test-component-id = '%s']/div[contains(@class, 'selected-item-text-wrapper')]";
    private static final String XPATH_SELECTOR_FOR_COMBO_BOX_OPTION =
            "//div[@test-component-id = '%s']//li[text() = '%s']";
    private static final String XPATH_SELECTOR_FOR_BUTTON = "//button[text() = '%s' and not(@disabled)]|//button[@test-component-id='%s' and not(@disabled)]|//button[text() = '%s' and not(@disabled)]|//button[text() = '%s' and not(@disabled)]";
    private static final String XPATH_SELECTOR_FOR_BASKET_RECEIPT = "//div[@class= 'basket-grid receipt' or @class= 'basket-area-container']";
    private static final String XPATH_SELECTOR_FOR_GRID_BODY = "//div[@class= 'basket-grid-body' or @class= 'basket-items-container basket-grid-body']";
    private static final String XPATH_SELECTOR_FOR_BASKET_RECEIPT_BODY = String.format("%s%s", XPATH_SELECTOR_FOR_BASKET_RECEIPT, XPATH_SELECTOR_FOR_GRID_BODY);
    private static final String XPATH_SELECTOR_FOR_SIMPLE_SELECTION = "//div[@class= 'selection-box']";

    private static final String VALUE = "value";
    private static final String TYPE = "type";
    private static final String PASSWORD = "password";

	public static final String ID = ".id";
	public static final String NAME = ".name";
	public static final String CLASS = ".class";
	public static final String XPATH = ".xpath";
	
    protected IAUTWebController getWebController() {
        return WEB_CONTROLLER;
    }

    protected void resetBrowserPool() {

        boolean isForcedSystemRestart = false;
        try {
            isForcedSystemRestart = getController().forceSystemExitOnRestart();
        } catch (AutomationException e) {
            logger.log(Logger.LOG_WARNING, "Failed to connect AUTController");
        }

        if (isForcedSystemRestart) {
            WebDriverPool.getInstance().resetBrowserPool();
            WEB_CONTROLLER.init();
        }
    }

    private void quitWebController() {
        getWebController().quit();
    }

    protected ISwingAppStepsProvider swingAppStepsProvider =
            new SwingAppStepsProvider(getController(), WAIT_WINDOW_DISPLAY);

    @Override
    public void checkWindow(String windowName) throws AutomationException {
        checkWindow(windowName, WAIT_WINDOW_DISPLAY);
    }

    @Override
    public void waitsForComponent(String component, String windowName) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider
                    .waitsForComponent(getComponentId(component), getWindowId(windowName), WAIT_COMPONENT_DISPLAY);
        } else {
            getWebController().waitForElement(By.xpath(getXpathForAnyElement(component)));
        }
    }

    @Override
    public void checkComponentNotDisplay(String component, String windowName) throws AutomationException {
    	if (!getWebController().atLeastSingleElementAvailable(By.xpath(getXpathForAnyElement(component)))) {
    	    return;
    	}
    	waits();
        Assert.assertFalse(getWebController().atLeastSingleElementAvailable(By.xpath(getXpathForAnyElement(component))),
                component + " should not be displayed in " + windowName);
    }

    @Override
    public String getComponentInnerContent(String component, String windowName) throws AutomationException {
        return getWebController().getAttributeInElement(By.xpath(getXpathForAnyElement(component)), INNER_HTML);
    }

    @Override
    public void pressButton(String buttonLabel, String windowName) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.pressButtonWithLabel(
                    getTestData(buttonLabel), getWindowId(windowName), getPosButtonComponentIdList(), WAIT_BUTTON_DISPLAY);
        } else {
            int basketItemsCountBeforeUpdate = -1;
            if (TRANSACTION_BASKET_WINDOW_NAME.equals(windowName) && ENT_BUTTON_LABEL.equals(buttonLabel)) {
                basketItemsCountBeforeUpdate = getBasketItemsCount();
            }
            getWebController().clickElementWithJavascript(By.xpath(getXpathForButton(buttonLabel)));
        	waits(2000);
            if (basketItemsCountBeforeUpdate > 0) {
                long end = System.currentTimeMillis() + WAIT_DIALOG_PANEL_UPDATE;
                while (System.currentTimeMillis() < end) {
                    if (getBasketItemsCount() != basketItemsCountBeforeUpdate) {
                        return;
                    }
                    waits(); // wait 1 second
                }
                throw new AutomationException("Basket not updated after pressing the ENT Button");
            }
        }
    }

    private int getBasketItemsCount() throws AutomationException {
        return Integer.parseInt(getWebController().getAttributeInElement(By.xpath(XPATH_SELECTOR_FOR_BASKET_RECEIPT_BODY), "basket-items-count"));
    }

    @Override
    public void pressButtonByComponent(String componentName, String windowName) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.pressButtonByComponentId(getComponentId(componentName), getWindowId(windowName));
        } else {
	         By element = By.xpath(getXpathForAnyElement(componentName));
	         String type = getWebController().getAttributeInElement(element, TYPE);
	         //PLAT-17824 - further investigate to handle check box clicks
	         waits();
	         if (CHECK_BOX.equals(type)) {
	             getWebController().clickElementIfPresent(element);
	         } else {
	             getWebController().clickElementWithJavascript(element);
	         }
            waits();
        }
    }

    @Override
    public void checkButtonNotDisplay(String buttonLabel, String windowName) throws AutomationException {
        if (!getWebController().atLeastSingleElementAvailable(By.xpath(getXpathForButton(buttonLabel)))) {
            return;
        }
        waits();
        Assert.assertFalse(getWebController().atLeastSingleElementAvailable(By.xpath(getXpathForButton(buttonLabel))));
    }

    @Override
    public void enterRichText(String text, String field, String windowName) throws AutomationException{
        text = getTestData(checkCalculationText(text));
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.enterText(text, getComponentId(field), getWindowId(windowName));
        } else {
            String dataype = getWebController().getAttributeInElement(
                    By.xpath(getXpathForRichText(field)), TEXT_INPUT_DATA_TYPE_ATTRIBUTE_NAME);
            String textData = getTextInputDataText(text, dataype);
            getWebController().enterText(By.xpath(getXpathForRichText(field)), textData);
        }
    }

	@Override
	public void enterText(String text, String field, String windowName) throws AutomationException {
		text = getTestData(checkCalculationText(text));
		if (isPeripheralWindow(windowName)) {
			swingAppStepsProvider.enterText(text, getComponentId(field), getWindowId(windowName));
		} else {
			String dataype = getWebController().getAttributeInElement(
					By.xpath(getXpathForInput(field)), TEXT_INPUT_DATA_TYPE_ATTRIBUTE_NAME);
			String textData = getTextInputDataText(text, dataype);
			getWebController().enterTextWithBlurEffect(By.xpath(getXpathForInput(field)), textData);
		}
	}

	private String getTextInputDataText(String text, String dataype) {
		String textData = text;
		if (dataype != null) {
			switch (dataype) {
			case TEXT_INPUT_DATE_DATE_TYPE:
				textData = text.replaceAll("/", "");
				break;
			case TEXT_INPUT_PERCENTAGE_DATA_TYPE:
				Float percentageValue = Float.parseFloat(text.replace("%", ""));
				textData = String.format("%.2f", percentageValue);
				break;
			default:
				textData = text;
			}
		}
		return textData;
	}

    @Override
    public void checkFieldIsNotEmpty(String componentName, String windowName) throws AutomationException {
        String textFieldValue = getWebController().
                getAttributeInElement(By.xpath(getXpathForAnyElement(componentName)), VALUE);
        Assert.assertTrue(!textFieldValue.isEmpty());
    }

    @Override
    public void checkTextFieldValue(String expected, String field, String windowName) throws AutomationException {
        if (getTestData(expected).matches(PASSWORD_PATTERN)) {
            Assert.assertEquals(PASSWORD, getWebController().
                    getAttributeInElement(By.xpath(getXpathForAnyElement(field)), TYPE));
        } else {
            String textFieldValue = getTextFieldValue(field);
            Assert.assertEquals(replaceNewLineWithSpace(textFieldValue).toLowerCase(), replaceNewLineWithSpace(getTestData(expected)).toLowerCase());
        }
    }

    @Override
    public void checkTextFieldContainsValue(String text, String field, String windowName) throws AutomationException {
        String textFieldValue = getTextFieldValue(field);
        Assert.assertTrue(textFieldValue.contains(getTestData(text)));
    }
    
    /**
     * Extract the value of text input fields, If no value returned then will check for the inner text value of the field(label values)
     *
     * @param field
     * @return
     * @throws AutomationException
     */
    private String getTextFieldValue(String field) throws AutomationException {
        String textFieldValue = getWebController().getAttributeInElement(By.xpath(getXpathForAnyElement(field)), VALUE);
        if (textFieldValue == null) {
            textFieldValue = getWebController().getAttributeInElement(By.xpath(getXpathForAnyElement(field)), INNER_TEXT);
        }
        return textFieldValue;
    }

    @Override
    public void selectListItem(String item, String listName, String windowName) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.selectListItem(getTestData(item), getComponentId(listName), getWindowId(windowName));
        } else {
            waits();
            getWebController().clickElementWithJavascript(getXpathForSelectionListItem(getTestData(item)));
        }
    }

    @Override
    public void selectComboboxItem(String item, String field, String windowName) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.selectComboboxItem(
                    getTestData(item), getComponentId(field), getWindowId(windowName));
        } else {
            getWebController().clickElement(By.xpath(getXpathForComboBox(field)));
            getWebController().clickElement(By.xpath(getXpathForComboBoxOption(field, getTestData(item))));
        }
    }

    @Override
    public void checkLabelFieldValue(String expected, String field, String windowName) throws AutomationException {
//    	getWebController().getTextByLocator(null);
        String labelValue = getWebController().getTextByLocator(getLocator(XPATH, field));
        String[] values = labelValue.split("\n");
        boolean exactTextMatchExists = false;
        for(String label: values) {
        	exactTextMatchExists = getTestData(expected).equals(label);
        	if (exactTextMatchExists) {
        		break;
        	}
        }        
        Assert.assertTrue(exactTextMatchExists);
    }

    @Override
    public void checkPanelTextDisplayed(String expected, String panel, String windowName) throws AutomationException {
        checkLabelFieldValue(expected, panel, windowName);
    }

    @Override
    public byte[] requestCaptureScreenshot() throws AutomationException {
        try {
            return FileUtils.getBytes(getWebController().captureScreenshot());
        } catch (Exception e) {
            throw new AutomationException("Capturing the screenshot failed");
        }
    }

    @Override
    public void restartApplication() throws AutomationException {
        quitWebController();
        beforeTest();
    }

    @Override
    public void beforeTest() throws AutomationException {
        try {
            getWebController().reloadPage(SeleniumConfig.getInstance().getPageUrl());
            getWebController().enterText(By.xpath(DEVICE_ID_INPUT_XPATH), PosTestManager.getWebPosDeviceId());
            String webPosHostName = PosTestManager.getWebPosHostName();
            if (!StringUtils.isEmpty(webPosHostName)) {
                getWebController().enterText(By.xpath(WEB_POS_HOST_NAME_INPUT_XPATH), webPosHostName);
            }
            getWebController().clickElement(By.xpath(DEVICE_ID_SUBMIT_BUTTON_XPATH));
        } catch (Exception e) {
            quitWebController();
            throw new AutomationException("Before test steps failed");
        }
    }

    @Override
    public void afterTestCheckEnterUserScreenShown(String testName, boolean clearTestPrinter, boolean testFailed) throws Exception {
        try {
            TRANSACTION_IDS.clear();
            if (clearTestPrinter) {
                clearPrinter();
            }
            if (testFailed) {
                failTest(testName);
            }
            checkWindow(ENTER_USER_WINDOW_NAME);
        } catch (Exception e) {
            recoverApplication();
        } finally {
            quitWebController();
        }
    }

    /**
     * Returns the {{processId}}.{{promptUrl}} from the window name.
     * This method extracts process Id and prompt url from the window name in configs related to page def POS
     *
     * @param windowName
     * @return
     * @throws AutomationException
     */
    @Override
    protected String getWindowId(String windowName) throws AutomationException {

        String pageName = super.getWindowId(windowName);

        if (isPeripheralWindow(windowName)) {
            return pageName;
        }

        if (StringUtils.isEmpty(pageName)) {
            throw new AutomationException("Page name cannot be empty");
        }

        Pattern pattern = Pattern.compile("([A-Z][A-Za-z]*[/][a-zA-Z/]*)+");
        Matcher matcher = pattern.matcher(pageName);

        String processId = matcher.find() ? matcher.group() : null;
        String promptUrl = matcher.find() ? matcher.group() : null;

        if (StringUtils.isEmpty(processId) || StringUtils.isEmpty(promptUrl) || matcher.find()) {
            throw new AutomationException("Extracting the process id and promptUrl failed for : " + pageName);
        }
        processId = processId.startsWith("/") ? processId.replaceFirst("/", "") : processId;
        promptUrl = promptUrl.startsWith("/") ? promptUrl.replaceFirst("/", "") : promptUrl;

        return (processId + "." + promptUrl).replaceAll("/", "-");
    }

    @Override
    protected void pressButtonInCurrentWindow(String buttonLabel, long timeout) throws AutomationException {
        getWebController().waitForElement(By.xpath(getXpathForButton(buttonLabel)), timeout).click();
    }

    @Override
    protected void pressButtonByComponentInCurrentWindow(String button, long timeout) throws AutomationException {
        getWebController().waitForElement(By.xpath(getXpathForAnyElement(button)), timeout).click();
    }

    @Override
    protected void checkWindow(String windowName, long timeout) throws AutomationException {
        if (isPeripheralWindow(windowName)) {
            swingAppStepsProvider.waitForWindow(getWindowId(windowName), timeout);
        } else {
            String windowId = getWindowId(windowName);
            getWebController().waitForElement(By.id(windowId));
        }
    }

    @Override
    protected List<String> getTextInListItems(String list, String windowName) throws AutomationException {
        List<String> lineTexts;
        List<String> lines = new ArrayList<>();
        if (isBasketViewActive()) {
            lineTexts = getReceiptLines();
        } else if (isSimpleSelectionActive()) {
            lineTexts = getSimpleSelectionRowLines();
        } else {
            lineTexts = getGridBodyLines();
        }

        if (lineTexts != null && !lineTexts.isEmpty()) {
            for (String lineText : lineTexts) {
            	lines.add(lineText.replaceAll("\n", " ").replaceAll(",", " ")
                        .replaceAll(" +", " "));
            }
        }
        return lines;
    }
    
    private static String replaceNewLineWithSpace(String textValue) {
		if (StringUtils.notEmpty(textValue)) {
			textValue = textValue.replaceAll("\n", " ");
		}
		return textValue;

	}

    @Override
    public void checkListItemDisplay(String item, String list, String windowName) throws AutomationException {
        List<String> lines = null;
        long end = System.currentTimeMillis() + WAIT_DIALOG_PANEL_UPDATE;
        while (System.currentTimeMillis() < end) {
            lines = getTextInListItems(list, windowName);
            Iterator<String> lineIterator = lines.iterator();
            StringBuilder lineBuilder = new StringBuilder();
            while (lineIterator.hasNext()) {
                lineBuilder.append(lineBuilder.length() != 0 ? " " : "").append(lineIterator.next());
                if (lineBuilder.toString().contains(getTestData(item))) {
                    return;
                } else if (!getTestData(item).contains(lineBuilder.toString())) {
                    lineBuilder = new StringBuilder();
                }
            }
            waits(); // wait 1 second
        }
        throw new AutomationException("Item [" + getTestData(item) + "] not display in [" + list + "] list...." + lines);
    }

    protected ISwingAppStepsProvider getSwingAppStepsProvider() {
        return swingAppStepsProvider;
    }

    private boolean isBasketViewActive() throws AutomationException {
        return getWebController().atLeastSingleElementAvailable(By.xpath(XPATH_SELECTOR_FOR_BASKET_RECEIPT));
    }

    private boolean isSimpleSelectionActive() throws AutomationException {
        return getWebController().atLeastSingleElementAvailable(By.xpath(XPATH_SELECTOR_FOR_SIMPLE_SELECTION));
    }

    private String getXpathForAnyElement(String component) throws AutomationException {
        return String.format(XPATH_SELECTOR_FOR_ANY_ELEMENT, getComponentId(component));
    }

	private By getXpathForSelectionListItem(String itemName) throws AutomationException {
		String testDataValue = getTestData(itemName.trim());
		return By.xpath(String.format(XPATH_SELECTOR_FOR_SELECTION_LIST_ITEM,
				testDataValue, testDataValue, testDataValue.toLowerCase(),
				testDataValue.toUpperCase(), testDataValue, testDataValue));
	}

    private String getXpathForInput(String component) throws AutomationException {
        return String.format(XPATH_SELECTOR_FOR_TEXT_INPUT, getComponentId(component), getComponentId(component));
    }

    private String getXpathForRichText(String component) throws AutomationException {
        return String.format(XPATH_SELECTOR_FOR_RICH_TEXT_ELEMENT, getComponentId(component), getComponentId(component));
    }

    private List<String> getReceiptLines() throws AutomationException {
        return getWebController().getTextInList(By.xpath(XPATH_SELECTOR_FOR_BASKET_RECEIPT),
                By.xpath(XPATH_SELECTOR_BASKET_GRID_ROW_AND_HEADER_AREA));
    }

    private List<String> getGridBodyLines() throws AutomationException {
        return getWebController().getTextInList(By.xpath(XPATH_SELECTOR_FOR_GRID_BODY),
                By.className(BASKET_GRID_ROW_CLASS_NAME));
    }

    private List<String> getSimpleSelectionRowLines() throws AutomationException {
        return getWebController().getTextInList(By.xpath(XPATH_SELECTOR_FOR_SIMPLE_SELECTION),
                By.className(SIMPLE_SELECTION_ROW_CLASS_NAME));
    }

    private String getXpathForComboBox(String component) throws AutomationException {
        return String.format(XPATH_SELECTOR_FOR_COMBO_BOX, getComponentId(component));
    }

    private String getXpathForComboBoxOption(String component, String itemName) throws AutomationException {
        return String.format(XPATH_SELECTOR_FOR_COMBO_BOX_OPTION, getComponentId(component), itemName);
    }

    private String getXpathForButton(String buttonLabel) {
        return String.format(XPATH_SELECTOR_FOR_BUTTON, buttonLabel, buttonLabel, buttonLabel.toLowerCase(), WordUtils.capitalize(buttonLabel.toLowerCase()));
    }

    protected boolean isPeripheralWindow(String windowName) {
        return PERIPHERAL_DEVICES_WINDOW_NAMES_LIST.contains(windowName);
    }

	@Override
	public void addToViewData(IApplicationProcessDataType type, Object data) throws AutomationException {
		throw new NotImplementedException("addToViewData is not supported for WebPos at the moment");
		
	}

	@Override
	public IComponentAccessor getComponentAccessor(String componentId, String windowId) throws AutomationException {
		throw new NotImplementedException("getComponentUrl is not supported for WebPos at the moment");
	}
	
	/**
	 * Get element locator in the required type.
	 *
	 * @param type type of the provided key
	 * @param key  key of the element in the provided type
	 * @return the locator based on the provided type and key
	 * @throws AutomationException throws when the provided type is unknown
	 */
	protected By getLocator(String type, String key) throws AutomationException {
		switch (type) {
		case ID:
			return By.id(key);
		case CLASS:
			return By.className(key);
		case NAME:
			return By.name(key);
		case XPATH:
			return By.xpath(getXpathForAnyElement(key));
		default:
			throw new AutomationException("Unknown element type [" + type + "]");
		}
	}
}
