package cz.moneta.test.harness.support.ws;

import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.BaseWsEndpoint;
import cz.moneta.test.harness.support.util.BaseRequestBuilder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

@SuppressWarnings("unchecked")
public final class Builders {

    private Builders() {
    }

    public static <RESP, R extends WsRequest<RESP>> R newWsBuilder(
            Object request, Class<RESP> responseType, Class<? extends BaseWsEndpoint> endpoint, StoreAccessor harness) {
        return (R) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{WsRequest.class},
                new RequestBuilderInvocationHandler<>(request, responseType, harness.getEndpoint(endpoint), harness));
    }

    private static class RequestBuilderInvocationHandler<RESP> extends BaseRequestBuilder<RESP> implements InvocationHandler {

        private final Object request;
        private final Class<RESP> responseType;
        private final BaseWsEndpoint endpoint;

        private RequestBuilderInvocationHandler(Object request, Class<RESP> responseType, BaseWsEndpoint endpoint, StoreAccessor harness) {
            super(harness);
            this.request = request;
            this.responseType = responseType;
            this.endpoint = endpoint;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            return Stream.<Supplier<Optional<?>>>of(
                    () -> handleLog(proxy, method, args),
                    () -> handlePollUntil(proxy, method, args),
                    () -> handlePollDelay(proxy, method, args),
                    () -> handlePollRetries(proxy, method, args),
                    () -> handlePollEvalFunction(proxy, method, args),
                    () -> handleSend(proxy, method))
                    .map(Supplier::get)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .findFirst()
                    .orElseThrow(() -> new IllegalStateException("Could not handle method: " +
                            proxy.getClass().getInterfaces()[0].getSimpleName() + "#" + method.getName()));
        }

        private Optional<RESP> handleSend(Object proxy, Method method) {
            return Optional.of(method)
                    .filter(m -> "send".equals(m.getName()))
                    .flatMap(m -> doSend(proxy));
        }

        protected Optional<RESP> doSend(Object proxy) {
            return Optional.ofNullable(endpoint.invoke(request, responseType))
                    .map(resp -> {
                        harness.store(responseType.getName(), resp);
                        responseLoggers.forEach(l -> l.accept(resp));
                        return resp;
                    });
        }
    }
}
