/*
 * Copyright Avaya Inc., All Rights Reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Avaya Inc.
 *
 * The copyright notice above does not evidence any actual or intended
 * publication of such source code.
 *
 * Some third-party source code components may have been modified from their
 * original versions by Avaya Inc.
 *
 * The modifications are Copyright Avaya Inc., All Rights Reserved.
 */

package com.avaya.oceanalytics.openinterface.websocketclient;

import com.avaya.oceanalytics.openinterface.websocketclient.auth.AuthenticationTokenCache;
import com.avaya.oceanalytics.openinterface.websocketclient.auth.UserCredentials;
import com.avaya.oceanalytics.openinterface.websocketclient.subscription.StreamType;
import com.avaya.oceanalytics.openinterface.websocketclient.subscription.*;
import com.avaya.oceanalytics.openinterface.websocketclient.websocket.RealtimeDataProvider;
import com.avaya.oceanalytics.openinterface.websocketclient.subscription.SubscriptionProducer;
import com.avaya.oceanalytics.openinterface.websocketclient.websocket.SuppressibleRealtimeDataHandler;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.collections.Sets;
import org.springframework.beans.factory.ObjectFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

/**
 * Test cases for the {@link ShellCommandHandler} class.
 *
 * @author Ivan Kovalev
 *
 */
public class TestShellCommandHandler {

    private static final String SOURCE_ID = "Oceana_118_Baelish_10.134.44.118_3.5";
    private static final String PRODUCER_ID = "RoutingService_SoD_3.5";
    private static final StreamType STREAM_TYPE = StreamType.SOD;
    private static final String DIMENSION = "Channel.Chan|Location.Inhouse";
    private static final String MEASURE = "AbandonedAfterThreshold";
    private static final String USERNAME = "user";
    private static final String PASSWORD = "password";
    private static final String TOKEN = "token";
    private static final String TENANT_ID = "1";
    private static final String ENDPOINT = "/advertime";

    private static final ByteArrayInputStream INPUT = new ByteArrayInputStream("abracadabra and ENTER\r\n".getBytes());
    private static final ByteArrayOutputStream OUTPUT = new ByteArrayOutputStream();
    private static final ByteArrayOutputStream ERROR = new ByteArrayOutputStream();

    @Mock
    private RestClient restClient;
    @Mock
    private ObjectFactory<UserCredentials> credentialsFactory;
    @Mock
    private AuthenticationTokenCache authTokenCache;
    @Mock
    private SubscriptionManager manager;
    @Mock
    private SuppressibleRealtimeDataHandler rtdHandler;
    @Mock
    private Subscription subscription;
    @Mock
    private SubscriptionResponse subscriptionResponse;
    @Mock
    private RealtimeDataProvider realtimeDataProvider;
    @Mock
    private SubscriptionProducer subscriptionProducer;

    private ShellCommandHandler shell;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        UserCredentials credentials = mock(UserCredentials.class);
        when(credentials.getUsername()).thenReturn(USERNAME);
        when(credentials.getPassword()).thenReturn(PASSWORD);
        when(credentialsFactory.getObject()).thenReturn(credentials);
        when(authTokenCache.getToken(credentials)).thenReturn(TOKEN);
    }

    @After
    public void cleanup() {
        INPUT.reset();
        OUTPUT.reset();
        ERROR.reset();
    }

    @Test
    public void testSubscribeForProducer() throws Exception {
        when(restClient.subscribeForRealtimeData(eq(TOKEN),any(SubscriptionRequest.class))).thenReturn(getSubscriptionResponse());
        initShell();
        shell.subscribeForRealtimeData(SOURCE_ID,PRODUCER_ID,
                STREAM_TYPE,TENANT_ID,USERNAME,PASSWORD);
        assertTrue(ERROR.toString().isEmpty());
        verify(restClient,times(1)).subscribeForRealtimeData(eq(TOKEN),any(SubscriptionRequest.class));
        verify(manager,times(1)).associate(eq(PRODUCER_ID), any(Subscription.class));
        verify(manager,never()).dissociate(eq(PRODUCER_ID));
    }

    @Test
    public void testUnsubscribeForProducer() throws Exception {
        when(restClient.subscribeForRealtimeData(eq(TOKEN),any(SubscriptionRequest.class))).thenReturn(subscriptionResponse);
        when(manager.getSubscription(PRODUCER_ID)).thenReturn(subscription);
        when(subscription.getRtdDataProvider()).thenReturn(realtimeDataProvider);
        initShell();
        shell.unsubscribeForRealtimeData(PRODUCER_ID,USERNAME,PASSWORD);
        assertTrue(ERROR.toString().isEmpty());
        verify(realtimeDataProvider,times(1)).disconnect();
        verify(manager,times(1)).dissociate(PRODUCER_ID);
    }


    @Test
    public void testListSubscription() throws Exception {
        List<SubscriptionResponse> subscriptionResponseList = Arrays.asList(subscriptionResponse);
        when(restClient.getListOfSubscriptions(TOKEN,SOURCE_ID)).thenReturn(subscriptionResponseList);
        initShell();
        shell.listSubscriptions(SOURCE_ID,USERNAME,PASSWORD,true);
        assertTrue(ERROR.toString().isEmpty());
        assertFalse(OUTPUT.toString().isEmpty());
        verify(restClient,times(1)).getListOfSubscriptions(TOKEN,SOURCE_ID);
        verify(manager, times(1)).getAllSubscriptions();
    }

    @Test
    public void testResume() throws Exception {
        when(manager.getAllSubscriptions()).thenReturn(Sets.newSet("subscription1","subscription2"));
        initShell();
        shell.resume();
        assertTrue(ERROR.toString().isEmpty());
        verify(rtdHandler,times(1)).suppress(eq(false));
        verify(rtdHandler,times(1)).suppress(eq(true));
    }


    private void setPrivateFinalField(String fieldName, Object value) throws Exception {
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        Field targetField = ShellCommandHandler.class.getDeclaredField(fieldName);
        modifiersField.setInt(targetField, targetField.getModifiers() & ~Modifier.FINAL);
        targetField.setAccessible(true);
        targetField.set(shell, value);
    }

    private void initShell() throws Exception {
        shell = new ShellCommandHandler(restClient,credentialsFactory,authTokenCache,manager,rtdHandler,subscriptionProducer,realtimeDataProvider);
        setPrivateFinalField("outputStream", OUTPUT);
        setPrivateFinalField("errStream", ERROR);
        setPrivateFinalField("inputScanner", new Scanner(INPUT));
    }

    private SubscriptionResponse getSubscriptionResponse() {
        SubscriptionResponse response = new SubscriptionResponse();
        response.setProducer("producer");
        response.setGuid("guid");
        response.setEndpoint(ENDPOINT);
        response.setProducerId(PRODUCER_ID);
        response.setSource("source");
        response.setSourceId("source id");
        response.setStreamType("SOD");
        response.setTransport("WebSocket");
        response.setVersion("1");
        return response;
    }
}
