package ru.yandex.direct.api.v5.ws;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ws.server.endpoint.MethodEndpoint;
import org.springframework.ws.transport.context.TransportContext;
import org.springframework.ws.transport.context.TransportContextHolder;
import org.springframework.ws.transport.http.HttpServletConnection;


/**
 * Несколько полезных функций, для работы в контексте spring-ws
 */
public class WsUtils {
    private static final String INVALID_ENDPOINT_ERROR_MESSAGE = "Invalid endpoint object";

    private WsUtils() {
    }

    /**
     * Получить текущий {@link HttpServletRequest}
     *
     * @throws IllegalStateException - если по какой-то причине не удалось получить объект запроса
     */
    @Nonnull
    public static HttpServletRequest getHttpServletRequest() {
        return getHttpServletConnection().map(HttpServletConnection::getHttpServletRequest)
                .orElseThrow(() -> new IllegalStateException("TransportContext is not defined"));
    }

    public static HttpServletResponse getHttpServletResponse() {
        return getHttpServletConnection().map(HttpServletConnection::getHttpServletResponse)
                .orElseThrow(() -> new IllegalStateException("TransportContext is not defined"));
    }

    private static Optional<HttpServletConnection> getHttpServletConnection() {
        return Optional.ofNullable(TransportContextHolder.getTransportContext())
                .map(TransportContext::getConnection)
                .map(HttpServletConnection.class::cast);
    }

    /**
     * Получить аннотацию переданного endpoint-а
     *
     * @throws IllegalStateException - если не удалось получить аннотацию
     */
    @Nonnull
    public static <T extends Annotation> T getEndpointMethodMeta(Object endpointObject, Class<T> annotationClass) {
        return getEndpointMethodMetaOpt(endpointObject, annotationClass)
                .orElseThrow(() -> new IllegalStateException(INVALID_ENDPOINT_ERROR_MESSAGE));
    }

    /**
     * Получить все аннотации заданного типа, которые есть у переданного endpoint-а
     *
     * @throws IllegalStateException - если не удалось получить аннотации
     */
    @Nonnull
    public static <T extends Annotation> T[] getEndpointMethodAnnotations(Object endpointObject,
                                                                          Class<T> annotationClass) {
        return Optional.ofNullable(endpointObject)
                .map(MethodEndpoint.class::cast)
                .map(MethodEndpoint::getMethod)
                .map(x -> x.getAnnotationsByType(annotationClass))
                .orElseThrow(() -> new IllegalStateException(INVALID_ENDPOINT_ERROR_MESSAGE));
    }

    /**
     * Получить аннотацию на классе переданного endpoint-а
     *
     * @throws IllegalStateException - если не удалось получить аннотацию
     */
    @Nonnull
    public static <T extends Annotation> T getEndpointClassMeta(Object endpointObject, Class<T> annotationClass) {
        return getEndpointClassMetaOpt(endpointObject, annotationClass)
                .orElseThrow(() -> new IllegalStateException(INVALID_ENDPOINT_ERROR_MESSAGE));
    }

    /**
     * Получить аннотацию на методе или классе переданного endpoint-а
     *
     * @throws IllegalStateException - если не удалось получить аннотацию
     */
    @Nonnull
    public static <T extends Annotation> T getEndpointMethodOrClassMeta(Object endpointObject,
                                                                        Class<T> annotationClass) {
        return getEndpointMethodMetaOpt(endpointObject, annotationClass)
                .orElse(getEndpointClassMetaOpt(endpointObject, annotationClass)
                        .orElseThrow(() -> new IllegalStateException(INVALID_ENDPOINT_ERROR_MESSAGE)));
    }

    @Nonnull
    public static <T extends Annotation> Optional<T> getEndpointMethodMetaOpt(Object endpointObject,
                                                                               Class<T> annotationClass) {
        return Optional.ofNullable(endpointObject)
                .map(MethodEndpoint.class::cast)
                .map(MethodEndpoint::getMethod)
                .map(x -> x.getAnnotation(annotationClass));
    }

    @Nonnull
    private static <T extends Annotation> Optional<T> getEndpointClassMetaOpt(Object endpointObject,
                                                                              Class<T> annotationClass) {
        return Optional.ofNullable(endpointObject)
                .map(MethodEndpoint.class::cast)
                .map(MethodEndpoint::getBean)
                .map(Object::getClass)
                .map(cl -> AnnotationUtils.findAnnotation(cl, annotationClass));
    }

    /**
     * Кешированное получение по объекту ответа метода-геттера, который возможно возвращает список ActionResult-ов
     * Логика такая:
     * - метод должен начинаться на get и заканчиваться на Results
     * - метод должен быть без параметров
     *
     * @param clazz Класс объекта ответа из messageContext.response.apiResponsePayload
     * @return Метод, возвращающий список результатов из объекта ответа
     */
    @Nonnull
    public static Optional<Method> takeResultsGetter(Class clazz) {
        for (Method method : clazz.getMethods()) {
            if (method.getName().startsWith("get")
                    && method.getName().endsWith("Results")
                    && method.getParameterCount() == 0) {
                method.setAccessible(true);
                return Optional.of(method);
            }
        }
        return Optional.empty();
    }

}
