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

import cz.moneta.test.harness.endpoints.MobileEndpoint;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.mobile.share.AnnotationsBuilder;
import cz.moneta.test.harness.support.mobile.share.MobileFlow;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.IntStream;

import static cz.moneta.test.harness.support.reflect.ReflectionUtils.getInterfaces;

public class AndroidBuilders extends AnnotationsBuilder {

    private AndroidBuilders() {
    }

    private static void doAndroidClick(MobileEndpoint endpoint, Method method, Object[] args) {
        Arrays.stream(method.getAnnotationsByType(AndroidClick.class))
                .filter(click -> StringUtils.isNotBlank(click.value()))
                .forEach(click -> {
                    if (click.by().equals(MobileLookup.ANDROID_TEXT)) {
                        endpoint.click(click::value, MobileLookup.ANDROID_TEXT);
                    } else {
                        endpoint.click(() -> constructDynamicElement(click.value(), click.isStringDynamicXpath(), args), click.by());
                    }
                    doAndroidWait(click.andWait(), endpoint, args);
                });
    }

    private static void doAndroidWait(MobileEndpoint mobileEndpoint, Method method, Object[] args) {
        Arrays.stream(method.getAnnotationsByType(AndroidWait.class))
                .forEach(w -> doAndroidWait(w, mobileEndpoint, args));
    }

    private static void doAndroidWait(AndroidWait androidWait, MobileEndpoint mobileEndpoint) {
        doAndroidWait(androidWait, mobileEndpoint, null);
    }

    private static void doAndroidWait(AndroidWait androidWait, MobileEndpoint mobileEndpoint, Object[] args) {
        doWait(androidWait.value(), androidWait.waitSecondsForElement(), androidWait.by(), androidWait.until(), androidWait.explicitWaitSeconds(), androidWait.isStringDynamicXpath(), mobileEndpoint, args);
    }


    private static void doAndroidScroll(Method method, MobileEndpoint mobileEndpoint) {
        Arrays.stream(method.getAnnotationsByType(AndroidScroll.class))
                .forEach(scroll -> {
                    doScroll(scroll.direction(), scroll, scroll.untilElementPresent(), scroll.by(), mobileEndpoint);
                    if (scroll.andClick()) {
                        mobileEndpoint.click(scroll::untilElementPresent, scroll.by());
                    }
                });
    }

    private static void doAndroidCheckElementContent(MobileEndpoint endpoint, Method method, Object[] args) {
        AndroidCheckElementContent[] elementContents = method.getAnnotationsByType(AndroidCheckElementContent.class);
        Arrays.stream(elementContents)
                .forEach(el -> doCheckElementContent(elementContents, el.value(), el.by(), endpoint, method, args));

    }

    private static void doAndroidType(MobileEndpoint endpoint, Method method, Object[] args) {
        AndroidTypeInto[] annotations = method.getAnnotationsByType(AndroidTypeInto.class);
        checkAnnotationLength(annotations, method, args);
        IntStream.range(0, annotations.length)
                .mapToObj(i -> Pair.of(annotations[i], String.valueOf(args[i])))
                .filter(p -> StringUtils.isNotBlank(p.getLeft().value()))
                .forEach(p -> {
                    endpoint.type(p.getLeft()::value, p.getRight(), p.getLeft().clear(), p.getLeft().by());
                    if (p.getLeft().hideKeyboardAfterType()) {
                        endpoint.getAndroid().hideAndroidKeyboard();
                    }
                    doAndroidWait(p.getLeft().andWait(), endpoint, args);
                });
    }

    public static Optional<Object> doAndroidGetElementContent(MobileEndpoint mobileEndpoint, Method method) {
        return Arrays.stream(method.getAnnotationsByType(AndroidGetElementContent.class))
                .filter(type -> StringUtils.isNotBlank(type.value()))
                .findFirst()
                .map(type -> Optional.ofNullable(mobileEndpoint.getText(type.value(), type.by()))
                        .filter(StringUtils::isNotBlank)
                        .orElseThrow(() -> new HarnessException("Element '" + type.value() + "' content could not be read")));
    }

    private static void doAndroidSelect(MobileEndpoint endpoint, Method method, Object[] args) {
        AndroidSelect[] annotations = method.getAnnotationsByType(AndroidSelect.class);
        if (annotations.length == 0) {
            return;
        }
        checkAnnotationLength(annotations, method, args);
        String value = getSelectValue(args[0]);

        IntStream.range(0, annotations.length)
                .mapToObj(i -> Pair.of(annotations[i], args[i]))
                .filter(AndroidBuilders::hasSelectValue)
                .forEach(p -> {
                    endpoint.click(() -> p.getLeft().value(), p.getLeft().by());
                    endpoint.scrollToElement(p.getLeft().scrollToValue(), p.getLeft().by(), p.getLeft().value());
                    endpoint.click(() -> value, MobileLookup.ANDROID_TEXT);
                });
    }

    //TODO: the same with iosCheckElementPresent, move to AnnotationsBuilder
    private static void doAndroidCheckElementPresent(MobileEndpoint endpoint, Method method, Object[] args) {
        AndroidCheckElementPresent[] annotations = method.getAnnotationsByType(AndroidCheckElementPresent.class);
        Arrays.stream(annotations)
                .filter(check -> StringUtils.isNotBlank(check.value()))
                .forEach(check -> {
                    try {
                        if (check.isStringDynamicXpath()) {
                            IntStream.range(0, args.length)
                                    .mapToObj(i -> Pair.of(annotations[i], args[i]))
                                    .forEach(elementPresentObjectPair -> endpoint.checkElementPresent(constructDynamicElement(elementPresentObjectPair.getLeft().value(), elementPresentObjectPair.getLeft().isStringDynamicXpath(), new Object[]{elementPresentObjectPair.getRight()}), elementPresentObjectPair.getLeft().by()));
                        } else {
                            endpoint.checkElementPresent(constructDynamicElement(check.value(), check.isStringDynamicXpath(), args), check.by());
                        }
                    } catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
                        throw new IllegalStateException(method.getDeclaringClass().getSimpleName() + "#" + method.getName() +
                                " The number of arguments passed to a AndroidCheckElementPresent-annotated method with isDynamicXpath = true parameter "
                                + "must match the annotation count!", e);
                    }
                });
    }

    private static boolean hasSelectValue(Pair<AndroidSelect, Object> pair) {
        return StringUtils.isNotBlank(pair.getLeft().value()) && pair.getRight() != null;
    }

    public static void androidBuilderOperations(MobileEndpoint endpoint, Method method, Object proxy, Object[] args) {
        doAndroidWait(endpoint, method, args);
        doAndroidScroll(method, endpoint);
        doAndroidClick(endpoint, method, args);
        doAndroidType(endpoint, method, args);
        doAndroidCheckElementContent(endpoint, method, args);
        doAndroidCheckElementPresent(endpoint, method, args);
        doAndroidSelect(endpoint, method, args);
        doCustomAction(proxy, method, args);
    }

    public static <A extends MobileFlow<A>> void processClassAnnotations(A builder, MobileEndpoint endpoint) {
        getInterfaces(builder.getClass()).stream()
                .flatMap(i -> Arrays.stream(i.getAnnotationsByType(AndroidWait.class)))
                .forEach(w -> doAndroidWait(w, endpoint));
    }
}