package net.openid.appauth;

import static net.openid.appauth.TestValues.TEST_APP_REDIRECT_URI;
import static net.openid.appauth.TestValues.TEST_ID_TOKEN;
import static net.openid.appauth.TestValues.TEST_STATE;
import static net.openid.appauth.TestValues.getTestServiceConfig;

import android.net.Uri;

import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 16)
public class EndSessionRequestTest {

    private EndSessionRequest.Builder mRequestBuilder;

    @Before
    public void setUp() {
        mRequestBuilder = new EndSessionRequest.Builder(getTestServiceConfig());
    }

    /* ********************************** Builder() ***********************************************/
    @Test(expected = NullPointerException.class)
    public void testBuilder_nullConfiguration() {
        new EndSessionRequest.Builder(null);
    }

    /* ******************************** id_token_hint *********************************************/

    @Test
    public void testIdTokenHint_unspecified() {
        EndSessionRequest request = mRequestBuilder.build();
        assertThat(request.idTokenHint).isNull();
    }

    @Test
    public void testIdTokenHint() {
        EndSessionRequest request = mRequestBuilder
            .setIdTokenHint(TEST_ID_TOKEN)
            .build();
        assertThat(request.idTokenHint).isEqualTo(TEST_ID_TOKEN);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIdTokenHint_empty() {
        mRequestBuilder.setIdTokenHint("").build();
    }

    /* *************************** post_logout_redirect_uri ***************************************/

    @Test
    public void testPostLogoutRedirectUri_unspecified() {
        EndSessionRequest request = mRequestBuilder.build();
        assertThat(request.postLogoutRedirectUri).isNull();
    }

    @Test
    public void testPostLogoutRedirectUri() {
        EndSessionRequest request = mRequestBuilder
            .setPostLogoutRedirectUri(TEST_APP_REDIRECT_URI)
            .build();
        assertThat(request.postLogoutRedirectUri).isEqualTo(TEST_APP_REDIRECT_URI);
    }

    /* *********************************** state **************************************************/

    @Test
    public void testState_autoGenerated() {
        EndSessionRequest request = mRequestBuilder.build();
        assertThat(request.state).isNotEmpty();
    }

    @Test
    public void testState() {
        EndSessionRequest request = mRequestBuilder
            .setState(TEST_STATE)
            .build();
        assertThat(request.state).isEqualTo(TEST_STATE);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testState_empty() {
        mRequestBuilder.setState("").build();
    }

    /* ******************************** ui_locales ************************************************/

    @Test
    public void testUiLocales_unspecified() {
        EndSessionRequest request = mRequestBuilder.build();
        assertThat(request.uiLocales).isNull();
        assertThat(request.getUiLocales()).isNull();
    }

    @Test
    public void testUiLocales() {
        EndSessionRequest req = mRequestBuilder
            .setUiLocales("en de fr-CA")
            .build();

        assertThat(req.uiLocales).isEqualTo("en de fr-CA");
        assertThat(req.getUiLocales())
            .hasSize(3)
            .contains("en")
            .contains("de")
            .contains("fr-CA");
    }

    @Test
    public void testUiLocales_nullValue() {
        EndSessionRequest req = mRequestBuilder.setUiLocales(null).build();
        assertThat(req.uiLocales).isNull();
        assertThat(req.getUiLocales()).isNull();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUiLocales_empty() {
        mRequestBuilder.setUiLocales("").build();
    }

    @Test
    public void testUiLocales_withVarargs() {
        EndSessionRequest req = mRequestBuilder
            .setUiLocalesValues("en", "de", "fr-CA")
            .build();

        assertThat(req.uiLocales).isEqualTo("en de fr-CA");
        assertThat(req.getUiLocales())
            .hasSize(3)
            .contains("en")
            .contains("de")
            .contains("fr-CA");
    }

    @Test
    public void testUiLocales_withNullVarargsArray() {
        EndSessionRequest req = mRequestBuilder.setUiLocalesValues((String[])null).build();
        assertThat(req.uiLocales).isNull();
        assertThat(req.getUiLocales()).isNull();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUiLocales_withNullStringInVarargs() {
        mRequestBuilder.setUiLocalesValues("en", null).build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUiLocales_withEmptyStringInVarargs() {
        mRequestBuilder.setUiLocalesValues("en", "").build();
    }

    @Test
    public void testUiLocales_withIterable() {
        EndSessionRequest req = mRequestBuilder
            .setUiLocalesValues(Arrays.asList("en", "de", "fr-CA"))
            .build();

        assertThat(req.uiLocales).isEqualTo("en de fr-CA");

        assertThat(req.getUiLocales())
            .hasSize(3)
            .contains("en")
            .contains("de")
            .contains("fr-CA");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUiLocales_withIterableContainingNullValue() {
        mRequestBuilder
            .setUiLocalesValues(Arrays.asList("en", null))
            .build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUiLocales_withIterableContainingEmptyValue() {
        mRequestBuilder
            .setUiLocalesValues(Arrays.asList("en", ""))
            .build();
    }

    /* ******************************* additionalParams *******************************************/

    @Test(expected = IllegalArgumentException.class)
    public void testBuilder_setAdditionalParams_withBuiltInParam() {
        Map<String, String> additionalParams = new HashMap<>();
        additionalParams.put(EndSessionRequest.PARAM_STATE, TEST_STATE);
        mRequestBuilder.setAdditionalParameters(additionalParams);
    }

    /* ******************************* toUri() ****************************************************/

    @Test
    public void testToUri() {
        EndSessionRequest request = TestValues.getTestEndSessionRequest();
        Uri requestUri = request.toUri();

        assertThat(requestUri.getQueryParameter(EndSessionRequest.PARAM_ID_TOKEN_HINT))
            .isEqualTo(request.idTokenHint);
        assertThat(requestUri.getQueryParameter(EndSessionRequest.PARAM_POST_LOGOUT_REDIRECT_URI))
            .isEqualTo(request.postLogoutRedirectUri.toString());
        assertThat(requestUri.getQueryParameter(EndSessionRequest.PARAM_STATE))
            .isEqualTo(request.state);

    }

    @Test
    public void testToUri_noState() throws Exception {
        EndSessionRequest req = mRequestBuilder.setState(null).build();
        assertThat(req.toUri().getQueryParameterNames())
            .doesNotContain(AuthorizationRequest.PARAM_STATE);
    }

    @Test
    public void testToUri_uiLocalesParam() {
        Uri uri = mRequestBuilder
            .setUiLocales("en de fr-CA")
            .build()
            .toUri();
        assertThat(uri.getQueryParameterNames())
            .contains(EndSessionRequest.PARAM_UI_LOCALES);
        assertThat(uri.getQueryParameter(EndSessionRequest.PARAM_UI_LOCALES))
            .isEqualTo("en de fr-CA");
    }

    @Test
    public void testToUri_additionalParams() throws Exception {
        Map<String, String> additionalParams = new HashMap<>();
        additionalParams.put("my_param", "1234");
        additionalParams.put("another_param", "5678");
        EndSessionRequest req = mRequestBuilder
            .setAdditionalParameters(additionalParams)
            .build();

        Uri uri = req.toUri();
        assertThat(uri.getQueryParameter("my_param"))
            .isEqualTo("1234");
        assertThat(uri.getQueryParameter("another_param"))
            .isEqualTo("5678");
    }

    /* ************************** jsonSerialize() / jsonDeserialize() *****************************/

    @Test
    public void testJsonSerialize() throws Exception {
        EndSessionRequest request = TestValues.getTestEndSessionRequest();
        EndSessionRequest copy = serializeDeserialize(request);
        assertThat(copy.idTokenHint).isEqualTo(TEST_ID_TOKEN);
        assertThat(copy.state).isEqualTo(request.state);
        assertThat(copy.postLogoutRedirectUri).isEqualTo(request.postLogoutRedirectUri);
    }

    @Test
    public void testJsonSerialize_uiLocales() throws Exception {
        EndSessionRequest copy = serializeDeserialize(
            mRequestBuilder.setUiLocales("en de fr-CA").build());
        assertThat(copy.uiLocales).isEqualTo("en de fr-CA");
    }

    @Test
    public void testJsonSerialize_additionalParams() throws Exception {
        Map<String, String> additionalParams = new HashMap<>();
        additionalParams.put("my_param", "1234");
        additionalParams.put("another_param", "5678");

        EndSessionRequest copy = serializeDeserialize(
            mRequestBuilder.setAdditionalParameters(additionalParams).build());
        assertThat(copy.additionalParameters).isEqualTo(additionalParams);
    }

    private EndSessionRequest serializeDeserialize(EndSessionRequest request)
        throws JSONException {
        return EndSessionRequest.jsonDeserialize(request.jsonSerializeString());
    }

}
