// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.bot.ai.qna;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.bot.ai.qna.dialogs.QnAMakerDialog;
import com.microsoft.bot.ai.qna.models.FeedbackRecord;
import com.microsoft.bot.ai.qna.models.FeedbackRecords;
import com.microsoft.bot.ai.qna.models.Metadata;
import com.microsoft.bot.ai.qna.models.QnAMakerTraceInfo;
import com.microsoft.bot.ai.qna.models.QnARequestContext;
import com.microsoft.bot.ai.qna.models.QueryResult;
import com.microsoft.bot.ai.qna.models.QueryResults;
import com.microsoft.bot.ai.qna.utils.QnATelemetryConstants;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.ConversationState;
import com.microsoft.bot.builder.MemoryStorage;
import com.microsoft.bot.builder.MemoryTranscriptStore;
import com.microsoft.bot.builder.PagedResult;
import com.microsoft.bot.builder.Storage;
import com.microsoft.bot.builder.TraceTranscriptLogger;
import com.microsoft.bot.builder.TranscriptLoggerMiddleware;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.builder.TurnContextImpl;
import com.microsoft.bot.builder.UserState;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.adapters.TestFlow;

import com.microsoft.bot.dialogs.ComponentDialog;
import com.microsoft.bot.dialogs.Dialog;
import com.microsoft.bot.dialogs.DialogContext;
import com.microsoft.bot.dialogs.DialogDependencies;
import com.microsoft.bot.dialogs.DialogManager;
import com.microsoft.bot.dialogs.DialogReason;
import com.microsoft.bot.dialogs.DialogTurnResult;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ActivityTypes;
import com.microsoft.bot.schema.ChannelAccount;
import com.microsoft.bot.schema.ConversationAccount;

import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.LoggerFactory;

import okhttp3.OkHttpClient;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class QnAMakerTests {
    private final String knowledgeBaseId = "dummy-id";
    private final String endpointKey = "dummy-key";
    private final String hostname = "http://localhost";
    private final Boolean mockQnAResponse = true;

    @Captor
    ArgumentCaptor<String> eventNameCaptor;

    @Captor
    ArgumentCaptor<Map<String, String>> propertiesCaptor;

    @Captor
    ArgumentCaptor<Map<String, Double>> metricsCaptor;

    private String getRequestUrl() {
        return String.format("/qnamaker/knowledgebases/%s/generateanswer", knowledgeBaseId);
    }

    private String getV2LegacyRequestUrl() {
        return String.format("/qnamaker/v2.0/knowledgebases/%s/generateanswer", knowledgeBaseId);
    }

    private String getV3LegacyRequestUrl() {
        return String.format("/qnamaker/v3.0/knowledgebases/%s/generateanswer", knowledgeBaseId);
    }

    private String getTrainRequestUrl() {
        return String.format("/qnamaker/v3.0/knowledgebases/%s/train", knowledgeBaseId);
    }

    @Test
    public void qnaMakerTraceActivity() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            // Invoke flow which uses mock
            MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore();
            TestAdapter adapter = new TestAdapter(
                TestAdapter.createConversationReference("QnaMaker_TraceActivity", "User1", "Bot"))
                .use(new TranscriptLoggerMiddleware(transcriptStore));
            final String[] conversationId = {null};
            new TestFlow(adapter, turnContext -> {
                // Simulate Qna Lookup
                if(turnContext.getActivity().getText().compareTo("how do I clean the stove?") == 0) {
                    QueryResult[] results = qna.getAnswers(turnContext, null).join();
                    Assert.assertNotNull(results);
                    Assert.assertTrue(results.length == 1);
                    Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack", results[0].getAnswer());
                }
                delay(500);
                conversationId[0] = turnContext.getActivity().getConversation().getId();
                Activity typingActivity = new Activity(ActivityTypes.TYPING);
                typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo());
                turnContext.sendActivity(typingActivity).join();
                delay(500);
                turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join();
                return CompletableFuture.completedFuture(null);
            })
                .send("how do I clean the stove?")
                    .assertReply(activity -> {
                        Assert.assertTrue(activity.isType(ActivityTypes.TYPING));
                    })
                    .assertReply("echo:how do I clean the stove?")
                .delay(500) // This delay avoids the assert immediately above occasionally getting "bar".
                .send("bar")
                    .assertReply(activity -> Assert.assertTrue(activity.isType(ActivityTypes.TYPING)))
                    .assertReply("echo:bar")
                .startTest().join();

            // Validate Trace Activity created
            PagedResult<Activity> pagedResult = transcriptStore.getTranscriptActivities("test", conversationId[0]).join();
            Assert.assertEquals(7, pagedResult.getItems().size());
            Assert.assertEquals("how do I clean the stove?", pagedResult.getItems().get(0).getText());
            Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.TRACE));
            QnAMakerTraceInfo traceInfo = (QnAMakerTraceInfo) pagedResult.getItems().get(1).getValue();
            Assert.assertNotNull(traceInfo);
            Assert.assertEquals("echo:how do I clean the stove?", pagedResult.getItems().get(3).getText());
            Assert.assertEquals("bar", pagedResult.getItems().get(4).getText());
            Assert.assertEquals("echo:bar", pagedResult.getItems().get(6).getText());
            for (Activity activity : pagedResult.getItems()) {
                Assert.assertFalse(StringUtils.isBlank(activity.getId()));
            }
        } catch(Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTraceActivityEmptyText() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            // Get basic Qna
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            // No text
            TestAdapter adapter = new TestAdapter(
                TestAdapter.createConversationReference("QnaMaker_TraceActivity_EmptyText", "User1", "Bot"));
            Activity activity = new Activity(ActivityTypes.MESSAGE);
            activity.setText(new String());
            activity.setConversation(new ConversationAccount());
            activity.setRecipient(new ChannelAccount());
            activity.setFrom(new ChannelAccount());

            TurnContext context = new TurnContextImpl(adapter, activity);
            Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTraceActivityNullText() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            // Get basic Qna
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            // No text
            TestAdapter adapter = new TestAdapter(
                TestAdapter.createConversationReference("QnaMaker_TraceActivity_NullText", "User1", "Bot"));
            Activity activity = new Activity(ActivityTypes.MESSAGE);
            activity.setText(null);
            activity.setConversation(new ConversationAccount());
            activity.setRecipient(new ChannelAccount());
            activity.setFrom(new ChannelAccount());

            TurnContext context = new TurnContextImpl(adapter, activity);
            Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTraceActivityNullContext() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            // Get basic Qna
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(null, null).join());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTraceActivityBadMessage() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            // Get basic Qna
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            // No text
            TestAdapter adapter = new TestAdapter(
                TestAdapter.createConversationReference("QnaMaker_TraceActivity_BadMessage", "User1", "Bot"));
            Activity activity = new Activity(ActivityTypes.TRACE);
            activity.setText("My Text");
            activity.setConversation(new ConversationAccount());
            activity.setRecipient(new ChannelAccount());
            activity.setFrom(new ChannelAccount());

            TurnContext context = new TurnContextImpl(adapter, activity);
            Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTraceActivityNullActivity() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            // Get basic Qna
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);

            // No text
            TestAdapter adapter = new TestAdapter(
                TestAdapter.createConversationReference("QnaMaker_TraceActivity_NullActivity", "User1", "Bot"));
            TurnContext context = new MyTurnContext(adapter, null);
            Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnsAnswer() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);
            QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
            Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack", results[0].getAnswer());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnsAnswerRaw() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);
            QnAMakerOptions options = new QnAMakerOptions();
            options.setTop(1);

            QueryResults results = qna.getAnswersRaw(getContext("how do I clean the stove?"), options, null, null).join();
            Assert.assertNotNull(results.getAnswers());
            Assert.assertTrue(results.getActiveLearningEnabled());
            Assert.assertTrue(results.getAnswers().length == 1);
            Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack",
                results.getAnswers()[0].getAnswer());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerLowScoreVariation() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_TopNAnswer.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint();
            qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnaMakerEndpoint.setEndpointKey(endpointKey);
            qnaMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions qnaMakerOptions = new QnAMakerOptions();
            qnaMakerOptions.setTop(5);
            QnAMaker qna = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions);
            QueryResult[] results = qna.getAnswers(getContext("Q11"), null).join();
            Assert.assertNotNull(results);
            Assert.assertEquals(4, results.length);

            QueryResult[] filteredResults = qna.getLowScoreVariation(results);
            Assert.assertNotNull(filteredResults);
            Assert.assertEquals(3, filteredResults.length);

            String content2 = readFileContent("QnaMaker_TopNAnswer_DisableActiveLearning.json");
            JsonNode response2 = mapper.readTree(content2);
            this.initializeMockServer(mockWebServer, response2, this.getRequestUrl());
            QueryResult[] results2 = qna.getAnswers(getContext("Q11"), null).join();
            Assert.assertNotNull(results2);
            Assert.assertEquals(4, results2.length);

            QueryResult[] filteredResults2 = qna.getLowScoreVariation(results2);
            Assert.assertNotNull(filteredResults2);
            Assert.assertEquals(3, filteredResults2.length);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerCallTrain() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
        String url = this.getTrainRequestUrl();
        String endpoint = "";
        try {
            JsonNode response = objectMapper.readTree("{}");
            endpoint = String.format(
                "%s:%s",
                hostname,
                initializeMockServer(
                    mockWebServer,
                    response,
                    url).port());
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint();
            qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnaMakerEndpoint.setEndpointKey(endpointKey);
            qnaMakerEndpoint.setHost(finalEndpoint);

            QnAMaker qna = new QnAMaker(qnaMakerEndpoint, null);
            FeedbackRecords feedbackRecords = new FeedbackRecords();

            FeedbackRecord feedback1 = new FeedbackRecord();
            feedback1.setQnaId(1);
            feedback1.setUserId("test");
            feedback1.setUserQuestion("How are you?");

            FeedbackRecord feedback2 = new FeedbackRecord();
            feedback2.setQnaId(2);
            feedback2.setUserId("test");
            feedback2.setUserQuestion("What up??");

            feedbackRecords.setRecords(new FeedbackRecord[] { feedback1, feedback2 });
            qna.callTrain(feedbackRecords);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnsAnswerConfiguration() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            QnAMaker qna = this.qnaReturnsAnswer(mockWebServer);
            QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
            Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack",
                results[0].getAnswer());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnsAnswerWithFiltering() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_UsesStrictFilters_ToReturnAnswer.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint();
            qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnaMakerEndpoint.setEndpointKey(endpointKey);
            qnaMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions qnaMakerOptions = new QnAMakerOptions();
            Metadata metadata = new Metadata();
            metadata.setName("topic");
            metadata.setValue("value");
            Metadata[] filters = new Metadata[] { metadata };
            qnaMakerOptions.setStrictFilters(filters);
            qnaMakerOptions.setTop(1);
            QnAMaker qna = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions);
            ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();

            QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), qnaMakerOptions).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
            Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack",
                results[0].getAnswer());
            Assert.assertEquals("topic", results[0].getMetadata()[0].getName());
            Assert.assertEquals("value", results[0].getMetadata()[0].getValue());

            JsonNode obj = null;
            try {
                RecordedRequest request = mockWebServer.takeRequest();
                obj = objectMapper.readTree(request.getBody().readUtf8());
            } catch (IOException | InterruptedException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
            // verify we are actually passing on the options
            Assert.assertEquals(1, obj.get("top").asInt());
            Assert.assertEquals("topic", obj.get("strictFilters").get(0).get("name").asText());
            Assert.assertEquals("value", obj.get("strictFilters").get(0).get("value").asText());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerSetScoreThresholdWhenThresholdIsZero() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_ReturnsAnswer.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint();
            qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnaMakerEndpoint.setEndpointKey(endpointKey);
            qnaMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions qnaMakerOptions = new QnAMakerOptions();
            qnaMakerOptions.setScoreThreshold(0.0f);

            QnAMaker qnaWithZeroValueThreshold = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions);

            QnAMakerOptions options = new QnAMakerOptions();
            options.setTop(1);

            QueryResult[] results = qnaWithZeroValueThreshold.getAnswers(getContext("how do I clean the stove?"), options).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTestThreshold() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_TestThreshold.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
            qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnAMakerEndpoint.setEndpointKey(endpointKey);
            qnAMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions qnaMakerOptions = new QnAMakerOptions();
            qnaMakerOptions.setTop(1);
            qnaMakerOptions.setScoreThreshold(0.99F);

            QnAMaker qna = new QnAMaker(qnAMakerEndpoint, qnaMakerOptions);

            QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 0);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTestScoreThresholdTooLargeOutOfRange() {
        PrintMethodName();

        QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
        qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
        qnAMakerEndpoint.setEndpointKey(endpointKey);
        qnAMakerEndpoint.setHost(hostname);

        QnAMakerOptions tooLargeThreshold = new QnAMakerOptions();
        tooLargeThreshold.setTop(1);
        tooLargeThreshold.setScoreThreshold(1.1f);

        Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, tooLargeThreshold));
    }

    @Test
    public void qnaMakerTestScoreThresholdTooSmallOutOfRange() {
        PrintMethodName();

        QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
        qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
        qnAMakerEndpoint.setEndpointKey(endpointKey);
        qnAMakerEndpoint.setHost(hostname);

        QnAMakerOptions tooSmallThreshold = new QnAMakerOptions();
        tooSmallThreshold.setTop(1);
        tooSmallThreshold.setScoreThreshold(-9000.0f);

        Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, tooSmallThreshold));
    }

    @Test
    public void qnaMakerReturnsAnswerWithContext() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_ReturnsAnswerWithContext.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
            qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnAMakerEndpoint.setEndpointKey(endpointKey);
            qnAMakerEndpoint.setHost(finalEndpoint);

            QnARequestContext context = new QnARequestContext();
            context.setPreviousQnAId(5);
            context.setPreviousUserQuery("how do I clean the stove?");

            QnAMakerOptions options = new QnAMakerOptions();
            options.setTop(1);
            options.setContext(context);

            QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options);

            QueryResult[] results = qna.getAnswers(getContext("Where can I buy?"), options).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
            Assert.assertEquals(55, (int)results[0].getId());
            Assert.assertEquals(1, (double)results[0].getScore(), 0);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnAnswersWithoutContext() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_ReturnsAnswerWithoutContext.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
            qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnAMakerEndpoint.setEndpointKey(endpointKey);
            qnAMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions options = new QnAMakerOptions();
            options.setTop(3);

            QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options);

            QueryResult[] results = qna.getAnswers(getContext("Where can I buy?"), options).join();
            Assert.assertNotNull(results);
            Assert.assertEquals(2, results.length);
            Assert.assertNotEquals(1, results[0].getScore().intValue());
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerReturnsHighScoreWhenIdPassed() {
        PrintMethodName();

        MockWebServer mockWebServer = new MockWebServer();
        try {
            String content = readFileContent("QnaMaker_ReturnsAnswerWithContext.json");
            ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
            JsonNode response = mapper.readTree(content);
            String url = this.getRequestUrl();
            String endpoint = "";
            if (this.mockQnAResponse) {
                endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port());
            }
            String finalEndpoint = endpoint;
            QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
            qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId);
            qnAMakerEndpoint.setEndpointKey(endpointKey);
            qnAMakerEndpoint.setHost(finalEndpoint);

            QnAMakerOptions options = new QnAMakerOptions();
            options.setTop(1);
            options.setQnAId(55);

            QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options);
            QueryResult[] results = qna.getAnswers(getContext("Where can I buy?"), options).join();
            Assert.assertNotNull(results);
            Assert.assertTrue(results.length == 1);
            Assert.assertEquals(55, (int)results[0].getId());
            Assert.assertEquals(1, (double)results[0].getScore(), 0);
        } catch (Exception e) {
            fail();
        } finally {
            try {
                mockWebServer.shutdown();
            } catch (IOException e) {
                LoggerFactory.getLogger(QnAMakerTests.class).error(e.getMessage());
            }
        }
    }

    @Test
    public void qnaMakerTestTopOutOfRange() {
        PrintMethodName();

        QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint();
