#include "activation.hpp"
#include "mocked_association_interface.hpp"
#include "mocked_utils.hpp"

#include <sdbusplus/test/sdbus_mock.hpp>

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

using namespace phosphor::software::updater;

using ::testing::_;
using ::testing::Return;
using ::testing::StrEq;

using std::experimental::any;

class TestActivation : public ::testing::Test
{
  public:
    using PropertyType = utils::UtilsInterface::PropertyType;
    using Status = Activation::Status;
    using RequestedStatus = Activation::RequestedActivations;
    TestActivation() :
        mockedUtils(
            reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
    {
        // By default make it compatible with the test software
        ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
            .WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
        ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MODEL)))
            .WillByDefault(Return(any(PropertyType(std::string("TestModel")))));
    }
    ~TestActivation()
    {
        utils::freeUtils();
    }

    void onUpdateDone()
    {
        activation->onUpdateDone();
    }
    void onUpdateFailed()
    {
        activation->onUpdateFailed();
    }
    int getProgress()
    {
        return activation->activationProgress->progress();
    }
    const auto& getPsuQueue()
    {
        return activation->psuQueue;
    }
    std::string getUpdateService(const std::string& psuInventoryPath)
    {
        return activation->getUpdateService(psuInventoryPath);
    }

    sdbusplus::SdBusMock sdbusMock;
    sdbusplus::bus::bus mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
    const utils::MockedUtils& mockedUtils;
    MockedAssociationInterface mockedAssociationInterface;
    std::unique_ptr<Activation> activation;
    std::string versionId = "abcdefgh";
    std::string extVersion = "manufacturer=TestManu,model=TestModel";
    std::string filePath = "";
    std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
    Status status = Status::Ready;
    AssociationList associations;
};

TEST_F(TestActivation, ctordtor)
{
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
}

TEST_F(TestActivation, ctorWithInvalidExtVersion)
{
    extVersion = "invalid text";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
}

TEST_F(TestActivation, getUpdateService)
{
    std::string psuInventoryPath = "/com/example/inventory/powersupply1";
    std::string toCompare = "psu-update@-com-example-inventory-"
                            "powersupply1\\x20-tmp-images-12345678.service";
    versionId = "12345678";
    filePath = "/tmp/images/12345678";

    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);

    auto service = getUpdateService(psuInventoryPath);
    EXPECT_EQ(toCompare, service);
}

TEST_F(TestActivation, doUpdateWhenNoPSU)
{
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(
            Return(std::vector<std::string>({}))); // No PSU inventory
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(0);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(0);
    EXPECT_EQ(Status::Failed, activation->activation());
}

TEST_F(TestActivation, doUpdateOnePSUOK)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(
            Return(std::vector<std::string>({psu0}))); // One PSU inventory
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Activating, activation->activation());

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(1);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(1);
    onUpdateDone();
    EXPECT_EQ(Status::Active, activation->activation());
}

TEST_F(TestActivation, doUpdateFourPSUsOK)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    constexpr auto psu1 = "/com/example/inventory/psu1";
    constexpr auto psu2 = "/com/example/inventory/psu2";
    constexpr auto psu3 = "/com/example/inventory/psu3";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(Return(
            std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(10, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(30, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(50, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(70, getProgress());

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(1);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(1);

    onUpdateDone();
    EXPECT_EQ(Status::Active, activation->activation());
}

TEST_F(TestActivation, doUpdateFourPSUsFailonSecond)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    constexpr auto psu1 = "/com/example/inventory/psu1";
    constexpr auto psu2 = "/com/example/inventory/psu2";
    constexpr auto psu3 = "/com/example/inventory/psu3";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(Return(
            std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(10, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(30, getProgress());

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(0);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(0);
    onUpdateFailed();
    EXPECT_EQ(Status::Failed, activation->activation());
}

TEST_F(TestActivation, doUpdateOnExceptionFromDbus)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(
            Return(std::vector<std::string>({psu0}))); // One PSU inventory
    ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr))
        .WillByDefault(Return(-1)); // Make sdbus call failure
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Failed, activation->activation());
}

TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    extVersion = "manufacturer=TestManu,model=DifferentModel";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(Return(std::vector<std::string>({psu0})));
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Failed, activation->activation());
}

TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    extVersion = "manufacturer=DifferentManu,model=TestModel";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(Return(std::vector<std::string>({psu0})));
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Failed, activation->activation());
}

TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
{
    ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
        .WillByDefault(Return(any(PropertyType(std::string("")))));
    extVersion = "manufacturer=AnyManu,model=TestModel";
    // Below is the same as doUpdateOnePSUOK case
    constexpr auto psu0 = "/com/example/inventory/psu0";
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(
            Return(std::vector<std::string>({psu0}))); // One PSU inventory
    activation->requestedActivation(RequestedStatus::Active);

    EXPECT_EQ(Status::Activating, activation->activation());

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(1);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(1);
    onUpdateDone();
    EXPECT_EQ(Status::Active, activation->activation());
}

TEST_F(TestActivation, doUpdateFourPSUsSecondPSUNotCompatible)
{
    constexpr auto psu0 = "/com/example/inventory/psu0";
    constexpr auto psu1 = "/com/example/inventory/psu1";
    constexpr auto psu2 = "/com/example/inventory/psu2";
    constexpr auto psu3 = "/com/example/inventory/psu3";
    ON_CALL(mockedUtils, getPropertyImpl(_, _, StrEq(psu1), _, StrEq(MODEL)))
        .WillByDefault(
            Return(any(PropertyType(std::string("DifferentModel")))));
    activation = std::make_unique<Activation>(
        mockedBus, dBusPath, versionId, extVersion, status, associations,
        &mockedAssociationInterface, filePath);
    ON_CALL(mockedUtils, getPSUInventoryPath(_))
        .WillByDefault(Return(
            std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
    activation->requestedActivation(RequestedStatus::Active);

    const auto& psuQueue = getPsuQueue();
    EXPECT_EQ(3u, psuQueue.size());

    // Only 3 PSUs shall be updated, and psu1 shall be skipped
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(10, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(36, getProgress());

    onUpdateDone();
    EXPECT_EQ(Status::Activating, activation->activation());
    EXPECT_EQ(62, getProgress());

    EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
        .Times(1);
    EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
        .Times(1);

    onUpdateDone();
    EXPECT_EQ(Status::Active, activation->activation());
}