/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-3112
// https://issues.jboss.org/browse/TEIID-3912
// https://issues.jboss.org/browse/TEIID-4284
// https://issues.redhat.com/browse/TEIID-5988
// https://issues.redhat.com/browse/TEIID-4439
package org.teiid.translator.salesforce.execution;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;

import java.io.IOException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
 
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.teiid.cdk.api.TranslationUtility;
import org.teiid.language.Select;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.query.unittest.TimestampUtil;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.salesforce.SalesForceExecutionFactory;
import org.teiid.translator.salesforce.SalesforceConnection;
import org.teiid.translator.salesforce.SalesforceConnection.BatchResultInfo;
import org.teiid.translator.salesforce.SalesforceConnection.BulkBatchResult;
import org.teiid.translator.salesforce.execution.QueryExecutionImpl.BulkValidator;
import org.teiid.translator.salesforce.execution.visitors.TestVisitors;
 
import com.sforce.async.JobInfo;
import com.sforce.async.OperationEnum;
import com.sforce.soap.partner.QueryResult;
import com.sforce.soap.partner.sobject.SObject;

@SuppressWarnings("nls")
public class TestQueryExecutionImpl {
	
	private static TranslationUtility translationUtility = new TranslationUtility(TestVisitors.exampleSalesforce());

	@Test public void testBatching() throws Exception {
		Select command = (Select)translationUtility.parseCommand("select Name from Account"); //$NON-NLS-1$
		SalesforceConnection sfc = Mockito.mock(SalesforceConnection.class);
		QueryResult qr = new QueryResult();
		SObject so = new SObject();
		so.setType("Account");
		so.addField("Name", null);
		qr.setRecords(new SObject[] {so});
		qr.setDone(false);
		QueryResult finalQr = new QueryResult();
		finalQr.setRecords(new SObject[] {so});
		finalQr.setDone(true);
		Mockito.when(sfc.query("SELECT Name FROM Account", 0, false)).thenReturn(qr);
		Mockito.when(sfc.queryMore(null, 0)).thenReturn(finalQr);
		QueryExecutionImpl qei = new QueryExecutionImpl(command, sfc, Mockito.mock(RuntimeMetadata.class), Mockito.mock(ExecutionContext.class), new SalesForceExecutionFactory());
		qei.execute();
		assertNotNull(qei.next());
		assertNotNull(qei.next());
		assertNull(qei.next());
	}

	@Test public void testJoin() throws Exception {
		Select command = (Select)translationUtility.parseCommand("select Account.Name, Contact.Id from Account inner join Contact on Account.Id = Contact.AccountId"); //$NON-NLS-1$
		SalesforceConnection sfc = Mockito.mock(SalesforceConnection.class);
		QueryResult qr = new QueryResult();
		SObject so = new SObject();
		so.setType("Account");
		so.addField("Name", "account name");
		SObject so1 = new SObject();
		so1.setType("Contact");
		so1.addField("Id", "contact id");
		so.addField("Contacts", so1);
		qr.setRecords(new SObject[] {so});
		qr.setDone(true);
		Mockito.when(sfc.query("SELECT Account.Name, Id FROM Contact WHERE AccountId != NULL", 0, false)).thenReturn(qr);
		QueryExecutionImpl qei = new QueryExecutionImpl(command, sfc, Mockito.mock(RuntimeMetadata.class), Mockito.mock(ExecutionContext.class), new SalesForceExecutionFactory());
		qei.execute();
		assertEquals(Arrays.asList("account name", "contact id"), qei.next());
		assertNull(qei.next());
	}
	
	@BeforeAll static public void oneTimeSetup() {
		TimeZone.setDefault(TimeZone.getTimeZone("GMT-06:00"));
	}
	
	@AfterAll static public void oneTimeTearDown() {
		TimeZone.setDefault(null);
	}
	
	@Test public void testValueParsing() throws Exception {
		assertEquals(TimestampUtil.createTime(2, 0, 0), QueryExecutionImpl.parseDateTime("08:00:00.000Z", Time.class, Calendar.getInstance()));
	}
	
	@Test public void testValueParsing1() throws Exception {
		assertEquals(TimestampUtil.createTimestamp(101, 0, 1, 2, 0, 0, 1000000), QueryExecutionImpl.parseDateTime("2001-01-01T08:00:00.001Z", Timestamp.class, Calendar.getInstance()));
	}
	
	@Test public void testRetrieve() throws Exception {
		Select command = (Select)translationUtility.parseCommand("select Name from Account where id = 'abc'"); //$NON-NLS-1$
		SalesforceConnection sfc = Mockito.mock(SalesforceConnection.class);

		Mockito.when(sfc.retrieve("Name", "Account", Arrays.asList("abc"))).thenReturn(new SObject[] {null});
		QueryExecutionImpl qei = new QueryExecutionImpl(command, sfc, Mockito.mock(RuntimeMetadata.class), Mockito.mock(ExecutionContext.class), new SalesForceExecutionFactory());
		qei.execute();
		assertNull(qei.next());
	}
	
	@Test public void testBulkValidator() throws Exception {
	   helpTestBulkValidator("select Name from Account where id = 'abc'", true, true);
	   helpTestBulkValidator("select max(Name) from Account", false, false);
	   helpTestBulkValidator("select Name from Account limit 1", true, false);
	   helpTestBulkValidator("select Name from Account limit 1,1", false, false);
	}

	private void helpTestBulkValidator(String sql, boolean bulk, boolean pkChunk) {
	   Select command = (Select)translationUtility.parseCommand(sql); //$NON-NLS-1$
	   BulkValidator validator = new BulkValidator();
	   validator.visit(command);
	   assertEquals(bulk, validator.isBulkEligible());
	   assertEquals(pkChunk, validator.usePkChunking());
	}

    @Test
    public void testBulkFlow() throws Exception {
        Select command = (Select)translationUtility.parseCommand("select Name from Account"); //$NON-NLS-1$

        SalesforceConnection connection = Mockito.mock(SalesforceConnection.class);
        JobInfo jobInfo = Mockito.mock(JobInfo.class);

        Mockito.when(connection.createBulkJob(Mockito.anyString(), Mockito.eq(OperationEnum.query), Mockito.eq(true))).thenReturn(jobInfo);

        final BatchResultInfo info = new BatchResultInfo("x");

        Mockito.when(connection.getBatchQueryResults(any(), any(BatchResultInfo.class))).thenAnswer(new Answer<BulkBatchResult>() {
            boolean first = true;
            @Override
            public BulkBatchResult answer(InvocationOnMock invocation)
                            throws Throwable {
                if (first) {
                    first = false;
                    throw new DataNotAvailableException();
                }
                if (info.getAndIncrementResultNum() == 0) {
                    final Iterator<List<String>> i = Arrays.asList(Arrays.asList("Name"), Arrays.asList("X")).iterator();
					return new BulkBatchResult() {

	                   @Override
	                   public List<String> nextRecord() throws IOException {
	                       if (!i.hasNext()) {
	                           return null;
	                       }
	                       return i.next();
	                   }

	                   @Override
	                   public void close() {

	                   }
                    };
                }
                return null;
            }
        });

        Mockito.when(connection.addBatch("SELECT Name FROM Account", jobInfo)).thenReturn(info);

        ExecutionContext mock = Mockito.mock(ExecutionContext.class);
        Mockito.when(mock.getSourceHint()).thenReturn("bulk");

        QueryExecutionImpl execution = new QueryExecutionImpl(command, connection, Mockito.mock(RuntimeMetadata.class), mock, new SalesForceExecutionFactory());

        execution.execute();

        try {
            execution.next();
            fail();
        } catch (DataNotAvailableException e) {

        }
        List<?> row = execution.next();
        assertEquals(Arrays.asList("X"), row);
        assertNull(execution.next());
    }

	@Test
	public void testBulkFlow_NotAllowedTables() throws Exception {
		Select command = (Select)translationUtility.parseCommand("select Type from FeedItem"); //$NON-NLS-1$

		SalesforceConnection connection = Mockito.mock(SalesforceConnection.class);
		JobInfo jobInfo = Mockito.mock(JobInfo.class);

		Mockito.when(connection.createBulkJob(Mockito.anyString(), Mockito.eq(OperationEnum.query), Mockito.eq(false))).thenReturn(jobInfo);

		final BatchResultInfo info = new BatchResultInfo("x");

		Mockito.when(connection.getBatchQueryResults(Mockito.any(), any(BatchResultInfo.class))).thenAnswer(new Answer<BulkBatchResult>() {
			boolean first = true;
			@Override
			public BulkBatchResult answer(InvocationOnMock invocation)
					throws Throwable {
				if (first) {
					first = false;
					throw new DataNotAvailableException();
				}
				if (info.getAndIncrementResultNum() == 0) {
					final Iterator<List<String>> i = Arrays.asList(Arrays.asList("Type"), Arrays.asList("CollaborationGroupCreated")).iterator();
					return new BulkBatchResult() {

						@Override
						public List<String> nextRecord() throws IOException {
							if (!i.hasNext()) {
								return null;
							}
							return i.next();
						}

						@Override
						public void close() {

						}
					};
				}
				return null;
			}
		});

		Mockito.when(connection.addBatch("SELECT Type FROM FeedItem", jobInfo)).thenReturn(info);

		ExecutionContext mock = Mockito.mock(ExecutionContext.class);
		Mockito.when(mock.getSourceHint()).thenReturn("bulk");

		QueryExecutionImpl execution = new QueryExecutionImpl(command, connection, Mockito.mock(RuntimeMetadata.class), mock, new SalesForceExecutionFactory());

		execution.execute();

		try {
			execution.next();
			fail();
		} catch (DataNotAvailableException e) {

		}
		List<?> row = execution.next();
		assertEquals(Arrays.asList("CollaborationGroupCreated"), row);
		assertNull(execution.next());
	}
}
    

