package com.consol.citrus.simulator.http;

import com.consol.citrus.http.message.HttpMessage;
import com.consol.citrus.simulator.scenario.*;
import org.mockito.Mockito;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.mockito.Mockito.when;

public class HttpRequestAnnotationMatcherTest {

    HttpRequestAnnotationMatcher cut = HttpRequestAnnotationMatcher.instance();

    private static final RequestMapping REQ_MAP_WITH_PATH_NAME = getRequestMapping(new ScenarioWithPathName());
    private static final RequestMapping REQ_MAP_WITH_PATH_VALUE = getRequestMapping(new ScenarioWithPathValue());
    private static final RequestMapping REQ_MAP_WITH_PATH_PLACEHOLDER = getRequestMapping(new ScenarioWithPathContainingPlaceholder());
    private static final RequestMapping REQ_MAP_WITH_PATH_PATTERN = getRequestMapping(new ScenarioWithPathPattern());
    private static final RequestMapping REQ_MAP_WITH_PUT_METHOD = getRequestMapping(new ScenarioWithPutMethod());
    private static final RequestMapping REQ_MAP_WITH_QUERY_PARAMS = getRequestMapping(new ScenarioWithQueryParams());
    private static final RequestMapping REQ_MAP_WITH_ALL_SUPPORTED_RESTRICTIONS = getRequestMapping(new ScenarioWithAllSupportedRestrictions());

    @DataProvider
    public Object[][] dpScenarios() {
        return new Object[][]{
                new Object[]{
                        REQ_MAP_WITH_PATH_NAME,
                        setupHttpMessage("/path/name", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_NAME,
                        setupHttpMessage("/path/name", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_NAME,
                        setupHttpMessage("/path/wrong-path", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_NAME,
                        setupHttpMessage("", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_PATH_VALUE,
                        setupHttpMessage("/path/value", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_VALUE,
                        setupHttpMessage("/path/wrong-path", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_VALUE,
                        setupHttpMessage("", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_PATH_PLACEHOLDER,
                        setupHttpMessage("/path/place-holder/123", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PLACEHOLDER,
                        setupHttpMessage("/path/place-holder/123", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PLACEHOLDER,
                        setupHttpMessage("/path/wrong-path", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PLACEHOLDER,
                        setupHttpMessage("/path/wrong-path", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("/path/pattern/match-me", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("/path/pattern/match-me", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("/path/wrong-pattern", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("/path/wrong-pattern", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_PATH_PATTERN,
                        setupHttpMessage("", HttpMethod.GET, Collections.emptyMap()),
                        false,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_PUT_METHOD,
                        setupHttpMessage("/any-path", HttpMethod.PUT, Collections.emptyMap()),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_PUT_METHOD,
                        setupHttpMessage("", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_QUERY_PARAMS,
                        setupHttpMessage("/any-path", HttpMethod.GET, Collections.singletonMap("a", "1")),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_QUERY_PARAMS,
                        setupHttpMessage("/any-path", HttpMethod.GET, Collections.singletonMap("a", null)),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_QUERY_PARAMS,
                        setupHttpMessage("/any-path", HttpMethod.GET, Stream.of("a=1","b=2").map(item -> item.split("=")).collect(Collectors.toMap(keyValuePair -> keyValuePair[0], keyValuePair -> keyValuePair[1]))),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_QUERY_PARAMS,
                        setupHttpMessage("/any-path", HttpMethod.GET, Collections.singletonMap("c", "3")),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_QUERY_PARAMS,
                        setupHttpMessage("/any-path", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },

                new Object[]{
                        REQ_MAP_WITH_ALL_SUPPORTED_RESTRICTIONS,
                        setupHttpMessage("/path/value", HttpMethod.GET, Collections.singletonMap("a", "1")),
                        true,
                        true
                },
                new Object[]{
                        REQ_MAP_WITH_ALL_SUPPORTED_RESTRICTIONS,
                        setupHttpMessage("/wrong-path", HttpMethod.GET, Collections.singletonMap("a", "1")),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_ALL_SUPPORTED_RESTRICTIONS,
                        setupHttpMessage("/path/value", HttpMethod.PUT, Collections.singletonMap("a", "1")),
                        true,
                        false
                },
                new Object[]{
                        REQ_MAP_WITH_ALL_SUPPORTED_RESTRICTIONS,
                        setupHttpMessage("/path/value", HttpMethod.GET, Collections.emptyMap()),
                        true,
                        false
                },
        };
    }

    @Test(dataProvider = "dpScenarios")
    public void testCheckRequestSupported(RequestMapping requestMapping, HttpMessage httpMessage, boolean exactMatch, boolean expectedResult) throws Exception {
        boolean actual = cut.checkRequestPathSupported(httpMessage, requestMapping, exactMatch)
                && cut.checkRequestMethodSupported(httpMessage, requestMapping)
                && cut.checkRequestQueryParamsSupported(httpMessage, requestMapping);
        Assert.assertEquals(actual, expectedResult);
    }

    @Scenario("ScenarioWithPathName")
    @RequestMapping("/path/name")
    private static class ScenarioWithPathName extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithPathValue")
    @RequestMapping(value = "/path/value")
    private static class ScenarioWithPathValue extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithPathContainingPlaceholder")
    @RequestMapping(value = "/path/place-holder/{value}")
    private static class ScenarioWithPathContainingPlaceholder extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithPathPattern")
    @RequestMapping(value = "/path/pattern/*")
    private static class ScenarioWithPathPattern extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithPutMethod")
    @RequestMapping(method = {RequestMethod.PUT})
    private static class ScenarioWithPutMethod extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithQueryParams")
    @RequestMapping(params = {"a", "!b"})
    private static class ScenarioWithQueryParams extends AbstractSimulatorScenario {
    }

    @Scenario("ScenarioWithAllSupportedRestrictions")
    @RequestMapping(method = RequestMethod.GET, value = "/path/value", params = {"a", "!b"})
    private static class ScenarioWithAllSupportedRestrictions extends AbstractSimulatorScenario {
    }

    private static RequestMapping getRequestMapping(SimulatorScenario scenario) {
        return AnnotationUtils.findAnnotation(scenario.getClass(), RequestMapping.class);
    }

    private HttpMessage setupHttpMessage(String path, HttpMethod method, Map<String, String> queryParams) {
        final HttpMessage httpMessage = Mockito.mock(HttpMessage.class);
        when(httpMessage.getPath()).thenReturn(path);
        when(httpMessage.getRequestMethod()).thenReturn(method);
        when(httpMessage.getQueryParams()).thenReturn(queryParams);
        return httpMessage;
    }
}