/*
 * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

/// @file SettingsTest.cpp

#include <memory>
#include <sstream>
#include <iterator>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/stringbuffer.h>
#include <AVSCommon/AVS/AbstractAVSConnectionManager.h>
#include <AVSCommon/AVS/Initialization/AlexaClientSDKInit.h>
#include <AVSCommon/SDKInterfaces/GlobalSettingsObserverInterface.h>
#include <AVSCommon/SDKInterfaces/MockMessageSender.h>
#include <AVSCommon/SDKInterfaces/SingleSettingObserverInterface.h>
#include <AVSCommon/Utils/JSON/JSONUtils.h>
#include <CertifiedSender/CertifiedSender.h>
#include <CertifiedSender/SQLiteMessageStorage.h>

#include "Settings/Settings.h"
#include "Settings/SettingsUpdatedEventSender.h"
#include "Settings/SQLiteSettingStorage.h"

namespace alexaClientSDK {
namespace capabilityAgents {
namespace settings {
namespace test {

using namespace avsCommon::avs::initialization;
using namespace avsCommon::sdkInterfaces;
using namespace avsCommon::sdkInterfaces::test;
using namespace avsCommon::utils::configuration;
using namespace avsCommon::utils::json::jsonUtils;
using namespace ::testing;
using namespace certifiedSender;

/// JSON key for the event section of a message.
static const std::string MESSAGE_EVENT_KEY = "event";

/// JSON key for the header section of a message.
static const std::string MESSAGE_HEADER_KEY = "header";

/// JSON key for the namespace field of a message header.
static const std::string MESSAGE_NAMESPACE_KEY = "namespace";

/// JSON key for the name field of a message header.
static const std::string MESSAGE_NAME_KEY = "name";

/// JSON key for the message ID field of a message header.
static const std::string MESSAGE_ID_KEY = "messageId";

/// JSON key for the payload section of an message.
static const std::string MESSAGE_PAYLOAD_KEY = "payload";

/// JSON key for the settings array filed of the message.
static const std::string MESSAGE_SETTINGS_KEY = "settings";

/// The namespace for this event.
static const std::string SETTINGS_NAMESPACE = "Settings";

/// JSON value for a SettingsUpdated event's name.
static const std::string SETTINGS_UPDATED_EVENT_NAME = "SettingsUpdated";

/// JSON value for the settings array's key.
static const std::string SETTINGS_KEY = "key";

/// JSON value for the settings array's value.
static const std::string SETTINGS_VALUE = "value";

/// JSON text for settings config values for initialization of settings Object.
// clang-format off
static const std::string SETTINGS_CONFIG_JSON =
"{"
    "\"certifiedSender\":{"
        "\"databaseFilePath\":\"database.db\""
    "},"
      "\"settings\":{"
        "\"databaseFilePath\":\"settingsUnitTest.db\","
          "\"defaultAVSClientSettings\":{"
          "\"locale\":\"en-GB\""
          "}"
        "}"
    "}";
// clang-format on

/**
 * Utility function to determine if the storage component is opened.
 *
 * @param storage The storage component to check.
 * @return True if the storage component's underlying database is opened, false otherwise.
 */
static bool isOpen(const std::shared_ptr<SettingsStorageInterface>& storage) {
    std::unordered_map<std::string, std::string> dummyMapOfSettings;
    return storage->load(&dummyMapOfSettings);
}

/**
 * This class allows us to test SingleSettingObserver interaction.
 */
class MockSingleSettingObserver : public SingleSettingObserverInterface {
public:
    MockSingleSettingObserver() {
    }
    MOCK_METHOD2(onSettingChanged, void(const std::string& key, const std::string& value));
};

/**
 * This class allows us to test GlobalSettingsObserver interaction.
 */
class MockGlobalSettingsObserver : public GlobalSettingsObserverInterface {
public:
    MockGlobalSettingsObserver() {
    }
    MOCK_METHOD1(onSettingChanged, void(const std::unordered_map<std::string, std::string>& mapOfSettings));
};

/**
 * Class with which to mock a connection ot AVS.
 */
class MockConnection : public avsCommon::avs::AbstractAVSConnectionManager {
public:
    MockConnection() = default;

    MOCK_METHOD0(enable, void());
    MOCK_METHOD0(disable, void());
    MOCK_METHOD0(isEnabled, bool());
    MOCK_METHOD0(reconnect, void());
    MOCK_METHOD1(
        addMessageObserver,
        void(std::shared_ptr<avsCommon::sdkInterfaces::MessageObserverInterface> observer));
    MOCK_METHOD1(
        removeMessageObserver,
        void(std::shared_ptr<avsCommon::sdkInterfaces::MessageObserverInterface> observer));

    bool isConnected() const;

    /**
     * Update the connection status.
     *
     * @param status The Connection Status.
     * @param reason The reason the Connection Status changed.
     */
    void updateConnectionStatus(
        ConnectionStatusObserverInterface::Status status,
        ConnectionStatusObserverInterface::ChangedReason reason);
};

bool MockConnection::isConnected() const {
    return ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus;
}

void MockConnection::updateConnectionStatus(
    ConnectionStatusObserverInterface::Status status,
    ConnectionStatusObserverInterface::ChangedReason reason) {
    AbstractAVSConnectionManager::updateConnectionStatus(status, reason);
}

class MockMessageStorage : public MessageStorageInterface {
public:
    MOCK_METHOD0(createDatabase, bool());
    MOCK_METHOD0(open, bool());
    MOCK_METHOD0(close, void());
    MOCK_METHOD2(store, bool(const std::string& message, int* id));
    MOCK_METHOD1(load, bool(std::queue<StoredMessage>* messageContainer));
    MOCK_METHOD1(erase, bool(int messageId));
    MOCK_METHOD0(clearDatabase, bool());
    virtual ~MockMessageStorage() = default;
};
/**
 * Utility class to take the parameters of SettingsUpdated event and provide functionalities
 * to verify the event being sent.
 */
class SettingsVerifyTest {
public:
    /**
     * Constructs an object which captures <key, value> pair of settings in the map.
     */
    SettingsVerifyTest(std::unordered_map<std::string, std::string> mapOfSettings);

    /**
     * This function verifies that the JSON content of SettingsUpdated event @c MessageRequest is correct.
     * This function signature matches that of @c MessageSenderInterface::sendMessage() so that an
     * @c ON_CALL() can @c Invoke() this function directly.
     *
     * @param request The @c MessageRequest to verify.
     */
    void verifyMessage(std::shared_ptr<avsCommon::avs::MessageRequest> request);

private:
    /// The map of settings which holds the <key, value> pair of settings sent to AVS.
    std::unordered_map<std::string, std::string> m_mapOfSettings;
};

SettingsVerifyTest::SettingsVerifyTest(std::unordered_map<std::string, std::string> mapOfSettings) :
        m_mapOfSettings{mapOfSettings} {
}

void SettingsVerifyTest::verifyMessage(std::shared_ptr<avsCommon::avs::MessageRequest> request) {
    rapidjson::Document document;
    document.Parse(request->getJsonContent().c_str());
    EXPECT_FALSE(document.HasParseError())
        << "rapidjson detected a parsing error at offset:" << std::to_string(document.GetErrorOffset())
        << ", error message: " << GetParseError_En(document.GetParseError());

    auto event = document.FindMember(MESSAGE_EVENT_KEY);
    ASSERT_NE(event, document.MemberEnd());
    auto header = event->value.FindMember(MESSAGE_HEADER_KEY);
    ASSERT_NE(header, event->value.MemberEnd());
    auto payload = event->value.FindMember(MESSAGE_PAYLOAD_KEY);
    ASSERT_NE(payload, event->value.MemberEnd());
    auto settings = payload->value.FindMember(MESSAGE_SETTINGS_KEY);
    ASSERT_NE(settings, payload->value.MemberEnd());

    std::string temp_string = "";
    ASSERT_TRUE(retrieveValue(header->value, MESSAGE_NAMESPACE_KEY, &temp_string));
    EXPECT_EQ(temp_string, SETTINGS_NAMESPACE);
    ASSERT_TRUE(retrieveValue(header->value, MESSAGE_NAME_KEY, &temp_string));
    EXPECT_EQ(temp_string, SETTINGS_UPDATED_EVENT_NAME);
    ASSERT_TRUE(retrieveValue(header->value, MESSAGE_ID_KEY, &temp_string));
    ASSERT_NE(temp_string, "");
    ASSERT_TRUE(jsonArrayExists(payload->value, MESSAGE_SETTINGS_KEY));
    rapidjson::Value& array = settings->value;

    ASSERT_TRUE(array.Size() == m_mapOfSettings.size());
    rapidjson::SizeType i;
    std::unordered_map<std::string, std::string>::const_iterator j;
    for (i = array.Size() - 1, j = m_mapOfSettings.begin(); j != m_mapOfSettings.end(); i--, j++) {
        auto key = array[i].FindMember(SETTINGS_KEY);
        ASSERT_NE(key, array[i].MemberEnd());
        auto value = array[i].FindMember(SETTINGS_VALUE);
        ASSERT_NE(value, array[i].MemberEnd());
        ASSERT_TRUE(retrieveValue(array[i], SETTINGS_KEY, &temp_string));
        EXPECT_EQ(temp_string, j->first);
        ASSERT_TRUE(retrieveValue(array[i], SETTINGS_VALUE, &temp_string));
        EXPECT_EQ(temp_string, j->second);
    }
}

/// Test harness for @c Settings class.
class SettingsTest : public ::testing::Test {
public:
    void SetUp() override;
    void TearDown() override;

protected:
    /**
     * Function to send a SettingsUpdated event and verify that it succeeds. Parameters are passed through
     * @c SettingsVerifyTest::SettingsVerifyTest().
     *
     * @param key The name of the setting to change.
     * @param value The value of the setting to which it has to be set.
     * @return @c true if the SettingsUpdated event was sent correctly, else @c false.
     */
    bool testChangeSettingSucceeds(const std::string& key, const std::string& value);
    /// The mock @c MessageSenderInterface.
    std::shared_ptr<MockMessageSender> m_mockMessageSender;
    /// Global Observer for all the settings which sends the event.
    std::shared_ptr<SettingsUpdatedEventSender> m_settingsEventSender;
    /// Settings Storage object.
    std::shared_ptr<SQLiteSettingStorage> m_storage;
    /// The @c Settings Object to test.
    std::shared_ptr<Settings> m_settingsObject;
    /// The object which verifies the json of message sent.
    std::shared_ptr<SettingsVerifyTest> m_settingsVerifyObject;
    /// The map which stores all the settings for the object.
    std::unordered_map<std::string, std::string> m_mapOfSettings;
    /// The data manager required to build the base object
    std::shared_ptr<registrationManager::CustomerDataManager> m_dataManager;
    /// Class under test.
    std::shared_ptr<CertifiedSender> m_certifiedSender;
    /// Mock message storage layer.
    std::shared_ptr<MockMessageStorage> m_msgStorage;
    /// Mocked connection
    std::shared_ptr<StrictMock<MockConnection>> m_mockConnection;
};

void SettingsTest::SetUp() {
    m_dataManager = std::make_shared<registrationManager::CustomerDataManager>();
    auto inString = std::shared_ptr<std::istringstream>(new std::istringstream(SETTINGS_CONFIG_JSON));
    ASSERT_TRUE(AlexaClientSDKInit::initialize({inString}));
    m_mockMessageSender = std::make_shared<MockMessageSender>();
    m_mockConnection = std::make_shared<StrictMock<MockConnection>>();
    m_msgStorage = std::make_shared<MockMessageStorage>();

    EXPECT_CALL(*m_msgStorage, open()).WillOnce(Return(true));
    m_certifiedSender = CertifiedSender::create(m_mockMessageSender, m_mockConnection, m_msgStorage, m_dataManager);

    m_mockConnection->updateConnectionStatus(
        ConnectionStatusObserverInterface::Status::CONNECTED,
        ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST);

    m_settingsEventSender = SettingsUpdatedEventSender::create(m_certifiedSender);
    ASSERT_NE(m_settingsEventSender, nullptr);
    m_storage = std::make_shared<SQLiteSettingStorage>("settingsUnitTest.db");
    m_settingsObject = Settings::create(m_storage, {m_settingsEventSender}, m_dataManager);
    ASSERT_NE(m_settingsObject, nullptr);
    ASSERT_TRUE(m_storage->load(&m_mapOfSettings));
}

void SettingsTest::TearDown() {
    m_mapOfSettings.clear();
    m_storage->clearDatabase();
    AlexaClientSDKInit::uninitialize();
    m_mockConnection->removeConnectionStatusObserver(m_certifiedSender);
    m_certifiedSender->shutdown();
}

bool SettingsTest::testChangeSettingSucceeds(const std::string& key, const std::string& value) {
    std::mutex mutex;
    std::condition_variable conditionVariable;
    bool done = false;
    EXPECT_CALL(*m_msgStorage, store(_, _)).WillRepeatedly(Return(true));
    EXPECT_CALL(*m_mockMessageSender, sendMessage(_))
        .WillRepeatedly(DoAll(
            Invoke([this, key, value](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
                m_mapOfSettings[key] = value;
                m_settingsVerifyObject = std::make_shared<SettingsVerifyTest>(m_mapOfSettings);
                m_settingsVerifyObject->verifyMessage(request);
            }),
            InvokeWithoutArgs([&] {
                std::lock_guard<std::mutex> lock(mutex);
                done = true;
                conditionVariable.notify_one();
            })));
    m_settingsObject->changeSetting(key, value);
    std::unique_lock<std::mutex> lock(mutex);
    return conditionVariable.wait_for(lock, std::chrono::seconds(1), [&done] { return done; });
}

/**
 * Test to verify the @c create function of @c Settings class.
 */
TEST_F(SettingsTest, createTest) {
    ASSERT_EQ(
        nullptr,
        m_settingsObject->create(
            m_storage, std::unordered_set<std::shared_ptr<GlobalSettingsObserverInterface>>(), m_dataManager));
    ASSERT_EQ(nullptr, m_settingsObject->create(nullptr, {m_settingsEventSender}, m_dataManager));
    ASSERT_EQ(nullptr, m_settingsObject->create(m_storage, {nullptr}, m_dataManager));
}

/**
 * Test to verify if by adding a global observer and changing the setting,
 * the global observer is notified of the change. It also verifies that event is being sent in correct JSON format.
 */
TEST_F(SettingsTest, addGlobalSettingsObserverTest) {
    std::shared_ptr<MockGlobalSettingsObserver> mockGlobalSettingObserver;
    mockGlobalSettingObserver = std::make_shared<MockGlobalSettingsObserver>();
    m_settingsObject->addGlobalSettingsObserver(mockGlobalSettingObserver);
    EXPECT_CALL(*mockGlobalSettingObserver, onSettingChanged(_)).Times(1);
    ASSERT_TRUE(testChangeSettingSucceeds("locale", "en-US"));
}

/**
 * Test to verify if by removing a global observer and changing the setting,
 * the global observer is not notified of the change. It also verifies that event is being sent in correct JSON format.
 */
TEST_F(SettingsTest, removeGlobalSettingsObserverTest) {
    std::shared_ptr<MockGlobalSettingsObserver> mockGlobalSettingObserver;
    mockGlobalSettingObserver = std::make_shared<MockGlobalSettingsObserver>();
    m_settingsObject->removeGlobalSettingsObserver(mockGlobalSettingObserver);
    EXPECT_CALL(*mockGlobalSettingObserver, onSettingChanged(_)).Times(0);
    ASSERT_TRUE(testChangeSettingSucceeds("locale", "en-US"));
}

/**
 * Test to verify @c addSingleSettingObserver function. This test verifies if the setting key is invalid
 * i.e. it doesn't exist in the list of @c SETTINGS_ACCEPTED_KEYS, the observer corresponding to it
 * will not be added.
 */
TEST_F(SettingsTest, addSingleSettingObserverWithInvalidKeyTest) {
    std::shared_ptr<MockSingleSettingObserver> wakewordObserver;
    wakewordObserver = std::make_shared<MockSingleSettingObserver>();
    m_settingsObject->addSingleSettingObserver("wakeword", wakewordObserver);
    EXPECT_CALL(*wakewordObserver, onSettingChanged(_, _)).Times(0);
    ASSERT_FALSE(testChangeSettingSucceeds("wakeword", "Alexa"));
    std::shared_ptr<MockSingleSettingObserver> localeObserver;
    localeObserver = std::make_shared<MockSingleSettingObserver>();
    m_settingsObject->addSingleSettingObserver("local", localeObserver);
    EXPECT_CALL(*localeObserver, onSettingChanged(_, _)).Times(0);
    ASSERT_FALSE(testChangeSettingSucceeds("local", "en-US"));
}

/**
 * Test to verify the function @c removeSingleSettingObserver. The test checks that if an observer is added for
 * an invalid key even if it is a typo, the observer will not be removed.
 * It also verifies that event is being sent in correct JSON format.
 */
TEST_F(SettingsTest, removeSingleSettingObserverWithInvalidKeyTest) {
    std::shared_ptr<MockSingleSettingObserver> localeObserver;
    localeObserver = std::make_shared<MockSingleSettingObserver>();
    m_settingsObject->addSingleSettingObserver("locale", localeObserver);
    m_settingsObject->removeSingleSettingObserver("local", localeObserver);
    EXPECT_CALL(*localeObserver, onSettingChanged(_, _)).Times(1);
    ASSERT_TRUE(testChangeSettingSucceeds("locale", "en-US"));
}

/**
 * Test verifies that if an observer is removed with a valid key, the observer gets removed
 * and is not notified when the setting changes. It also verifies that event is being sent in correct JSON format.
 */
TEST_F(SettingsTest, removeSingleSettingObserverWithCorrectKeyTest) {
    std::shared_ptr<MockSingleSettingObserver> localeObserver;
    localeObserver = std::make_shared<MockSingleSettingObserver>();
    m_settingsObject->addSingleSettingObserver("locale", localeObserver);
    m_settingsObject->removeSingleSettingObserver("locale", localeObserver);
    EXPECT_CALL(*localeObserver, onSettingChanged(_, _)).Times(0);
    ASSERT_TRUE(testChangeSettingSucceeds("locale", "en-US"));
}

/**
 * Test to check if the settings loaded from the database are same as the default settings.
 */
TEST_F(SettingsTest, defaultSettingsCorrect) {
    std::string DEFAULT_SETTINGS = "defaultAVSClientSettings";
    std::string settings_json = "";
    retrieveValue(SETTINGS_CONFIG_JSON, MESSAGE_SETTINGS_KEY, &settings_json);
    std::string default_settings = "";
    retrieveValue(settings_json, DEFAULT_SETTINGS, &default_settings);
    rapidjson::Document document;
    ASSERT_TRUE(parseJSON(default_settings, &document));
    std::string value = "";
    for (auto& it : m_mapOfSettings) {
        auto key = document.FindMember(it.first);
        ASSERT_NE(key, document.MemberEnd());
        ASSERT_TRUE(retrieveValue(document, it.first, &value));
        EXPECT_EQ(it.second, value);
    }
}

/**
 * Test to check that @c clearData() removes any setting stored in the database.
 */
TEST_F(SettingsTest, clearDataTest) {
    ASSERT_TRUE(testChangeSettingSucceeds("locale", "en-CA"));
    m_settingsObject->clearData();

    std::unordered_map<std::string, std::string> tempMap;
    ASSERT_TRUE(m_storage->load(&tempMap));
    ASSERT_TRUE(tempMap.empty());
}

/**
 * Test to check clear database works as expected.
 */
TEST_F(SettingsTest, clearDatabaseTest) {
    std::unordered_map<std::string, std::string> tempMap;
    ASSERT_TRUE(m_storage->clearDatabase());
    ASSERT_TRUE(m_storage->load(&tempMap));
    ASSERT_TRUE(tempMap.empty());
}

/**
 * Test to check the store function of SQLiteSettingStorage class.
 */
TEST_F(SettingsTest, storeDatabaseTest) {
    ASSERT_TRUE(m_storage->clearDatabase());
    std::map<std::string, std::string> MapToStore = {{"wakeword", "Alexa"}, {"locale", "en-US"}};
    for (auto& it : MapToStore) {
        ASSERT_TRUE(m_storage->store(it.first, it.second));
    }
    std::unordered_map<std::string, std::string> LoadMap;
    ASSERT_TRUE(m_storage->load(&LoadMap));
    ASSERT_EQ(LoadMap.size(), MapToStore.size());
    ASSERT_TRUE(std::equal(MapToStore.rbegin(), MapToStore.rend(), LoadMap.begin()));
}

/**
 * Test to check the modify function of SQLiteSettingStorage class.
 */
TEST_F(SettingsTest, modifyDatabaseTest) {
    ASSERT_TRUE(m_storage->modify("locale", "en-US"));
    ASSERT_FALSE(m_storage->modify("local", "en-GB"));
    ASSERT_TRUE(m_storage->clearDatabase());
    ASSERT_FALSE(m_storage->settingExists("locale"));
}
/**
 * Test to check the erase function of SQLiteSettingStorage class.
 */
TEST_F(SettingsTest, eraseTest) {
    ASSERT_TRUE(m_storage->erase("locale"));
    ASSERT_FALSE(m_storage->settingExists("locale"));
    ASSERT_FALSE(m_storage->erase("local"));
}

/**
 * Test to check the createDatabase function of SQLiteSettingStorage class.
 */
TEST_F(SettingsTest, createDatabaseTest) {
    m_storage->close();
    ASSERT_FALSE(m_storage->createDatabase());
}

/**
 * Test to check the open and close functions of SQLiteSettingStorage class.
 */
TEST_F(SettingsTest, openAndCloseDatabaseTest) {
    ASSERT_FALSE(m_storage->open());
    ASSERT_TRUE(isOpen(m_storage));
    m_storage->close();
    ASSERT_TRUE(m_storage->open());
    ASSERT_TRUE(isOpen(m_storage));
}
}  // namespace test
}  // namespace settings
}  // namespace capabilityAgents
}  // namespace alexaClientSDK

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    if (argc < 1) {
        std::cerr << "USAGE: " << std::string(argv[0]) << std::endl;
        return 1;
    }
    return RUN_ALL_TESTS();
}
