/**
 * The MIT License (MIT)
 * Copyright (c) 2017 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.microsoft.azure.documentdb.bulkimport;

import static com.microsoft.azure.documentdb.bulkimport.TestUtils.getBulkImportStoredProcedureResponse;
import static com.microsoft.azure.documentdb.bulkimport.TestUtils.getStoredProcedureResponse;
import static com.microsoft.azure.documentdb.bulkimport.TestUtils.getThrottleException;
import static com.microsoft.azure.documentdb.bulkimport.TestUtils.withRequestCharge;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;

import com.google.common.collect.Iterators;
import com.microsoft.azure.documentdb.DocumentClient;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.RequestOptions;
import com.microsoft.azure.documentdb.StoredProcedureResponse;

public class BatchInserterTests {


    @Test
    public void callbackCount() {
        BulkImportStoredProcedureOptions options = null;
        String bulkImportSproc = null;
        DocumentClient client = Mockito.mock(DocumentClient.class);
        List<List<String>> batchesToInsert = new ArrayList<>();
        batchesToInsert.add(new ArrayList<>());
        batchesToInsert.add(new ArrayList<>());
        batchesToInsert.add(new ArrayList<>());

        String partitionIndex = "0";
        BatchInserter bi = new BatchInserter(partitionIndex, batchesToInsert, client, bulkImportSproc, options);

        Iterator<Callable<InsertMetrics>> callbackIterator = bi.miniBatchInsertExecutionCallableIterator();

        List<Callable<InsertMetrics>> list = new ArrayList<>();
        Iterators.addAll(list, callbackIterator);

        assertThat(list.size(), equalTo(3));
    }

    @Test
    public void simple() throws Exception {

        BulkImportStoredProcedureOptions options = null;
        String bulkImportSproc = null;
        DocumentClient client = Mockito.mock(DocumentClient.class);
        List<List<String>> batchesToInsert = new ArrayList<>();
        batchesToInsert.add(new ArrayList<>());

        int numberOfDocuments = 10;
        for (int i = 0; i < numberOfDocuments; i++) {
            batchesToInsert.get(0).add("{}");
        }

        String partitionIndex = "0";
        BatchInserter bi = new BatchInserter(partitionIndex, batchesToInsert, client, bulkImportSproc, options);

        Iterator<Callable<InsertMetrics>> callbackIterator = bi.miniBatchInsertExecutionCallableIterator();

        List<Callable<InsertMetrics>> list = new ArrayList<>();
        Iterators.addAll(list, callbackIterator);

        assertThat(list.size(), equalTo(1));

        Callable<InsertMetrics> callable = list.get(0);

        Map<String, String> headers = withRequestCharge(null, 5.5);

        StoredProcedureResponse bulkImportResponse = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(numberOfDocuments, 0), headers);

        when(client.executeStoredProcedure(Mockito.any(String.class), Mockito.any(RequestOptions.class),
                Mockito.any(Object[].class))).thenReturn(bulkImportResponse);

        InsertMetrics metrics = callable.call();

        verify(client, Mockito.times(1)).executeStoredProcedure(Mockito.anyString(), Mockito.any(RequestOptions.class), Mockito.any(Object[].class));

        // verify all documents inserted
        assertThat(metrics.numberOfDocumentsInserted, equalTo(10l));
        // verify no throttle
        assertThat(metrics.numberOfThrottles, equalTo(0l));
        // verify request charge
        assertThat(metrics.requestUnitsConsumed, equalTo(5.5));
        // verify non zero total time
        assertThat(metrics.timeTaken.getNano() > 0, equalTo(true));

        assertThat(bi.getNumberOfDocumentsImported(), equalTo((int) metrics.numberOfDocumentsInserted));
        assertThat(bi.getTotalRequestUnitsConsumed(), equalTo(metrics.requestUnitsConsumed));
    }

    @Test
    @Ignore
    public void partialProgress() throws Exception {

        BulkImportStoredProcedureOptions options = null;
        String bulkImportSproc = null;
        DocumentClient client = Mockito.mock(DocumentClient.class);
        List<List<String>> batchesToInsert = new ArrayList<>();
        batchesToInsert.add(new ArrayList<>());

        int numberOfDocuments = 10;
        for (int i = 0; i < numberOfDocuments; i++) {
            batchesToInsert.get(0).add("{}");
        }

        String partitionIndex = "0";
        BatchInserter bi = new BatchInserter(partitionIndex, batchesToInsert, client, bulkImportSproc, options);

        Iterator<Callable<InsertMetrics>> callbackIterator = bi.miniBatchInsertExecutionCallableIterator();

        List<Callable<InsertMetrics>> list = new ArrayList<>();
        Iterators.addAll(list, callbackIterator);

        assertThat(list.size(), equalTo(1));

        Callable<InsertMetrics> callable = list.get(0);

        // 3 documents progress
        StoredProcedureResponse bulkImportResponse1 = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(3, 1),  withRequestCharge(null, 1.5));
        // 1 document progress
        StoredProcedureResponse bulkImportResponse2 = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(1, 1),  withRequestCharge(null, 2.5));
        // 0 document progress
        StoredProcedureResponse bulkImportResponse3 = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(0, 1),  withRequestCharge(null, 3.5));
        // 6 document progress
        StoredProcedureResponse bulkImportResponse4 = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(6, 1),  withRequestCharge(null, 4.5));

        when(client.executeStoredProcedure(Mockito.any(String.class), Mockito.any(RequestOptions.class),
                Mockito.any(Object[].class)))
        .thenReturn(bulkImportResponse1)
        .thenReturn(bulkImportResponse2)
        .thenReturn(bulkImportResponse3)
        .thenReturn(bulkImportResponse4);

        InsertMetrics metrics = callable.call();

        verify(client, Mockito.times(4)).executeStoredProcedure(Mockito.anyString(), Mockito.any(RequestOptions.class), Mockito.any(Object[].class));

        // verify all documents inserted
        assertThat(metrics.numberOfDocumentsInserted, equalTo(10l));
        // verify no throttle
        assertThat(metrics.numberOfThrottles, equalTo(0l));
        // verify request charge
        assertThat(metrics.requestUnitsConsumed, equalTo(1.5+ 2.5 + 3.5 + 4.5));
        // verify non zero total time
        assertThat(metrics.timeTaken.getNano() > 0, equalTo(true));

        assertThat(bi.getNumberOfDocumentsImported(), equalTo((int) metrics.numberOfDocumentsInserted));
        assertThat(bi.getTotalRequestUnitsConsumed(), equalTo(metrics.requestUnitsConsumed));
    }

    @Test
    public void throttle() throws Exception {

        BulkImportStoredProcedureOptions options = new BulkImportStoredProcedureOptions(true, true, "fakeCollectionId", true);

        String bulkImportSproc = null;
        DocumentClient client = Mockito.mock(DocumentClient.class);
        List<List<String>> batchesToInsert = new ArrayList<>();
        batchesToInsert.add(new ArrayList<>());

        int numberOfDocuments = 10;
        for (int i = 0; i < numberOfDocuments; i++) {
            batchesToInsert.get(0).add("{}");
        }

        String partitionIndex = "0";
        BatchInserter bi = new BatchInserter(partitionIndex, batchesToInsert, client, bulkImportSproc, options);

        Iterator<Callable<InsertMetrics>> callbackIterator = bi.miniBatchInsertExecutionCallableIterator();

        List<Callable<InsertMetrics>> list = new ArrayList<>();
        Iterators.addAll(list, callbackIterator);

        assertThat(list.size(), equalTo(1));

        Callable<InsertMetrics> callable = list.get(0);

        Map<String, String> headers = withRequestCharge(null, 5.5);

        StoredProcedureResponse bulkImportResponse = getStoredProcedureResponse(getBulkImportStoredProcedureResponse(numberOfDocuments, 0), headers);

        DocumentClientException throttleException = getThrottleException();

        when(client.executeStoredProcedure(Mockito.any(String.class), Mockito.any(RequestOptions.class),
                Mockito.any(Object[].class))).thenThrow(throttleException).thenReturn(bulkImportResponse);

        InsertMetrics metrics = callable.call();

        // verify that execute stored proc is invoked twice (once with throttle and second time without throttle)
        verify(client, Mockito.times(2)).executeStoredProcedure(Mockito.anyString(), Mockito.any(RequestOptions.class), Mockito.any(Object[].class));

        // verify all documents inserted
        assertThat(metrics.numberOfDocumentsInserted, equalTo((long) numberOfDocuments));
        // verify one throttle
        assertThat(metrics.numberOfThrottles, equalTo(1l));
        // verify request charge
        assertThat(metrics.requestUnitsConsumed, equalTo(5.5));
        // verify non zero total time
        assertThat(metrics.timeTaken.getNano() > 0, equalTo(true));

        assertThat(bi.getNumberOfDocumentsImported(), equalTo((int) metrics.numberOfDocumentsInserted));
        assertThat(bi.getTotalRequestUnitsConsumed(), equalTo(metrics.requestUnitsConsumed));
    }

}