package org.arquillian.cube.openshift.impl.enricher;

import io.fabric8.openshift.api.model.v3_1.Route;
import org.arquillian.cube.impl.util.ReflectionUtil;
import org.arquillian.cube.kubernetes.api.Configuration;
import org.arquillian.cube.openshift.impl.client.CubeOpenShiftConfiguration;
import org.arquillian.cube.openshift.impl.client.OpenShiftClient;
import org.jboss.arquillian.config.impl.extension.StringPropertyReplacer;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.test.spi.TestEnricher;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

import static org.arquillian.cube.openshift.impl.client.ResourceUtil.awaitRoute;

/**
 * RouteProxyProvider
 *
 * @author Rob Cernich
 */
public class RouteURLEnricher implements TestEnricher {

    @Inject
    private Instance<OpenShiftClient> clientInstance;

    @Inject
    private Instance<Configuration> configurationInstance;

    @Override
    public void enrich(Object testCase) {
        for (Field field : ReflectionUtil.getFieldsWithAnnotation(testCase.getClass(), RouteURL.class)) {
            Object url;
            AwaitRoute await;
            try {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                RouteURL routeURL = getAnnotation(RouteURL.class, field.getAnnotations());
                url = lookup(routeURL, field.getType());
                field.set(testCase, url);
                await = getAnnotation(AwaitRoute.class, field.getAnnotations());
            } catch (Exception e) {
                throw new RuntimeException("Could not set RouteURL value on field " + field, e);
            }
            configureAwaitRoute(url, await);
        }
    }

    @Override
    public Object[] resolve(Method method) {
        Object[] values = new Object[method.getParameterTypes().length];
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            RouteURL routeURL = getAnnotation(RouteURL.class, method.getParameterAnnotations()[i]);
            if (routeURL != null) {
                Object url = lookup(routeURL, method.getParameterTypes()[i]);
                values[i] = url;
                AwaitRoute await = getAnnotation(AwaitRoute.class, method.getParameterAnnotations()[i]);
                configureAwaitRoute(url, await);
            }
        }
        return values;
    }

    private <T extends Annotation> T getAnnotation(Class<T> annotationClass, Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if (annotation.annotationType() == annotationClass) {
                return annotationClass.cast(annotation);
            }
        }
        return null;
    }

    private Object lookup(RouteURL routeURL, Class<?> returnType) {
        if (routeURL == null) {
            throw new NullPointerException("RouteURL is null!");
        }

        final String routeName = StringPropertyReplacer.replaceProperties(routeURL.value());
        if (routeName == null || routeName.length() == 0) {
            throw new NullPointerException("Route name is null, must specify a route name!");
        }

        final CubeOpenShiftConfiguration config = (CubeOpenShiftConfiguration) configurationInstance.get();
        if (config == null) {
            throw new NullPointerException("CubeOpenShiftConfiguration is null.");
        }

        final OpenShiftClient client = clientInstance.get();
        final Route route = client.getClient().routes().inNamespace(config.getNamespace()).withName(routeName).get();
        if (route == null) {
            throw new IllegalArgumentException("Could not resolve route: " + routeName);
        }

        final String protocol = route.getSpec().getTls() == null ? "http" : "https";
        // adding the port number to the URL if it's equal to the default port number for given protocol
        // is 100% correct, but unexpected
        final int port;
        if ("http".equals(protocol)) {
            port = config.getOpenshiftRouterHttpPort() == 80 ? -1 : config.getOpenshiftRouterHttpPort();
        } else {
            port = config.getOpenshiftRouterHttpsPort() == 443 ? -1 : config.getOpenshiftRouterHttpsPort();
        }

        try {
            URL url = new URL(protocol, route.getSpec().getHost(), port, routeURL.path());
            if (returnType == URL.class) {
                return url;
            } else if (returnType == URI.class) {
                return url.toURI();
            } else if (returnType == String.class) {
                return url.toExternalForm();
            } else {
                throw new IllegalArgumentException("Invalid route injection type (can only handle URL, URI, String): " + returnType.getName());
            }
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to create route URL", e);
        }
    }

    private void configureAwaitRoute(Object route, AwaitRoute await) {
        // we _intentionally_ don't check if `configurationInstance.get().isWaitEnabled()` here;
        // even if awaiting readiness is disabled, we still want to await the route, because the user
        // explicitly opted into it, maybe because they want to workaround a Fabric8 Kubernetes Client
        // issue and rely on @AwaitRoute instead

        if (await == null) {
            return;
        }

        URL url;
        try {
            url = new URL(route.toString());
            if (!AwaitRoute.DEFAULT_PATH_FOR_ROUTE_AVAILABILITY_CHECK.equals(await.path())) {
                url = new URL(url.getProtocol(), url.getHost(), url.getPort(), await.path());
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        awaitRoute(url, await.timeout(), await.timeoutUnit(), await.statusCode());
    }
}

@@ -97,16 +97,70 @@
 .Route;%0A
+import io.fabric8.openshift.api.model.v3_1.RouteList;%0A
 import o
@@ -818,16 +818,128 @@
 net.URL;
+%0Aimport java.util.ArrayList;%0Aimport java.util.Arrays;%0Aimport java.util.List;%0Aimport java.util.stream.Collectors;
 %0A%0Aimport
@@ -4021,82 +4021,323 @@
     
-throw new IllegalArgumentException(%22Could not resolve route: %22 + routeName
+List%3CRoute%3E availableRoutes = client.getClient().routes().inNamespace(config.getNamespace()).list().getItems();%0A            throw new IllegalArgumentException(%22Could not resolve route: %22 + routeName + %22. Available routes: %22 + availableRoutes.stream().map(r -%3E r.getMetadata().getName()).collect(Collectors.toList())
 );%0A 

Improve could not resolve route message (#926)