package cz.moneta.test.harness.connectors.mobile.android;

import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.remote.AutomationName;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class AndroidConnector extends AppiumMobileConnector<AndroidDriver> {
    private final AndroidDriver driver;

    public AndroidConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String appPackage, String appBinaryPath) {
        super(defaultLookup, store, host, appBinaryPath);
        this.deviceName = deviceName;
        this.udid = udid;
        this.bundleId = appPackage;
        this.driver = initDriver();
    }

    @Override
    protected AndroidDriver getDriver() {
        return driver;
    }

    private AndroidDriver initDriver() {
        return startDriver()
                .map(this::startRecording)
                .flatMap(d ->
                        Stream.<Supplier<Optional<AndroidDriver>>>of(
                                () -> launchExistingApp(d))
                                .map(Supplier::get)
                                .filter(Optional::isPresent)
                                .map(Optional::get)
                                .findFirst())
                .orElseThrow(() -> new HarnessException("App was not started"));
    }

    private Optional<AndroidDriver> startDriver() {
        return Optional.ofNullable(host)
                .map(cs -> {
                    DesiredCapabilities capabilities = new DesiredCapabilities();
                    mobileLanguage(capabilities, "US", "CZ");
                    setDesiredCapabilities(Platform.ANDROID, deviceName, udid, getUiAutomator(), capabilities);
                    try {
                        return new AndroidDriver(new URL(cs), capabilities);
                    } catch (MalformedURLException e) {
                        throw new HarnessException(String.format("Appium connection to ANDROID device is invalid: %s", cs), e);
                    } catch (WebDriverException e) {
                        if (e.getCause() instanceof ConnectException) {
                            throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
                        } else {
                            throw new HarnessException("Android driver didn't start, please check the connect to server", e);
                        }
                    }
                });
    }

    @Override
    public void captureVideo(String videoFileName) {
        if (!checkVideoRecord()) {
            return;
        }
        String videoPath = store.getConfig("appium.video.path", "target/videos");
        File file = Paths.get(videoPath, videoFileName).toFile();
        try {
            FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(driver.stopRecordingScreen()));
        } catch (IOException e) {
            throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
        }
    }

    @Override
    public void resetApp() {
        driver.resetApp();
        driver.activateApp(bundleId);
    }

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

    public void hideAndroidKeyboard() {
        driver.hideKeyboard();
    }

    private AndroidDriver startRecording(AndroidDriver driver) {
        if (checkVideoRecord()) {
            driver.startRecordingScreen();
        }
        return driver;
    }

    private String getUiAutomator() {
        boolean uiAutomator = Boolean.parseBoolean(store.getConfig("appium.android.uiAutomator", "false"));
        return uiAutomator ? AutomationName.ANDROID_UIAUTOMATOR2 : null;
    }

    public void pressAndroidKeys(AndroidKey... keys) {
        Arrays.stream(keys)
                .forEach(key -> getDriver().pressKey(new KeyEvent(key)));
    }
}
