package cz.moneta.test.harness.connectors.web;

import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

public abstract class SeleniumWebConnector implements Connector {

    private final WebDriver webDriver;
    private final boolean isRemoteDriver;
    protected final Function<String, By> defaultLookup;
    protected final StoreAccessor store;
    protected final String[] extraOptions;

    private String nodeHost;

    public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store, String[] extraOptions) {
        this.extraOptions = extraOptions;
        this.store = store;
        this.defaultLookup = defaultLookup;
        Pair<WebDriver, Boolean> driverInfo = getPreferredWebDriver();
        isRemoteDriver = driverInfo.getRight();
        WebDriver driver = driverInfo.getLeft();
        if (driver == null) {
            throw new HarnessException("We were not able to initialize webDriver. Please check your configs, logs and talk to your admin");
        }

        //TODO fix this - it slows down test execution but some tests are build with this implicit wait
        driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);
        driver.manage().timeouts().pageLoadTimeout(90L, TimeUnit.SECONDS);
        this.webDriver = driver;
    }

    public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store) {
        this(defaultLookup, store, new String[]{});
    }

    public Clicks clicks() {
        return new Clicks(this);
    }

    public Waits waits() {
        return new Waits(this);
    }

    public Alerts alerts() {
        return new Alerts(this);
    }

    public Types types() {
        return new Types(this);
    }

    public Selects selects() {
        return new Selects(this);
    }

    public SendKeys sendKeys() {
        return new SendKeys(this);
    }

    public Frames frames() {
        return new Frames(this);
    }

    public Windows windows() {
        return new Windows(this);
    }

    public Navigations navigations() {
        return new Navigations(this);
    }

    public ElementsChecks elementsChecks() {
        return new ElementsChecks(this);
    }

    public Cookies cookies() {
        return new Cookies(this);
    }

    public Scripts scripts() {
        return new Scripts(this);
    }

    public ElementsActions elementsActions() {
        return new ElementsActions(this);
    }

    public Scrolling scrolling() {return new Scrolling(this); }

    public DriverUtils driverUtils() {
        return new DriverUtils(this);
    }

    public StoreAccessor getStore() {
        return store;
    }

    public boolean isRemoteDriver() {
        return isRemoteDriver;
    }

    public abstract void downloadFileViaIePrompt(int waitInSeconds);

    /**
     * returns a pair of web driver and whether it is a remote instance (true = remote, false = local driver)
     */
    private Pair<WebDriver, Boolean> getPreferredWebDriver() {
        return Stream.<Supplier<Pair<WebDriver, Boolean>>>of(this::getDedicatedWebDriver, this::getSharedWebDriver, this::getLocalWebDriver)
                .map(Supplier::get)
                .filter(Objects::nonNull)
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("No suitable web driver could be initialized."));
    }

    private Pair<WebDriver, Boolean> getDedicatedWebDriver() {
        return Optional.ofNullable(store.getConfig("selenium.grid.dedicated.url"))
                .map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
                .orElse(null);
    }

    private Pair<WebDriver, Boolean> getSharedWebDriver() {
        return Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
                .map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
                .orElse(null);
    }

    public String getAttribute(String path, Lookup lookup, String attributeName) {
        return waits().waitForLazyElement(path, lookup)
                .getAttribute(attributeName );
    }

    protected abstract Pair<WebDriver, Boolean> getLocalWebDriver();

    protected abstract Capabilities getDesiredRemoteCapabilities();

    public WebDriver getDriver() {
        return webDriver;
    }

    @Override
    public void close() {
        webDriver.quit();
    }

    public String getCurrentUrl() {
        return webDriver.getCurrentUrl();
    }

    protected Function<String, By> resolveLookup(Lookup lookup) {
        switch (lookup) {
            case XPATH:
                return By::xpath;
            case ID:
                return By::id;
            case CLASSNAME:
                return By::className;
            case NAME:
                return By::name;
            default:
                return defaultLookup;
        }
    }

    public String getNodeHost() {
        return nodeHost;
    }

    public void setNodeHost(String nodeHost) {
        this.nodeHost = nodeHost;
    }
}
