/**
 * 
 */
package com.stratio.deep.cassandra.cql;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

import com.datastax.driver.core.ColumnMetadata;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.TableMetadata;
import com.stratio.deep.cassandra.config.CassandraDeepJobConfig;
import com.stratio.deep.cassandra.filter.value.EqualsInValue;
import com.stratio.deep.commons.rdd.DeepTokenRange;
import com.stratio.deep.commons.utils.Pair;

/**
 *
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({ CassandraClientProvider.class })
public class DeepRecordReaderTest {

    private final static int PAGE_SIZE_CONSTANT = 1000;

    private final static String TABLE_NAME_CONSTANT = "TABLENAME";

    private final static String[] INPUT_COLUMNS_CONSTANT = { "col1", "col2", "col3" };

    private final static String LOCALHOST_CONSTANT = "localhost";

    @Mock
    private DeepTokenRange<Long, String> tokenRange;

    @Mock
    private CassandraDeepJobConfig<?> config;

    @Mock
    private Session session;

    @Mock
    private TableMetadata tableMetadata;

    @Mock
    private ColumnMetadata columnMetadata;

    @Mock
    private EqualsInValue equalsInValue;

    @Mock
    private ResultSet resultSet;

    @Test
    public void testEqualsInForDeepRecordReader() {

        // Static stubs
        PowerMockito.mockStatic(CassandraClientProvider.class);

        // Stubbing
        when(config.getPageSize()).thenReturn(PAGE_SIZE_CONSTANT);
        when(config.getTable()).thenReturn(TABLE_NAME_CONSTANT);
        when(config.getInputColumns()).thenReturn(INPUT_COLUMNS_CONSTANT);
        when(config.fetchTableMetadata()).thenReturn(tableMetadata);
        when(config.getEqualsInValue()).thenReturn(equalsInValue);
        when(config.getPartitionerClassName()).thenReturn("org.apache.cassandra.dht.Murmur3Partitioner");

        when(tokenRange.getReplicas()).thenReturn(Arrays.asList(LOCALHOST_CONSTANT));
        when(tokenRange.getStartTokenAsComparable()).thenReturn(-8000000000000000000L, -7000000000000000000L,
                2200000000000000000L, 2300000000000000000L, 2600000000000000000L);
        when(tokenRange.getEndTokenAsComparable()).thenReturn(-7000000000000000000L, 2200000000000000000L,
                2300000000000000000L, 2600000000000000000L, -8000000000000000000L);

        when(CassandraClientProvider.trySessionForLocation(any(String.class), any(CassandraDeepJobConfig.class),
                any(Boolean.class))).thenReturn(Pair.create(session, LOCALHOST_CONSTANT));

        when(tableMetadata.getPartitionKey()).thenReturn(Arrays.asList(columnMetadata, columnMetadata));
        when(tableMetadata.getClusteringColumns()).thenReturn(new ArrayList<ColumnMetadata>());

        when(columnMetadata.getName()).thenReturn("col1", "col2");
        when(columnMetadata.getType()).thenReturn(DataType.bigint());

        when(equalsInValue.getEqualsList()).thenReturn(Arrays.asList(Pair.create("col1", (Serializable) 1L)));
        when(equalsInValue.getInField()).thenReturn("col2");
        when(equalsInValue.getInValues()).thenReturn(
                Arrays.asList((Serializable) 1L, (Serializable) 2L, (Serializable) 3L, (Serializable) 4L,
                        (Serializable) 5L));

        Object[] values = { 1L, Arrays.asList(1L, 4L) };
        SimpleStatement stmt = new SimpleStatement(
                "SELECT \"col1\",\"col2\",\"col3\" FROM \"TABLENAME\" WHERE col1 = ? AND col2 IN ?", values);
        when(session.execute(any(Statement.class))).thenReturn(resultSet);

        DeepRecordReader recordReader = new DeepRecordReader(config, tokenRange);

        // TODO How do we check two statements are the same?
        verify(session, times(1)).execute(Matchers.argThat(new StatementMatcher(stmt)));
    }

    class StatementMatcher extends BaseMatcher<SimpleStatement> {

        private final SimpleStatement expectedStmt;

        public StatementMatcher(SimpleStatement expectedStmt) {
            this.expectedStmt = expectedStmt;
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean matches(Object obj) {
            if (obj != null && obj instanceof SimpleStatement) {
                SimpleStatement receivedStmt = (SimpleStatement) obj;
                Object[] expectedValues = Whitebox.getInternalState(expectedStmt, "values");
                Object[] receivedValues = Whitebox.getInternalState(receivedStmt, "values");

                return matchValues(expectedValues, receivedValues);
            }
            return false;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description)
         */
        @Override
        public void describeTo(Description description) {
            description.appendText("Matches a class or subclass");
        }

        private boolean matchValues(Object[] expectedValues, Object[] receivedValues) {

            boolean match = true;
            int pointer = 0;
            while (pointer < expectedValues.length && match) {
                if (expectedValues[pointer] instanceof Long) {
                    Long expectedValue = (Long) expectedValues[pointer];
                    Long receivedValue = (Long) receivedValues[pointer];
                    match = expectedValue.equals(receivedValue);
                } else if (expectedValues[pointer] instanceof List) {
                    List<Long> expectedValue = (List<Long>) expectedValues[pointer];
                    List<Long> receivedValue = (List<Long>) receivedValues[pointer];
                    match = expectedValue.equals(receivedValue);
                } else {
                    match = expectedValues[pointer].equals(receivedValues[pointer]);
                }

                pointer++;
            }

            return match;
        }
    }
}