package com.enactor.retail.pos.stepDefinitions;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;

import javax.naming.InitialContext;
import javax.sql.DataSource;

import com.enactor.core.application.process.ApplicationProcessDataType;
import com.enactor.core.application.process.IApplicationProcessDataType;
import com.enactor.core.automation.AUT.AutomationException;
import com.enactor.core.config.ICorePropertyConfigs;
import com.enactor.core.database.DataAccessContext;
import com.enactor.core.database.DatabaseException;
import com.enactor.core.database.IDataAccessSession;
import com.enactor.core.jndi.mock.MockJNDIInitialiser;
import com.enactor.core.servers.EntityServerProxy;
import com.enactor.core.servers.LockType;
import com.enactor.core.utilities.CorePropertyConfigs;
import com.enactor.core.utilities.PackageDeployerUtil;
import com.enactor.core.utilities.PropertyConfigs;
import com.enactor.core.utilities.PropertyRegistry;
import com.enactor.core.utilities.StringUtils;
import com.enactor.mfc.location.LocationKey;
import com.enactor.mfc.posTerminal.IPOSTerminal;
import com.enactor.mfc.posTerminal.IPOSTerminalServer;
import com.enactor.mfc.posTerminal.POSTerminal;
import com.enactor.pos.IPosStepsProvider;
import com.enactor.pos.PosTestConstants;
import com.enactor.pos.PosTestManager;
import com.enactor.pos.TestDataConfig;

import io.cucumber.datatable.DataTable;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;


/**
 * POS Automation common steps.
 *
 * @author Charith Gunathilaka
 */
public class CommonPosSteps {

    private static final String USERNAME_FIELD = "User Name Text";
    private static final String PASSWORD_FIELD = "Password Text";
    private static final String ENTER_BUTTON_LABEL = "ENT";
    private static final String ENTER_USER_WINDOW = "Enter User";
    private static final String TRANSACTION_BASKET_WINDOW = "Transaction Basket";

    private Connection connection = null;
    private IPOSTerminal posTerminal = null;
    private IPOSTerminalServer terminalServer = null;

    public static String currentPage = null;

    private final IPosStepsProvider stepsProvider = PosTestManager.getInstance().getStepsProvider();

	public IPosStepsProvider getPosStepsProvider() {
		return stepsProvider;
	}

    @Before
    public void initTest() throws AutomationException {
        stepsProvider.beforeTest();
    }

    /**
     * Press a button with a given label on current page
     *
     * @param label
     * @throws Exception
     */
    @Given("^user presses \"(.+)\" button$")
    public void userPressesButtonOnCurrentPage(String label) throws Exception {
        stepsProvider.pressButton(label, currentPage);
    }


    public void initializeDBConnection() throws Exception {
        // Get relevant configuration via pos enactor.xml
        File configFolder = new File(TestDataConfig.getInstance().getConfigDirectoryPath());
        File enactorXml = new File(configFolder, CorePropertyConfigs.XML_FILENAME);
        if (!enactorXml.exists()) {
            System.err.println("Configuration does not exist: " +
                    enactorXml.getAbsolutePath());
            return;
        }
        PropertyConfigs.setDefaultFileResourcePath(configFolder.toString());
        ICorePropertyConfigs configs = PropertyRegistry.getCoreConfigs();

        try (IDataAccessSession dataAccess = DataAccessContext.getSession()) {

        }

        // Register Packages
        PackageDeployerUtil.registerPackages();
        MockJNDIInitialiser.init();
        InitialContext initialContext = new InitialContext();
        DataSource dataSource = (DataSource) initialContext.lookup(PropertyRegistry.getDatasourceName());

        // Get DB Connection
        connection = dataSource.getConnection();

        // Get Currently using POS Terminal
        terminalServer = EntityServerProxy.getServer(POSTerminal.ENTITY_QNAME);
        int terminalNumber = Integer.parseInt(configs.getProperty(PosTestConstants.TERMINAL_NUMBER));
        String locationId = configs.getProperty(PosTestConstants.LOCATION_ID);
        LocationKey locationKey = new LocationKey(locationId);
        posTerminal = terminalServer.load(locationKey, terminalNumber, LockType.READ_LOCK);
    }

    /**
     * Changes POS Training Mode Status (ON/OFF)
     *
     * @throws Exception
     */
    @Given("^user changes pos training mode status$")
    public void toggleTrainingMode() throws Exception {
        // Singleton Usage of DB Connection
        if (connection == null) {
            initializeDBConnection();
        }
        posTerminal.setTrainingMode(!posTerminal.getTrainingMode());
        try {
            terminalServer.save(posTerminal);
        } catch (DatabaseException e) {
            System.out.println(("Error saving Pos Terminal: " + e.getMessage()));
        }
        // In order to reload the pos terminal
        stepsProvider.restartApplication();
    }

    /**
     * Press icon button
     *
     * @param buttonName
     * @throws Exception
     */
    @Given("^user presses \"(.+)\" icon button$")
    public void userPressesIconButtonOnCurrentPage(String buttonName) throws Exception {
        stepsProvider.pressButtonByComponent(buttonName, currentPage);
    }

    /**
     * Press button with a given label on current page
     * and check different/given page shown
     *
     * @param label
     * @param page
     * @throws Exception
     */
    @When("^user presses \"(.+)\" button and \"(.+)\" page shown$")
    public void userPressesButtonOnCurrentPageAndPageShown(String label, String page) throws Exception {
        userPressesButtonOnCurrentPage(label);
        checkPageShown(page);
    }

    /**
     * navigate to given menu starting on current page and check different/given page shown
     * ex: "Sales > Discount > Item Discount"
     *
     * @param menu
     * @param page
     * @throws Exception
     */
    @When("^user navigates to \"(.+)\" menu and \"(.+)\" page shown$")
    public void userPressesMenuOnCurrentPageAndPageShown(String menu, String page) throws Exception {
        stepsProvider.pressMenu(menu, currentPage);
        checkPageShown(page);
    }

    /**
     * navigate to given menu starting on current page
     * ex: "Sales > Discount > Item Discount"
     *
     * @param menu
     * @throws Exception
     */
    @When("^user navigates to \"(.+)\" menu$")
    public void userPressesMenuOnCurrentPage(String menu) throws Exception {
        stepsProvider.pressMenu(menu, currentPage);
    }


    /**
     * Press check box on current page
     *
     * @param label
     * @throws Exception
     */
    @Given("^user presses \"(.+)\" checkbox$")
    public void userPressCheckboxOnCurrentPage(String checkbox) throws Exception {
        stepsProvider.pressButtonByComponent(checkbox, currentPage);
    }

    /**
     * Enter text into a given field on current page
     *
     * @param text
     * @param field
     * @throws Exception
     */
    @When("^user enters \"(.+)\" into \"(.+)\" field$")
    public void userEntersTextOnCurrentPage(String text, String field) throws Exception {
        stepsProvider.enterText(text, field, currentPage);
    }

    /**
     * Enter text into a given field on current page
     *
     * @param text
     * @param field
     * @throws Exception
     */
    @When("^user enters \"(.+)\" into \"(.+)\" rich text field$")
    public void userEntersRichTextOnCurrentPage(String text, String field) throws Exception {
        stepsProvider.enterRichText(text, field, currentPage);
    }
    /**
     * Current page text field's value matches the given text
     * <p>
     * eg: text field value : "Mr"
     * Matches text "Mr"
     * but not "mr" or "Mr."
     *
     * @param text
     * @param field
     * @throws Exception
     */
    @When("^\"(.+)\" field displays the text \"(.+)\"$")
    public void checkTextDisplayOnCurrentPage(String field, String text) throws Exception {
        stepsProvider.checkTextFieldValue(text, field, currentPage);
    }

    /**
     * Current page text field's value contains in the given text
     * <p>
     * eg: text field value : "Kit"
     * Contains in the text "HD Complexion Starter Kit"
     *
     * @param text
     * @param field
     * @throws Exception
     */
    @When("^\"(.+)\" field contains the text \"(.+)\"$")
    public void checkTextContainOnCurrentPage(String field, String text) throws Exception {
        stepsProvider.checkTextFieldContainsValue(text, field, currentPage);
    }

    /**
     * Current page given label text matches the given text
     * <p>
     * eg: label field value : "£0.00"
     * Matches text "£0.00"
     *
     * @param text
     * @param field
     * @throws Exception
     */
    @When("^\"(.+)\" label displays the text \"(.+)\"$")
    public void checkLabelTextDisplayOnCurrentPage(String field, String text) throws Exception {
        stepsProvider.checkLabelFieldValue(text, field, currentPage);
    }

    /**
     * Check page display
     * eg:
     * Then "Transaction Basket" page is shown
     * page = "Transaction Basket"
     * this method get window id from client property file
     * by checking entry WINDOW_TRANSACTION_BASKET
     *
     * @param page
     * @throws Exception
     */
	@Then("^\"(.+)\" page is shown$")
	public void checkPageShown(String page) throws Exception {
		stepsProvider.checkWindow(page);
		currentPage = page;

		// Log visited process and prompt
		String logProcessAndPrompt = System.getProperty("logProcessAndPrompt");
		if (logProcessAndPrompt != null && Boolean.valueOf(logProcessAndPrompt)) {
			System.out.println(String.format("AUTOMATION_VISITED_THE_PAGE: %s",
					stepsProvider.getExtractedProcessIDandPromptIdFromWindowName(page)));
		}
	}

    /**
     * Select given item from a selection list on current page
     *
     * @param item
     * @throws Exception
     */
    @When("^user selects \"(.+)\" from \"(.+)\" list$")
    public void userSelectsFromSelectionListOnCurrentPage(String item, String listName) throws Exception {
        stepsProvider.selectListItem(item, listName, currentPage);
    }

    /**
     * Select item from combobox on current page
     *
     * @param item
     * @param field
     * @throws Exception
     */
    @Then("^user selects \"(.+)\" from \"(.+)\" combobox$")
    public void userSelectsOptionFromComboboxOnCurrentPage(String item, String field) throws Exception {
        stepsProvider.selectComboboxItem(item, field, currentPage);
    }

    /**
     * Check item display in a list on current page
     *
     * @param item
     * @param list
     * @throws Throwable
     */
    @Given("^\"(.+)\" is displayed in \"(.+)\" list$")
    public void checkItemDisplayOnCurrentPage(String item, String list) throws Throwable {
        stepsProvider.checkListItemDisplay(item, list, currentPage);
    }

    /**
     * Check item is not displayed in a list on current page
     *
     * @param item
     * @param list
     * @throws Throwable
     */
    @Given("^\"(.+)\" is not displayed in \"(.+)\" list$")
    public void checkItemNotDisplayedOnCurrentPage(String item, String list) throws Throwable {
        stepsProvider.checkListItemNotDisplayed(item, list, currentPage);
    }

    /**
     * Check text displayed in a given panel on current page
     *
     * @param text
     * @param component
     * @throws Exception
     */
    @Given("^\"(.+)\" is displayed in \"(.+)\" panel$")
    public void checkCallToCustomerNameDisplayed(String text, String component) throws Exception {
        stepsProvider.checkPanelTextDisplayed(text, component, currentPage);
    }

    /**
     * Press a button with a given label on a test peripheral window
     *
     * @param label
     * @param page
     * @throws Exception
     */
    @Given("^user presses \"(.+)\" button on external \"(.+)\" window$")
    public void userPressesButtonOnPage(String label, String peripheral) throws Exception {
        stepsProvider.pressButtonOnTestPeripheral(label, peripheral);
    }

    /**
     * User waits component to display on current page
     *
     * @param component
     * @throws Exception
     */
    @Given("^user waits \"(.+)\" to display$")
    public void userWaitsComponentDisplay(String component) throws Exception {
        stepsProvider.waitsForComponent(component, currentPage);
    }

    /**
     * User waits given number of seconds
     * NOTE:This step should not be used for any scenario but only in
     * special cases where the page is slow to render
     *
     * @param seconds
     * @throws Exception
     */
    @Given("^user waits \"(.+)\" seconds$")
    public void userWaits(String seconds) throws Exception {
        stepsProvider.waits(Long.parseLong(seconds) * 1000);
    }

    /**
     * User confirms component NOT display on current page
     *
     * @param component
     * @throws Exception
     */
    @Given("^user confirms \"(.+)\" not display$")
    public void userConfirmsComponentNotDisplay(String component) throws Exception {
        stepsProvider.checkComponentNotDisplay(component, currentPage);
    }

    /**
     * User confirms button with a given label NOT display on current page
     *
     * @param label
     * @throws Exception
     */
    @Given("^button with label \"(.+)\" is not displayed$")
    public void userConfirmsbuttonNotDisplay(String label) throws Exception {
        stepsProvider.checkButtonNotDisplay(label, currentPage);
    }

    /**
     * Login as given user.
     * User details must be defined in data property file in following format
     * <b>[User]_USERNAME=[Username]</b>
     * <b>[User]_PASSWORD=[Password]</b>
     * <br>eg : user "Sales Assistant"
     * SALES_ASSISTANT_USERNAME=admin
     * SALES_ASSISTANT_PASSWORD=admin
     *
     * @param user
     * @throws Exception
     */
    @Given("^user signs on as \"(.+)\"$")
    public void signOn(String user) throws Exception {
        // check login screen display
        checkPageShown(ENTER_USER_WINDOW);
        // Enter username and password
        userEntersTextOnCurrentPage(stepsProvider.getTestDataValue(user + " username"), USERNAME_FIELD);
        userEntersTextOnCurrentPage(stepsProvider.getTestDataValue(user + " password"), PASSWORD_FIELD);
        // press ENT button
        userPressesButtonOnCurrentPage(ENTER_BUTTON_LABEL);
        // check transaction basket page display
        checkPageShown(TRANSACTION_BASKET_WINDOW);
    }

    /**
     * Check receipt details
     *
     * @param table
     * @throws Exception
     */
    @Then("^receipt display with following details$")
    public void confirmReceipt(DataTable table) throws Exception {
        List<List<String>> receipt = table.asLists();
        int index = 0;
        for (List<String> items : receipt) {
            String key = items.get(0);
            String value = items.get(1);
            switch (key) {
                case "address":
                case "item":
                    List<String> entryLines = Arrays.asList(value.split(","));
                    index = stepsProvider.checkMultipleLineReceiptEntry(entryLines, index);
                    break;
                case "item flag":
                    index = stepsProvider.checkReceiptItemFlag(Arrays.asList(value.split(",")), index);
                    break;
                case "telephone":
                case "gift card type":
                case "gift card number":
                case "text":
                    index = stepsProvider.checkReceiptEntry(value, index);
                    break;
                default:
                    throw new AutomationException(String.format("Invalid key '%s'", key));
            }
        }
        stepsProvider.storeTransactionID();
    }
    
    

    /**
     * Check receipt details on printer
     *
     * @param table
     * @throws Exception
     */
    @Then("^the receipt displayed on \"(.*?)\" printer with following details$")
    public void confirmReceiptOnPrinter(String windowName, DataTable table) throws Exception {
        List<List<String>> receipt = table.asLists();
        int index = 0;
        for (List<String> items : receipt) {
            String key = items.get(0);
            String value = items.get(1);
            switch (key) {
                case "address":
                case "item":
                    List<String> entryLines = Arrays.asList(value.split(","));
                    index = stepsProvider.checkMultipleLineReceiptEntryOnPrinter(entryLines, index, windowName);
                    break;
                case "item flag":
                    index = stepsProvider.checkReceiptItemFlagOnPrinter(Arrays.asList(value.split(",")), index, windowName);
                    break;
                case "telephone":
                case "gift card type":
                case "gift card number":
                case "text":
                    index = stepsProvider.checkReceiptEntryOnPrinter(value, index,windowName);
                    break;
                default:
                    throw new AutomationException(String.format("Invalid key '%s'", key));
            }
        }
        stepsProvider.storeTransactionID();
    }

    /**
     * Check receipt details NOT display
     *
     * @param table
     * @throws Exception
     */
    @Then("^following details are not displayed on the receipt$")
    public void confirmNotDisplayOnReceipt(DataTable table) throws Exception {
        List<List<String>> receipt = table.asLists();
        for (List<String> items : receipt) {
            String key = items.get(0);
            String value = items.get(1);
            switch (key) {
                case "address":
                case "item":
                    List<String> entryLines = Arrays.asList(value.split(","));
                    stepsProvider.checkMultipleLineReceiptEntryNotDisplay(entryLines, 0);
                    break;
                case "telephone":
                case "gift card type":
                case "gift card number":
                case "text":
                    stepsProvider.checkReceiptEntryNotDisplay(value, 0);
                    break;
                default:
                    throw new AutomationException(String.format("Invalid key '%s'", key));
            }
        }
    }

    /**
     * Block connections to the given uri
     *
     * @param uri
     * @throws AutomationException
     */
    @And("the pos blocks connections to \"(.+)\"")
    public void posBlocksConnectionToUrl(String uri) throws AutomationException {
        stepsProvider.blockConnectionsToUri(uri);
    }

    /**
     * Unblock connections to the given uri
     *
     * @param uri
     * @throws AutomationException
     */
    @Then("the pos unblocks connections to \"(.+)\"")
    public void posUnblocksConnectionToUrl(String uri) throws AutomationException {
        stepsProvider.unblockConnectionsToUri(uri);
    }


    /**
     * Check the field is not empty
     *
     * @param componentName
     * @throws Throwable
     */
    @And("^the \"(.+)\" field is not empty$")
    public void checkFieldIsNotEmptyOnCurrentPage(String componentName) throws Throwable {
        stepsProvider.checkFieldIsNotEmpty(componentName, currentPage);
    }


    @After("@No_Printer")
    public void afterTest_ClearTestPrinter(Scenario scenario) throws Exception {
        stepsProvider.afterTestCheckEnterUserScreenShown(scenario.getName(), false, scenario.isFailed());
    }

    @After("not @No_Printer")
    public void afterTest(Scenario scenario) throws Exception {
        stepsProvider.afterTestCheckEnterUserScreenShown(scenario.getName(), true, scenario.isFailed());
    }

    /**
     * A transaction is created for each line from a csv file where the values are the product codes to add to the transaction.
     *
     * @param csvFile              Lines of product codes
     * @param createBasketMenuPath Menu text for the buttons that needs to be clicked to get to the capture and sell function
     * @param storeBasketMenuPath  Menu text for storing the created transaction
     * @throws Exception
     */
    @Then("^create and store baskets from csv \"([^\"]*)\" with create basket menu \"([^\"]*)\" and store basket menu \"([^\"]*)\"$")
    public void createAndStoreBasketsFromCSV(String csvFile, String createBasketMenuPath, String storeBasketMenuPath) throws Exception {
        // It is expected the POS is on the transaction basket screen
        try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
            String[] createBasketMenuArray = StringUtils.splitCommaDelimitedString(createBasketMenuPath);
            String[] storeBasketMenuArray = StringUtils.splitCommaDelimitedString(storeBasketMenuPath);
            String line;
            while ((line = br.readLine()) != null) {
                if (line.length() > 0) {
                    System.out.println("Processing line " + line);
                    createAndStoreBasket(line, createBasketMenuArray, storeBasketMenuArray);
                }
            }

        }
    }

    /**
     * @param csvString
     * @param createBasketMenuArray
     * @param storeBasketMenuArray
     * @throws Exception
     */
    private void createAndStoreBasket(String csvString, String[] createBasketMenuArray, String[] storeBasketMenuArray) throws Exception {
        String[] values = StringUtils.splitCommaDelimitedString(csvString);

        // Use the supplied menu text to start the capture and sell product quantity list function
        for (String menuEntry : createBasketMenuArray) {
            userPressesButtonOnCurrentPage(menuEntry);
        }
        checkPageShown("Product QTY List Enter QTY");

        // Place each of the values into the test scanner and click the scan button
        checkPageShown("Scanner");
        for (String value : values) {
            userEntersTextOnCurrentPage(value, "Scan Text");
            userPressesIconButtonOnCurrentPage("Test Scanner Scan Button");
        }
        // Switch focus back to the enter qty page and press Complete to return to the main transaction basket page
        checkPageShown("Product QTY List Enter QTY");
        userPressesButtonOnCurrentPageAndPageShown("Complete", TRANSACTION_BASKET_WINDOW);

        // Use the supplied menu text to store the transaction
        for (String menuEntry : storeBasketMenuArray) {
            userPressesButtonOnCurrentPage(menuEntry);
        }

        checkPageShown(TRANSACTION_BASKET_WINDOW);

    }

    @And("^read transaction xml from path \"(.*?)\"$")
    public void read_transaction_xml_from_path(String filePath) throws Throwable {
        stepsProvider.readTransactionXmlFromPath(filePath);
    }

    @And("^read given \"(.*?)\" transaction xml from path \"(.*?)\"$")
    public void read_given_transaction_xml_from_path(String filename, String filePath) throws Throwable {
        stepsProvider.readTransactionXmlFromPath(filePath, filename);
    }


    /**
     * Add view data of given type to POS.
     *
     * @param type Data type
     * @param name Data element name
     * @param value Value
     * @throws Throwable
     */
    @When("^View data of type \"(.*?)\" with name \"(.*?)\" and value \"(.*?)\" is set$")
    public void set_view_data(String type, String name, String value) throws Throwable {
        Class classType = ClassLoader.getSystemClassLoader().loadClass(type);
        IApplicationProcessDataType dataType = new ApplicationProcessDataType(name, classType);
        stepsProvider.addToViewData(dataType, value);
    }
}