package datawave.webservice.query.model;

import java.util.Collections;
import java.util.Set;

import datawave.webservice.model.Direction;
import datawave.webservice.model.FieldMapping;

import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.hadoop.io.Text;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(ModelKeyParser.class)
@PowerMockIgnore("org.apache.log4j")
public class ModelKeyParserTest {
    
    private static final String MODEL_NAME = "MODEL";
    private static final String FIELD_NAME = "field1";
    private static final String MODEL_FIELD_NAME = "mappedField1";
    private static final String DATATYPE = "test";
    private static final String COLVIZ = "PRIVATE";
    private static final Direction FORWARD = Direction.FORWARD;
    private static final Direction REVERSE = Direction.REVERSE;
    private static final Set<Authorizations> AUTHS = Collections.singleton(new Authorizations("PRIVATE, PUBLIC"));
    private static FieldMapping FORWARD_FIELD_MAPPING = null;
    private static FieldMapping REVERSE_FIELD_MAPPING = null;
    private static FieldMapping NULL_CV_MAPPING = null;
    private static Key FORWARD_KEY = null;
    private static Key REVERSE_KEY = null;
    private static Key NULL_CV_KEY = null;
    private static Mutation FORWARD_MUTATION = null;
    private static Mutation FORWARD_DELETE_MUTATION = null;
    private static Mutation REVERSE_MUTATION = null;
    private static Mutation REVERSE_DELETE_MUTATION = null;
    
    private static long TIMESTAMP = System.currentTimeMillis();
    
    @Before
    public void setup() throws Exception {
        FORWARD_FIELD_MAPPING = new FieldMapping();
        FORWARD_FIELD_MAPPING.setColumnVisibility(COLVIZ);
        FORWARD_FIELD_MAPPING.setDatatype(DATATYPE);
        FORWARD_FIELD_MAPPING.setDirection(FORWARD);
        FORWARD_FIELD_MAPPING.setFieldName(FIELD_NAME);
        FORWARD_FIELD_MAPPING.setModelFieldName(MODEL_FIELD_NAME);
        REVERSE_FIELD_MAPPING = new FieldMapping();
        REVERSE_FIELD_MAPPING.setColumnVisibility(COLVIZ);
        REVERSE_FIELD_MAPPING.setDatatype(DATATYPE);
        REVERSE_FIELD_MAPPING.setDirection(REVERSE);
        REVERSE_FIELD_MAPPING.setFieldName(FIELD_NAME);
        REVERSE_FIELD_MAPPING.setModelFieldName(MODEL_FIELD_NAME);
        NULL_CV_MAPPING = new FieldMapping();
        NULL_CV_MAPPING.setColumnVisibility("");
        NULL_CV_MAPPING.setDatatype(DATATYPE);
        NULL_CV_MAPPING.setDirection(REVERSE);
        NULL_CV_MAPPING.setFieldName(FIELD_NAME);
        NULL_CV_MAPPING.setModelFieldName(MODEL_FIELD_NAME);
        FORWARD_KEY = new Key(MODEL_FIELD_NAME, MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(),
                        COLVIZ, TIMESTAMP);
        REVERSE_KEY = new Key(FIELD_NAME, MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(),
                        COLVIZ, TIMESTAMP);
        NULL_CV_KEY = new Key(FIELD_NAME, MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(),
                        "", TIMESTAMP);
        FORWARD_MUTATION = new Mutation(MODEL_FIELD_NAME);
        FORWARD_MUTATION.put(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(),
                        new ColumnVisibility(COLVIZ), TIMESTAMP, ModelKeyParser.NULL_VALUE);
        FORWARD_DELETE_MUTATION = new Mutation(MODEL_FIELD_NAME);
        FORWARD_DELETE_MUTATION.putDelete(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(),
                        new ColumnVisibility(COLVIZ), TIMESTAMP);
        FORWARD_DELETE_MUTATION.putDelete(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, FIELD_NAME + ModelKeyParser.NULL_BYTE + "index_only"
                        + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), new ColumnVisibility(COLVIZ), TIMESTAMP);
        
        REVERSE_MUTATION = new Mutation(FIELD_NAME);
        REVERSE_MUTATION.put(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(),
                        new ColumnVisibility(COLVIZ), TIMESTAMP, ModelKeyParser.NULL_VALUE);
        REVERSE_DELETE_MUTATION = new Mutation(FIELD_NAME);
        REVERSE_DELETE_MUTATION.putDelete(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(),
                        new ColumnVisibility(COLVIZ), TIMESTAMP);
        
        PowerMock.mockStatic(System.class, System.class.getMethod("currentTimeMillis"));
    }
    
    @Test
    public void testForwardKeyParse() throws Exception {
        FieldMapping mapping = ModelKeyParser.parseKey(FORWARD_KEY, AUTHS);
        Assert.assertEquals(FORWARD_FIELD_MAPPING, mapping);
        
        // Test ForwardKeyParse with no datatype
        FORWARD_FIELD_MAPPING.setDatatype(null);
        FORWARD_KEY = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), COLVIZ, TIMESTAMP);
        
        mapping = ModelKeyParser.parseKey(FORWARD_KEY, AUTHS);
        Assert.assertEquals(FORWARD_FIELD_MAPPING, mapping);
    }
    
    @Test
    public void testReverseKeyParse() throws Exception {
        FieldMapping mapping = ModelKeyParser.parseKey(REVERSE_KEY, AUTHS);
        Assert.assertEquals(REVERSE_FIELD_MAPPING, mapping);
        
        // Test ReverseKeyParse with no datatype
        REVERSE_FIELD_MAPPING.setDatatype(null);
        REVERSE_KEY = new Key(FIELD_NAME, MODEL_NAME, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(), COLVIZ, TIMESTAMP);
        mapping = ModelKeyParser.parseKey(REVERSE_KEY, AUTHS);
        Assert.assertEquals("ReverseKeyParse with no datatype failed.", REVERSE_FIELD_MAPPING, mapping);
    }
    
    @Test
    public void testForwardMappingParse() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Key k = ModelKeyParser.createKey(FORWARD_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        Assert.assertEquals(FORWARD_KEY, k);
        
        // Test forwardMappingParse with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        FORWARD_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        k = ModelKeyParser.createKey(FORWARD_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        FORWARD_KEY = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), COLVIZ, TIMESTAMP);
        PowerMock.verifyAll();
        Assert.assertEquals(FORWARD_KEY, k);
    }
    
    @Test
    public void testReverseMappingParse() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Key k = ModelKeyParser.createKey(REVERSE_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        Assert.assertEquals(REVERSE_KEY, k);
        
        // Test with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        REVERSE_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        REVERSE_KEY = new Key(FIELD_NAME, MODEL_NAME, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(), COLVIZ, TIMESTAMP);
        k = ModelKeyParser.createKey(REVERSE_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        Assert.assertEquals(REVERSE_KEY, k);
    }
    
    @Test
    public void testForwardCreateMutation() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Mutation m = ModelKeyParser.createMutation(FORWARD_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(FORWARD_MUTATION, m);
        
        // Test with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        FORWARD_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        m = ModelKeyParser.createMutation(FORWARD_FIELD_MAPPING, MODEL_NAME);
        FORWARD_MUTATION = new Mutation(MODEL_FIELD_NAME);
        FORWARD_MUTATION.put(MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), new ColumnVisibility(COLVIZ), TIMESTAMP,
                        ModelKeyParser.NULL_VALUE);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(FORWARD_MUTATION, m);
    }
    
    @Test
    public void testReverseCreateMutation() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Mutation m = ModelKeyParser.createMutation(REVERSE_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(REVERSE_MUTATION, m);
        
        // Test with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        REVERSE_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        m = ModelKeyParser.createMutation(REVERSE_FIELD_MAPPING, MODEL_NAME);
        REVERSE_MUTATION = new Mutation(FIELD_NAME);
        REVERSE_MUTATION.put(MODEL_NAME, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(), new ColumnVisibility(COLVIZ), TIMESTAMP,
                        ModelKeyParser.NULL_VALUE);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(REVERSE_MUTATION, m);
    }
    
    @Test
    public void testForwardCreateDeleteMutation() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP).times(2);
        PowerMock.replayAll();
        Mutation m = ModelKeyParser.createDeleteMutation(FORWARD_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(FORWARD_DELETE_MUTATION, m);
        
        // Test with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP).times(2);
        FORWARD_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        FORWARD_DELETE_MUTATION = new Mutation(MODEL_FIELD_NAME);
        FORWARD_DELETE_MUTATION.putDelete(MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), new ColumnVisibility(COLVIZ), TIMESTAMP);
        FORWARD_DELETE_MUTATION.putDelete(MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + "index_only" + ModelKeyParser.NULL_BYTE + FORWARD.getValue(),
                        new ColumnVisibility(COLVIZ), TIMESTAMP);
        m = ModelKeyParser.createDeleteMutation(FORWARD_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(FORWARD_DELETE_MUTATION, m);
    }
    
    @Test
    public void testReverseCreateDeleteMutation() throws Exception {
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Mutation m = ModelKeyParser.createDeleteMutation(REVERSE_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(REVERSE_DELETE_MUTATION, m);
        
        // Test with null datatype
        PowerMock.resetAll();
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        REVERSE_FIELD_MAPPING.setDatatype(null);
        PowerMock.replayAll();
        REVERSE_DELETE_MUTATION = new Mutation(FIELD_NAME);
        REVERSE_DELETE_MUTATION
                        .putDelete(MODEL_NAME, MODEL_FIELD_NAME + ModelKeyParser.NULL_BYTE + REVERSE.getValue(), new ColumnVisibility(COLVIZ), TIMESTAMP);
        m = ModelKeyParser.createDeleteMutation(REVERSE_FIELD_MAPPING, MODEL_NAME);
        PowerMock.verifyAll();
        m.getUpdates();
        Assert.assertEquals(REVERSE_DELETE_MUTATION, m);
    }
    
    @Test
    public void testParseKeyNullCV() throws Exception {
        FieldMapping mapping = ModelKeyParser.parseKey(NULL_CV_KEY, AUTHS);
        Assert.assertEquals(NULL_CV_MAPPING, mapping);
    }
    
    @Test
    public void testForwardMappingIndexOnlyParse() throws Exception {
        
        // Test with datatype
        FieldMapping forwardMapping = new FieldMapping();
        forwardMapping.setColumnVisibility(COLVIZ);
        forwardMapping.setDatatype(DATATYPE);
        forwardMapping.setDirection(FORWARD);
        forwardMapping.setFieldName(FIELD_NAME);
        forwardMapping.setModelFieldName(MODEL_FIELD_NAME);
        
        Key expectedForwardKey = new Key(MODEL_FIELD_NAME, MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE, FIELD_NAME + ModelKeyParser.NULL_BYTE
                        + FORWARD.getValue(), COLVIZ, TIMESTAMP);
        
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Key k = ModelKeyParser.createKey(forwardMapping, MODEL_NAME);
        Assert.assertEquals(expectedForwardKey, k);
        
        // Test without datatype
        PowerMock.resetAll();
        forwardMapping = new FieldMapping();
        forwardMapping.setColumnVisibility(COLVIZ);
        forwardMapping.setDirection(FORWARD);
        forwardMapping.setFieldName(FIELD_NAME);
        forwardMapping.setModelFieldName(MODEL_FIELD_NAME);
        
        expectedForwardKey = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue(), COLVIZ, TIMESTAMP);
        
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        k = ModelKeyParser.createKey(forwardMapping, MODEL_NAME);
        Assert.assertEquals(expectedForwardKey, k);
    }
    
    @Test
    public void testForwardIndexOnlyCreateMutation() throws Exception {
        FieldMapping forwardMapping = new FieldMapping();
        forwardMapping.setColumnVisibility("PRIVATE");
        forwardMapping.setDatatype(DATATYPE);
        forwardMapping.setDirection(FORWARD);
        forwardMapping.setFieldName(FIELD_NAME);
        forwardMapping.setModelFieldName(MODEL_FIELD_NAME);
        
        Mutation expectedforwardMutation = new Mutation(MODEL_FIELD_NAME);
        Text cf = new Text(MODEL_NAME + ModelKeyParser.NULL_BYTE + DATATYPE);
        Text cq = new Text(FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue());
        expectedforwardMutation.put(cf, cq, new ColumnVisibility(COLVIZ), TIMESTAMP, ModelKeyParser.NULL_VALUE);
        
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        Mutation m = ModelKeyParser.createMutation(forwardMapping, MODEL_NAME);
        m.getUpdates();
        Assert.assertTrue("Expected true: expectedforwardMutation.equals(m)", expectedforwardMutation.equals(m));
        
        // Without Datatype
        PowerMock.resetAll();
        forwardMapping = new FieldMapping();
        forwardMapping.setColumnVisibility(COLVIZ);
        forwardMapping.setDirection(FORWARD);
        forwardMapping.setFieldName(FIELD_NAME);
        forwardMapping.setModelFieldName(MODEL_FIELD_NAME);
        
        expectedforwardMutation = new Mutation(MODEL_FIELD_NAME);
        cf = new Text(MODEL_NAME);
        cq = new Text(FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue());
        expectedforwardMutation.put(cf, cq, new ColumnVisibility(COLVIZ), TIMESTAMP, ModelKeyParser.NULL_VALUE);
        
        EasyMock.expect(System.currentTimeMillis()).andReturn(TIMESTAMP);
        PowerMock.replayAll();
        m = ModelKeyParser.createMutation(forwardMapping, MODEL_NAME);
        m.getUpdates();
        Assert.assertTrue("Expected true: expectedforwardMutation.equals(m)", expectedforwardMutation.equals(m));
    }
    
    /**
     * Test boundary conditions on ForwardKeyParsing / Trigger failure conditions
     * 
     * @throws Exception
     */
    
    @Test(expected = IllegalArgumentException.class)
    public void testKeyWithNoDirection() throws Exception {
        // Test key with no direction
        Key keyNoDirection = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME, COLVIZ, TIMESTAMP);
        ModelKeyParser.parseKey(keyNoDirection, AUTHS);
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void testKeyWithInvalidDirection() throws Exception {
        Key keyWrongDirection = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + "someInvalidDirection", COLVIZ, TIMESTAMP);
        ModelKeyParser.parseKey(keyWrongDirection, AUTHS);
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void testKeyWithTooManyPartsInColQualifier() throws Exception {
        Key keyTooManyParts = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue() + ModelKeyParser.NULL_BYTE
                        + "index_only" + ModelKeyParser.NULL_BYTE + REVERSE.getValue(), COLVIZ, TIMESTAMP);
        ModelKeyParser.parseKey(keyTooManyParts, AUTHS);
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void testKeyWithIncorrectlyPositionedIndexOnlyAndDirection() throws Exception {
        // Correct cq: field\x00
        Key mismatchedParts = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + FORWARD.getValue() + ModelKeyParser.NULL_BYTE
                        + "index_only", COLVIZ, TIMESTAMP);
        ModelKeyParser.parseKey(mismatchedParts, AUTHS);
        Assert.fail("Expected IllegalArgumentException on key with 'index_only' and 'forward' in wrong positions.");
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void testIndexOnlyOnAReverseKeyIsInvalid() throws Exception {
        // Test index_only on a reverse key.. reverse keys should not have index_only
        Key reverseIndexOnly = new Key(MODEL_FIELD_NAME, MODEL_NAME, FIELD_NAME + ModelKeyParser.NULL_BYTE + "index_only" + REVERSE.getValue(), COLVIZ,
                        TIMESTAMP);
        ModelKeyParser.parseKey(reverseIndexOnly, AUTHS);
    }
}