/*
 * Copyright 2017-2019 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.
 */

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-actions.h>

#include <AVSCommon/Utils/Timing/TimeUtils.h>

#include "Alerts/AlertScheduler.h"

namespace alexaClientSDK {
namespace capabilityAgents {
namespace alerts {
namespace test {

using namespace testing;

/// Tokens for alerts.
static const std::string ALERT1_TOKEN = "token1";
static const std::string ALERT2_TOKEN = "token2";
static const std::string ALERT3_TOKEN = "token3";
static const std::string ALERT4_TOKEN = "token4";

/// Test alert type
static const std::string ALERT_TYPE = "TEST_ALERT_TYPE";

/// A schedule instant in the past for alerts.
static const std::string PAST_INSTANT = "2000-01-01T12:34:56+0000";

/// A schedule instant in the future for alerts.
static const std::string FUTURE_INSTANT_SUFFIX = "-01-01T12:34:56+0000";

/// Amount of time that the alert observer should wait for a task to finish.
static const std::chrono::milliseconds TEST_TIMEOUT{100};

/// Alert past due time limit.
static const std::chrono::seconds ALERT_PAST_DUE_TIME_LIMIT{10};

class MockRenderer : public renderer::RendererInterface {
public:
    MOCK_METHOD5(
        start,
        void(
            std::shared_ptr<capabilityAgents::alerts::renderer::RendererObserverInterface> observer,
            std::function<std::unique_ptr<std::istream>()> audioFactory,
            const std::vector<std::string>& urls,
            int loopCount,
            std::chrono::milliseconds loopPause));
    MOCK_METHOD0(stop, void());
};

class TestAlert : public Alert {
public:
    TestAlert() :
            Alert(defaultAudioFactory, shortAudioFactory),
            m_alertType{ALERT_TYPE},
            m_renderer{std::make_shared<MockRenderer>()} {
        this->setRenderer(m_renderer);
    }

    TestAlert(const std::string& token, const std::string& schedTime) :
            Alert(defaultAudioFactory, shortAudioFactory),
            m_alertType{ALERT_TYPE},
            m_renderer{std::make_shared<MockRenderer>()} {
        this->setRenderer(m_renderer);

        // clang-format off
        const std::string payloadJson =
            "{"
                "\"token\": \"" + token + "\","
                "\"type\": \"" + m_alertType + "\","
                "\"scheduledTime\": \"" + schedTime + "\""
            "}";
        // clang-format on

        std::string errorMessage;
        rapidjson::Document payload;
        payload.Parse(payloadJson);

        this->parseFromJson(payload, &errorMessage);
    }

    std::string getTypeName() const override {
        return m_alertType;
    }

private:
    static std::unique_ptr<std::istream> defaultAudioFactory() {
        return std::unique_ptr<std::stringstream>(new std::stringstream("default audio"));
    }

    static std::unique_ptr<std::istream> shortAudioFactory() {
        return std::unique_ptr<std::stringstream>(new std::stringstream("short audio"));
    }

    const std::string m_alertType;
    std::shared_ptr<MockRenderer> m_renderer;
};

class MockAlertStorage : public storage::AlertStorageInterface {
public:
    MockAlertStorage() {
        m_createDatabaseRetVal = true;
        m_openRetVal = true;
        m_isOpenRetVal = true;
        m_alertExistsRetVal = true;
        m_storeRetVal = true;
        m_loadRetVal = true;
        m_eraseRetVal = true;
    }

    bool createDatabase() {
        return m_createDatabaseRetVal;
    }
    bool open() {
        return m_openRetVal;
    }
    bool isOpen() {
        return m_isOpenRetVal;
    }
    void close() {
    }
    bool alertExists(const std::string& token) {
        return m_alertExistsRetVal;
    }
    bool store(std::shared_ptr<Alert> alert) {
        return m_storeRetVal;
    }
    bool load(std::vector<std::shared_ptr<Alert>>* alertContainer) {
        if (m_loadRetVal) {
            alertContainer->clear();
            for (std::shared_ptr<Alert> alertToAdd : m_alertsInStorage) {
                alertContainer->push_back(alertToAdd);
            }
        }
        return m_loadRetVal;
    }
    bool erase(const std::vector<int>& alertDbIds) {
        return m_eraseRetVal;
    }
    void setCreateDatabaseRetVal(bool retVal) {
        m_createDatabaseRetVal = retVal;
    }
    void setOpenRetVal(bool retVal) {
        m_openRetVal = retVal;
    }
    void setIsOpenRetVal(bool retVal) {
        m_isOpenRetVal = retVal;
    }
    void setAlertExistsRetVal(bool retVal) {
        m_alertExistsRetVal = retVal;
    }
    void setStoreRetVal(bool retVal) {
        m_storeRetVal = retVal;
    }
    void setLoadRetVal(bool retVal) {
        m_loadRetVal = retVal;
    }
    void setEraseRetVal(bool retVal) {
        m_eraseRetVal = retVal;
    }
    void setAlerts(std::vector<std::shared_ptr<TestAlert>> alertsToAdd) {
        m_alertsInStorage.clear();
        for (std::shared_ptr<TestAlert> alertToAdd : alertsToAdd) {
            m_alertsInStorage.push_back(alertToAdd);
        }
    }

    MOCK_METHOD1(bulkErase, bool(const std::list<std::shared_ptr<Alert>>&));
    MOCK_METHOD1(erase, bool(std::shared_ptr<Alert>));
    MOCK_METHOD1(modify, bool(std::shared_ptr<Alert>));
    MOCK_METHOD0(clearDatabase, bool());

private:
    std::vector<std::shared_ptr<Alert>> m_alertsInStorage;
    bool m_createDatabaseRetVal;
    bool m_openRetVal;
    bool m_isOpenRetVal;
    bool m_alertExistsRetVal;
    bool m_storeRetVal;
    bool m_loadRetVal;
    bool m_eraseRetVal;
};

class TestAlertObserver : public AlertObserverInterface {
public:
    bool waitFor(AlertScheduler::State newState) {
        std::unique_lock<std::mutex> lock(m_mutex);
        return m_conditionVariable.wait_for(lock, TEST_TIMEOUT, [this, newState] { return m_state == newState; });
    }

    void onAlertStateChange(
        const std::string& alertToken,
        const std::string& alertType,
        AlertScheduler::State newState,
        const std::string& reason) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_state = newState;
        m_conditionVariable.notify_all();
    }

private:
    std::mutex m_mutex;
    std::condition_variable m_conditionVariable;
    AlertScheduler::State m_state;
};

class AlertSchedulerTest : public ::testing::Test {
public:
    AlertSchedulerTest();
    void SetUp() override;
    std::shared_ptr<TestAlert> doSimpleTestSetup(bool activateAlert = false, bool initWithAlertObserver = false);

protected:
    std::shared_ptr<MockAlertStorage> m_alertStorage;
    std::shared_ptr<MockRenderer> m_alertRenderer;
    std::chrono::seconds m_alertPastDueTimeLimit;
    std::shared_ptr<AlertScheduler> m_alertScheduler;
    std::shared_ptr<TestAlertObserver> m_testAlertObserver;
};

static std::string getFutureInstant(int yearsPlus) {
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    time_t tt = std::chrono::system_clock::to_time_t(now);
    tm utc_tm = *gmtime(&tt);
    return std::to_string(utc_tm.tm_year + 1900 + yearsPlus) + FUTURE_INSTANT_SUFFIX;
}

static std::string getTimeNow() {
    std::string timeNowStr;
    auto timeNow = std::chrono::system_clock::now();
    avsCommon::utils::timing::TimeUtils().convertTimeToUtcIso8601Rfc3339(timeNow, &timeNowStr);
    return timeNowStr;
}

AlertSchedulerTest::AlertSchedulerTest() :
        m_alertStorage{std::make_shared<MockAlertStorage>()},
        m_alertRenderer{std::make_shared<MockRenderer>()},
        m_alertPastDueTimeLimit{ALERT_PAST_DUE_TIME_LIMIT},
        m_alertScheduler{std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)},
        m_testAlertObserver{std::make_shared<TestAlertObserver>()} {
}

void AlertSchedulerTest::SetUp() {
    m_alertStorage->setOpenRetVal(true);
}

/**
 * Helper method that does a basic setup for tests.
 * The method performs the following functions:
 * 1 - Creates a test alert and adds it to be scheduled
 * 2 - If initializing with alert scheduler observer, then creates alert scheduler observer
 * 3 - Initializes the alert scheduler
 * 4 - Based on the argument, may initialize the alert
 *
 * The method will return the scheduled alert
 */
std::shared_ptr<TestAlert> AlertSchedulerTest::doSimpleTestSetup(bool activateAlert, bool initWithAlertObserver) {
    std::vector<std::shared_ptr<TestAlert>> alertToAdd;
    std::shared_ptr<TestAlert> alert = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    alertToAdd.push_back(alert);
    m_alertStorage->setAlerts(alertToAdd);

    if (initWithAlertObserver) {
        m_alertScheduler->initialize(m_testAlertObserver);
    } else {
        std::shared_ptr<AlertScheduler> alertSchedulerObs{
            std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
        m_alertScheduler->initialize(alertSchedulerObs);
    }

    if (activateAlert) {
        alert->activate();
        m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND);
    }

    return alert;
}

/**
 * Test initializing AlertScheduler
 */
TEST_F(AlertSchedulerTest, initialize) {
    /// check if init fails if scheduler is not available
    ASSERT_FALSE(m_alertScheduler->initialize(nullptr));

    /// check if init fails if a database for alerts cant be created
    m_alertStorage->setOpenRetVal(false);
    m_alertStorage->setCreateDatabaseRetVal(false);
    ASSERT_FALSE(m_alertScheduler->initialize(m_alertScheduler));

    /// check if init succeeds. Pass in 3 alerts of which 1 is expired. Only 2 should actually remain in the end.
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    m_alertStorage->setOpenRetVal(true);
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;

    /// past alert
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, PAST_INSTANT);
    alertsToAdd.push_back(alert1);

    /// future active alert
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(1));
    alert2->activate();
    alert2->setStateActive();
    alertsToAdd.push_back(alert2);

    /// future inactive alert
    std::shared_ptr<TestAlert> alert3 = std::make_shared<TestAlert>(ALERT3_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert3);
    m_alertStorage->setAlerts(alertsToAdd);

    /// past alert should get removed
    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);

    /// active alert should get modified
    EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1);

    ASSERT_TRUE(m_alertScheduler->initialize(alertSchedulerObs));

    const unsigned int expectedRemainingAlerts = 2;

    /// only the 2 future alerts remain
    ASSERT_EQ(m_alertScheduler->getContextInfo().scheduledAlerts.size(), expectedRemainingAlerts);
}

/**
 * Test AlertScheduler getting focus
 */
TEST_F(AlertSchedulerTest, updateGetFocus) {
    std::shared_ptr<TestAlert> alert = doSimpleTestSetup();

    // check if focus changes to foreground
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::FOREGROUND);
    ASSERT_EQ(m_alertScheduler->getFocusState(), avsCommon::avs::FocusState::FOREGROUND);

    // check if focus changes to background
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND);
    ASSERT_EQ(m_alertScheduler->getFocusState(), avsCommon::avs::FocusState::BACKGROUND);

    // check alert state change if focus is gone
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::NONE);
    ASSERT_EQ(alert->getState(), Alert::State::STOPPING);
}

/**
 * Test scheduling alerts
 */
TEST_F(AlertSchedulerTest, scheduleAlert) {
    // check that a future alert is scheduled
    std::shared_ptr<TestAlert> alert1 = doSimpleTestSetup(true);
    ASSERT_TRUE(m_alertScheduler->scheduleAlert(alert1));

    // check that a future alert is not scheduled if you cant store the alert
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(1));
    m_alertStorage->setStoreRetVal(false);
    ASSERT_FALSE(m_alertScheduler->scheduleAlert(alert2));

    // check that past alerts cant be scheduled
    std::shared_ptr<TestAlert> alert3 = std::make_shared<TestAlert>(ALERT3_TOKEN, PAST_INSTANT);
    m_alertStorage->setStoreRetVal(true);
    ASSERT_TRUE(m_alertScheduler->scheduleAlert(alert2));
    ASSERT_FALSE(m_alertScheduler->scheduleAlert(alert3));
}

/**
 * Test update alert scheduled time.
 */
TEST_F(AlertSchedulerTest, rescheduleAlert) {
    // Schedule an alert and create an updated version with the same token.
    auto oldAlert = doSimpleTestSetup();
    auto newAlert = std::make_shared<TestAlert>(oldAlert->getToken(), getFutureInstant(2));
    ASSERT_NE(oldAlert->getScheduledTime_ISO_8601(), newAlert->getScheduledTime_ISO_8601());

    // Expect database to be updated with new schedule.
    EXPECT_CALL(*m_alertStorage, modify(_)).WillOnce(Return(true));

    // Call schedule alert for an alert that already exists.
    EXPECT_TRUE(m_alertScheduler->scheduleAlert(newAlert));
    ASSERT_EQ(oldAlert->getScheduledTime_ISO_8601(), newAlert->getScheduledTime_ISO_8601());
}

/**
 * Test update alert scheduled time to now will start rendering the alert.
 */
TEST_F(AlertSchedulerTest, rescheduleAlertNow) {
    // Schedule an alert and create an updated version with the same token.
    auto oldAlert = doSimpleTestSetup(/*activeAlert*/ false, /*initWithAlertObserver*/ true);
    auto newAlert = std::make_shared<TestAlert>(oldAlert->getToken(), getTimeNow());
    ASSERT_NE(oldAlert->getScheduledTime_ISO_8601(), newAlert->getScheduledTime_ISO_8601());

    // Expect database to be updated with new schedule.
    EXPECT_CALL(*m_alertStorage, modify(_)).WillOnce(Return(true));

    // Call schedule alert for an alert that already exists.
    EXPECT_TRUE(m_alertScheduler->scheduleAlert(newAlert));
    ASSERT_EQ(oldAlert->getScheduledTime_ISO_8601(), newAlert->getScheduledTime_ISO_8601());

    // Wait till alarm is ready to be rendered.
    EXPECT_TRUE(m_testAlertObserver->waitFor(AlertObserverInterface::State::READY));
}

/**
 * Test update alert scheduled time fails.
 */
TEST_F(AlertSchedulerTest, rescheduleAlertFails) {
    // Schedule an alert and create an updated version with the same token.
    auto oldAlert = doSimpleTestSetup();
    auto newAlert = std::make_shared<TestAlert>(oldAlert->getToken(), getFutureInstant(2));
    auto oldScheduledTime = oldAlert->getScheduledTime_ISO_8601();
    ASSERT_NE(newAlert->getScheduledTime_ISO_8601(), oldScheduledTime);

    // Simulate database failure.
    EXPECT_CALL(*m_alertStorage, modify(_)).WillOnce(Return(false));

    // Call schedule alert for an alert that already exists.
    EXPECT_FALSE(m_alertScheduler->scheduleAlert(newAlert));
    EXPECT_EQ(oldAlert->getScheduledTime_ISO_8601(), oldScheduledTime);
}

/**
 * Test snoozing alerts
 */
TEST_F(AlertSchedulerTest, snoozeAlert) {
    doSimpleTestSetup(true);

    // check that a random alert token is ignored
    ASSERT_FALSE(m_alertScheduler->snoozeAlert(ALERT2_TOKEN, getFutureInstant(1)));

    // check that we succeed if the correct token is available
    ASSERT_TRUE(m_alertScheduler->snoozeAlert(ALERT1_TOKEN, getFutureInstant(1)));
}

/**
 * Test deleting single alert
 */
TEST_F(AlertSchedulerTest, deleteAlertSingle) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert1);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND);

    // if active alert and the token matches, ensure that we dont delete it (we deactivate the alert actually)
    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(0);
    ASSERT_TRUE(m_alertScheduler->deleteAlert(ALERT1_TOKEN));

    // check that a random alert token is ignored
    ASSERT_TRUE(m_alertScheduler->deleteAlert(ALERT2_TOKEN));

    // if inactive alert, then check that we succeed if the correct token is available
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);
    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);
    ASSERT_TRUE(m_alertScheduler->deleteAlert(ALERT2_TOKEN));
}

/**
 * Test deleting multiple alerts - one at a time
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsSingle) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete one existing
    ASSERT_EQ(m_alertScheduler->getAllAlerts().size(), 2u);
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN}));
    ASSERT_EQ(m_alertScheduler->getAllAlerts().size(), 1u);

    // Delete one non-existing
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT3_TOKEN}));
    ASSERT_EQ(m_alertScheduler->getAllAlerts().size(), 1u);

    // Delete the last existing
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT2_TOKEN}));
    ASSERT_EQ(m_alertScheduler->getAllAlerts().size(), 0u);
}

/**
 * Test deleting multiple existing alerts
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsMultipleExisting) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete multiple existing
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT2_TOKEN}));
    EXPECT_EQ(m_alertScheduler->getAllAlerts().size(), 0u);
}

/**
 * Test deleting multiple alerts, both existing and not
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsMultipleMixed) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete multiple mixed
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT3_TOKEN}));
    EXPECT_EQ(m_alertScheduler->getAllAlerts().size(), 1u);
}

/**
 * Test deleting multiple non-existing alerts
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsMultipleMissing) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete multiple non-existing
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT3_TOKEN, ALERT4_TOKEN}));
    EXPECT_EQ(m_alertScheduler->getAllAlerts().size(), 2u);
}

/**
 * Test deleting same alerts multiple times
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsMultipleSame) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete same multiple times
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT1_TOKEN}));
    EXPECT_EQ(m_alertScheduler->getAllAlerts().size(), 1u);
}

/**
 * Test bulk deleting with empty list
 */
TEST_F(AlertSchedulerTest, bulkDeleteAlertsMultipleEmpty) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(2));

    ON_CALL(*m_alertStorage.get(), bulkErase(_)).WillByDefault(Return(true));

    alertsToAdd.push_back(alert1);
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);

    // Delete empty
    EXPECT_TRUE(m_alertScheduler->deleteAlerts({}));
    EXPECT_EQ(m_alertScheduler->getAllAlerts().size(), 2u);
}

/**
 * Test method that checks if an alert is active
 */
TEST_F(AlertSchedulerTest, isAlertActive) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;

    /// active alert
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert1);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND);

    /// inactive alert
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);

    /// Success expected for active alert
    ASSERT_TRUE(m_alertScheduler->isAlertActive(alert1));

    /// Failure expected for inactive alert
    ASSERT_FALSE(m_alertScheduler->isAlertActive(alert2));
}

/**
 * Test to see if the correct context about the scheduler is obtained
 */
TEST_F(AlertSchedulerTest, getContextInfo) {
    std::shared_ptr<AlertScheduler> alertSchedulerObs{
        std::make_shared<AlertScheduler>(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)};
    std::vector<std::shared_ptr<TestAlert>> alertsToAdd;

    /// Schedule 2 alerts one of which is active.
    std::shared_ptr<TestAlert> alert1 = std::make_shared<TestAlert>(ALERT1_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert1);
    std::shared_ptr<TestAlert> alert2 = std::make_shared<TestAlert>(ALERT2_TOKEN, getFutureInstant(1));
    alertsToAdd.push_back(alert2);
    m_alertStorage->setAlerts(alertsToAdd);
    m_alertScheduler->initialize(alertSchedulerObs);
    m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND);

    AlertScheduler::AlertsContextInfo resultContextInfo = m_alertScheduler->getContextInfo();

    const unsigned int expectedRemainingScheduledAlerts = 2;
    const unsigned int expectedRemainingActiveAlerts = 1;

    /// Check that 2 alerts were scheduled, one of which is active.
    ASSERT_EQ(resultContextInfo.scheduledAlerts.size(), expectedRemainingScheduledAlerts);
    ASSERT_EQ(resultContextInfo.activeAlerts.size(), expectedRemainingActiveAlerts);
}

/**
 * Test local stop on AlertScheduler
 */
TEST_F(AlertSchedulerTest, onLocalStop) {
    std::shared_ptr<TestAlert> alert = doSimpleTestSetup(true);

    m_alertScheduler->onLocalStop();

    ASSERT_EQ(alert->getState(), Alert::State::STOPPING);
    ASSERT_EQ(alert->getStopReason(), Alert::StopReason::LOCAL_STOP);
}

/**
 * Test if AlertScheduler clears data
 */
TEST_F(AlertSchedulerTest, clearData) {
    std::shared_ptr<TestAlert> alert = doSimpleTestSetup(true);
    EXPECT_CALL(*(m_alertStorage.get()), clearDatabase()).Times(1);

    m_alertScheduler->clearData();

    ASSERT_EQ(alert->getState(), Alert::State::STOPPING);
    ASSERT_EQ(alert->getStopReason(), Alert::StopReason::SHUTDOWN);
}

/**
 * Test if AlertScheduler clears data
 */
TEST_F(AlertSchedulerTest, clearDataLogout) {
    std::shared_ptr<TestAlert> alert = doSimpleTestSetup(true);
    EXPECT_CALL(*(m_alertStorage.get()), clearDatabase()).Times(1);

    m_alertScheduler->clearData(Alert::StopReason::LOG_OUT);

    ASSERT_EQ(alert->getState(), Alert::State::STOPPING);
    ASSERT_EQ(alert->getStopReason(), Alert::StopReason::LOG_OUT);
}

/**
 * Test if AlertScheduler shuts down appropriately
 */
TEST_F(AlertSchedulerTest, shutdown) {
    doSimpleTestSetup(true);

    m_alertScheduler->shutdown();

    const unsigned int expectedRemainingAlerts = 0;
    /// check if all scheduled alerts are cleared out
    ASSERT_EQ(m_alertScheduler->getContextInfo().scheduledAlerts.size(), expectedRemainingAlerts);
}

/**
 * Test Alert state change to Active on an inactive alert
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeStartedInactiveAlert) {
    const std::string testReason = "stateStarted";
    auto testState = AlertScheduler::State::STARTED;

    doSimpleTestSetup(false, true);

    /// check that we ignore inactive alerts
    EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(0);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
}

/**
 * Test Alert state change to Active on an active alert
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeStartedActiveAlert) {
    const std::string testReason = "stateStarted";
    auto testState = AlertScheduler::State::STARTED;

    doSimpleTestSetup(true, true);

    /// active alerts should be handled
    EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

/**
 * Test Alert state change to Stopped
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeStopped) {
    const std::string testReason = "stateStopped";
    auto testState = AlertScheduler::State::STOPPED;

    doSimpleTestSetup(true, true);

    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

/**
 * Test Alert state change to Completed
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeCompleted) {
    const std::string testReason = "stateCompleted";
    auto testState = AlertScheduler::State::COMPLETED;

    doSimpleTestSetup(true, true);

    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

/**
 * Test Alert state change to Snoozed
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeSnoozed) {
    const std::string testReason = "stateSnoozed";
    auto testState = AlertScheduler::State::SNOOZED;

    doSimpleTestSetup(true, true);

    EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

/**
 * Test Alert state change to Error on an active alert
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeErrorActiveAlert) {
    const std::string testReason = "stateError";
    auto testState = AlertScheduler::State::ERROR;

    doSimpleTestSetup(true, true);

    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

/**
 * Test Alert state change to Error on an inactive alert
 */
TEST_F(AlertSchedulerTest, onAlertStateChangeErrorInactiveAlert) {
    const std::string testReason = "stateError";
    auto testState = AlertScheduler::State::ERROR;

    doSimpleTestSetup(false, true);

    EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1);
    m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason);
    ASSERT_TRUE(m_testAlertObserver->waitFor(testState));
}

}  // namespace test
}  // namespace alerts
}  // namespace capabilityAgents
}  // namespace alexaClientSDK
