/*
 * Copyright (c) 2015, salesforce.com, inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *    Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *    following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *    the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 *    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
 *    promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.salesforce.dataloader.mapping;

import com.salesforce.dataloader.mapping.SOQLInfo.SOQLFieldInfo;
import com.salesforce.dataloader.mapping.SOQLInfo.SOQLParserException;
import org.junit.Assert;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
 * Tests parsing SOQLInfo
 * 
 * @author Colin Jarvis
 * @since 21.0
 */
public class SoqlInfoTest {

    @Test
    public void testParseSoql() throws SOQLParserException {
        SOQLInfo soqlInfo = new SOQLInfo("select account.id, blarney.name from account blarney");
        assertEquals("blarney", soqlInfo.getTableAlias());
        assertEquals("account", soqlInfo.getTableName());
        assertEquals(2, soqlInfo.getSelectedFields().size());
        doFieldAssertions(soqlInfo.getSelectedFields().get(0), "account.id");
        doFieldAssertions(soqlInfo.getSelectedFields().get(1), "blarney.name");
    }

    @Test
    public void testParseSoql3() throws SOQLParserException {
        SOQLInfo soqlInfo = new SOQLInfo("Select Id, Name From Account Blarney");
        assertEquals("Blarney", soqlInfo.getTableAlias());
        assertEquals("Account", soqlInfo.getTableName());
        assertEquals(2, soqlInfo.getSelectedFields().size());
        doFieldAssertions(soqlInfo.getSelectedFields().get(0), "Id");
        doFieldAssertions(soqlInfo.getSelectedFields().get(1), "Name");
    }

    @Test
    public void testParseSoql1() throws SOQLParserException {
        SOQLInfo soqlInfo = new SOQLInfo("select id, name from account blarney where id = ''");
        assertEquals("blarney", soqlInfo.getTableAlias());
        assertEquals("account", soqlInfo.getTableName());
        assertEquals(2, soqlInfo.getSelectedFields().size());
        doFieldAssertions(soqlInfo.getSelectedFields().get(0), "id");
        doFieldAssertions(soqlInfo.getSelectedFields().get(1), "name");
    }

    @Test
    public void testParseSoql2() throws SOQLParserException {
        SOQLInfo soqlInfo = new SOQLInfo("select id, name from account  where id = ''");
        assertEquals("", soqlInfo.getTableAlias());
        assertEquals("account", soqlInfo.getTableName());
        assertEquals(2, soqlInfo.getSelectedFields().size());
        doFieldAssertions(soqlInfo.getSelectedFields().get(0), "id");
        doFieldAssertions(soqlInfo.getSelectedFields().get(1), "name");
    }

    private void doFieldAssertions(SOQLInfo.SOQLFieldInfo fieldInfo, String expectedFieldName) {
        assertNull(fieldInfo.getAggregateFunction());
        assertNull(fieldInfo.getAlias());
        assertEquals(expectedFieldName, fieldInfo.getFieldName());
    }

    @Test
    public void testNoFields() throws SOQLParserException {
        try {
            new SOQLInfo("select  from account  where id = ''");
            Assert.fail("should not be able to parse query");
        } catch (IllegalArgumentException e) {
            assertEquals("Cannot parse empty string", e.getMessage());
        }
    }

    @Test
    public void testParseAggregateFields() throws SOQLParserException {
        SOQLInfo info = new SOQLInfo("SELECT max(fld) xXx FROM account blarney");
        assertEquals("blarney", info.getTableAlias());
        assertEquals("account", info.getTableName());
        assertEquals(1, info.getSelectedFields().size());
        SOQLFieldInfo fieldInfo = info.getSelectedFields().get(0);
        assertEquals("max", fieldInfo.getAggregateFunction());
        assertEquals("xXx", fieldInfo.getAlias());
        assertEquals("fld", fieldInfo.getFieldName());
    }

    @Test
    public void testParseAggregateFields1() throws SOQLParserException {
        SOQLInfo info = new SOQLInfo("select max(fld)  from account blarney");
        assertEquals("blarney", info.getTableAlias());
        assertEquals("account", info.getTableName());
        assertEquals(1, info.getSelectedFields().size());
        SOQLFieldInfo fieldInfo = info.getSelectedFields().get(0);
        assertEquals("max", fieldInfo.getAggregateFunction());
        assertEquals("expr0", fieldInfo.getAlias());
        assertEquals("fld", fieldInfo.getFieldName());
    }

    @Test
    public void testAggregateExpressionAliases() throws SOQLParserException {
        SOQLInfo info = new SOQLInfo("select max(fld1), min(fld2) fld2_min, max(fld3)  from account blarney");
        doSoqlInfoAssertions(info, "account", "blarney", 3);
        List<SOQLFieldInfo> fields = info.getSelectedFields();
        doFieldInfoAssertions(fields.get(0), "fld1", "max", "expr0");
        doFieldInfoAssertions(fields.get(1), "fld2", "min", "fld2_min");
        doFieldInfoAssertions(fields.get(2), "fld3", "max", "expr1");
    }

    private void doSoqlInfoAssertions(SOQLInfo soqlInfo, String tableName, String tableAlias, int numFields) {
        assertEquals(tableName, soqlInfo.getTableName());
        assertEquals(tableAlias, soqlInfo.getTableAlias());
        assertEquals(numFields, soqlInfo.getSelectedFields().size());
    }

    private void doFieldInfoAssertions(SOQLFieldInfo fieldInfo, String expectedFieldName, String expectedAggFunc,
            String expectedAlias) {
        assertEquals(expectedFieldName, fieldInfo.getFieldName());
        assertEquals(expectedAggFunc, fieldInfo.getAggregateFunction());
        assertEquals(expectedAlias, fieldInfo.getAlias());
    }

    @Test
    public void testParseAggregateMissingField() throws SOQLParserException {
        try {
            new SOQLInfo("select max() xxx from account blarney");
            Assert.fail("should not be able to parse query");
        } catch (IllegalArgumentException e) {
            assertEquals("Cannot parse empty string", e.getMessage());
        }
    }

    @Test
    public void testNoSelect() {
        runInvalidQueryTest("id, name from account blarney", "No 'SELECT' keyword");
    }

    @Test
    public void testNoFrom() {
        try {
            new SOQLInfo("select id, name account blarney");
            Assert.fail("should not be able to parse query");
        } catch (SOQLParserException e) {
            assertEquals("Invalid soql: No 'FROM' keyword", e.getMessage());
        }
    }

    @Test
    public void testNestedQuery() {
        runInvalidQueryTest("select id, (select name from contacts) from account blarney",
                "Nested queries are not supported");
    }

    @Test
    public void testMissingTableName() {
        runInvalidQueryTest("select id from where Name='sometext'", "Failed to parse table name");
        runInvalidQueryTest("select id from", "No sobject specified after 'FROM' keyword");
    }

    @Test
    public void testGroupQuery() throws SOQLParserException {
        new SOQLInfo("select id from group");
    }

    @Test
    public void testOrderQuery() throws SOQLParserException {
        new SOQLInfo("select id from order");
    }

    @Test
    public void testWhereValidation() throws Exception {
        SOQLInfo info = runValidQueryTest("select id from account WHERE id != null");
        assertEquals("", info.getTableAlias());

        runInvalidQueryTest("select id FrOm wHeRe id != null", "Failed to parse table name");
    }

    @Test
    public void testReservedKeywords() throws Exception {
        runValidQueryTest("select id from from__c");
        runValidQueryTest("select from__c from from__c");
        runValidQueryTest("select id from where__c");
        runValidQueryTest("select where__c from where__c");
        runValidQueryTest("select where__c from where__c where where__c='abc'");
        runValidQueryTest("select select__c from from__c where where__c='abc'");
    }

    private SOQLInfo runValidQueryTest(String query) throws SOQLParserException {
        return new SOQLInfo(query);
    }

    private void runInvalidQueryTest(String query, String expectedMessage) {
        try {
            new SOQLInfo(query);
            Assert.fail("should not be able to parse query: " + query);
        } catch (SOQLParserException e) {
            assertEquals("Invalid soql: " + expectedMessage, e.getMessage());
        }
    }
}
