package cz.senslog.connector.fetch.azure.auth;

import cz.senslog.connector.tools.http.HttpRequest;
import cz.senslog.connector.model.config.DefaultConfig;
import cz.senslog.connector.tools.http.HttpClient;
import cz.senslog.connector.tools.http.HttpCode;
import cz.senslog.connector.tools.http.HttpResponse;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cz.senslog.connector.tools.json.BasicJson.objectToJson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class AuthenticationServiceTest {

    @Test
    void getAccessToken_Valid_Response_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
            AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
            authInfo.setExpires(LocalDateTime.now().plusDays(1));
            authInfo.setAccessToken("#12345");
            List<AzureAuthorizationInfo> azureInfos = new ArrayList<>(1);
            azureInfos.add(authInfo);
            return HttpResponse.newBuilder()
                    .status(HttpCode.OK).body(objectToJson(azureInfos)).build();
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_EmptyResponse_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                List<AzureAuthorizationInfo> body = new ArrayList<>();
                if (invoked) {
                    AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                    authInfo.setExpires(LocalDateTime.now().plusDays(1));
                    authInfo.setAccessToken("#12345");
                    body.add(authInfo);
                }
                invoked = true;
                return HttpResponse.newBuilder()
                        .status(HttpCode.OK).body(objectToJson(body)).build();
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_UnauthorizedResponse_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                if (invoked) {
                    List<AzureAuthorizationInfo> body = new ArrayList<>();
                    AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                    authInfo.setExpires(LocalDateTime.now().plusDays(1));
                    authInfo.setAccessToken("#12345");
                    body.add(authInfo);
                    return HttpResponse.newBuilder()
                            .status(HttpCode.OK).body(objectToJson(body)).build();
                } else {
                    invoked = true;
                    Map<String, String> body = new HashMap<>();
                    body.put("Message", "Unauthorized");
                    return HttpResponse.newBuilder()
                            .status(HttpCode.UNAUTHORIZED).body(objectToJson(body)).build();
                }
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_ErrorResponse_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                if (invoked) {
                    List<AzureAuthorizationInfo> body = new ArrayList<>();
                    AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                    authInfo.setExpires(LocalDateTime.now().plusDays(1));
                    authInfo.setAccessToken("#12345");
                    body.add(authInfo);
                    return HttpResponse.newBuilder()
                            .status(HttpCode.OK).body(objectToJson(body)).build();
                } else {
                    invoked = true;
                    return HttpResponse.newBuilder()
                            .status(HttpCode.SERVER_ERROR).body("Server Error").build();
                }
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_ExpiredToken_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                List<AzureAuthorizationInfo> body = new ArrayList<>();
                AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                authInfo.setAccessToken("#12345");
                body.add(authInfo);
                if (invoked) {
                    authInfo.setExpires(LocalDateTime.now().plusDays(1));
                } else {
                    invoked = true;
                    authInfo.setExpires(LocalDateTime.now().minusDays(1));
                }
                return HttpResponse.newBuilder()
                        .status(HttpCode.OK).body(objectToJson(body)).build();
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_NotJsonResponse_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                if (invoked) {
                    List<AzureAuthorizationInfo> body = new ArrayList<>();
                    AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                    authInfo.setExpires(LocalDateTime.now().plusDays(1));
                    authInfo.setAccessToken("#12345");
                    body.add(authInfo);
                    return HttpResponse.newBuilder()
                            .status(HttpCode.OK).body(objectToJson(body)).build();
                } else {
                    invoked = true;
                    return HttpResponse.newBuilder()
                            .status(HttpCode.OK).body("invalid_json").build();
                }
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }

    @Test
    void getAccessToken_BlankToken_True() {

        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.send(any(HttpRequest.class))).thenAnswer(new Answer<HttpResponse>() {
            private boolean invoked = false;
            @Override public HttpResponse answer(InvocationOnMock invocationOnMock) {
                List<AzureAuthorizationInfo> body = new ArrayList<>();
                AzureAuthorizationInfo authInfo = new AzureAuthorizationInfo();
                authInfo.setExpires(LocalDateTime.now().plusDays(1));
                body.add(authInfo);
                if (invoked) {
                    authInfo.setAccessToken("#12345");
                } else {
                    invoked = true;
                    authInfo.setAccessToken("   ");
                }
                return HttpResponse.newBuilder()
                        .status(HttpCode.OK).body(objectToJson(body)).build();
            }
        });

        DefaultConfig defaultConfig = new DefaultConfig("auth", any());
        defaultConfig.setProperty("username", "username");
        defaultConfig.setProperty("password", "password");
        defaultConfig.setProperty("refreshPeriodIfFail", 100);
        defaultConfig.setProperty("host", new HashMap<String, Object>(){{
            put("domain", "http://test.com");
            put("path", "test");
        }});
        AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);

        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);

        assertEquals("#12345", service.getAccessToken());
    }
}