/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2020, Agnieszka Cicha-Cisek
 * Copyright (c) 2020, Patryk Cisek
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include<ctime>
#include <memory>
#include "gtest/gtest.h"
#include "nitrokeyimpl.h"

#include <boost/log/trivial.hpp>

// Test vector:
// Base32: VVTVOPASJDPDAYAH3UTMV33FWKRDOSI5
// HEX: AD67573C1248DE306007DD26CAEF65B2A237491D
// Online: https://www.tomeko.net/online_tools/base32.php?lang=en

class NitrokeyImplTest : public ::testing::Test
{
protected:
    static constexpr std::size_t TEMP_PASS_MAX_LEN = 25;
    std::unique_ptr<NitrokeyImpl> key;
    const bool OVERWRITE_TOTP_SLOTS = true;
    const std::string ADMIN_PIN = "12345678";
    const std::string USER_PIN = "123456";
    const std::string TEMP_ADMIN_PASSWORD = std::string("22a6ba74-b0a4-4927-addd-eef6130661e4").substr(0, TEMP_PASS_MAX_LEN);
    const std::string TEMP_USER_PASSWORD  = std::string("967f9eca-fde3-4a9f-8aad-d679cc186099").substr(0, TEMP_PASS_MAX_LEN);
    const std::uint64_t TEST_TIMESTAMP = 1590172719;

    const std::string B32_SECRET = "VVTVOPASJDPDAYAH3UTMV33FWKRDOSI5";
    const std::string HEX_SECRET = "AD67573C1248DE306007DD26CAEF65B2A237491D";

    const std::string SLOT0_NAME = "test1";
    const std::string SLOT1_NAME = "test2";

    void SetUp() override
    {
        key.reset(new NitrokeyImpl(boost::log::trivial::warning));
    }

    void eraseAllTotpSlots()
    {
        auto obtainedSlots = key->getSlots();
        for (const auto &obtainedSlot : obtainedSlots)
        {
            key->eraseTotpSlot(obtainedSlot.slotNumber(), TEMP_ADMIN_PASSWORD);
        }
    }

    void initiateTestSlots()
    {
        key->connect();
        key->setTime(TEST_TIMESTAMP);
        ASSERT_TRUE(key->firstAuth(ADMIN_PIN, TEMP_ADMIN_PASSWORD));
        if (OVERWRITE_TOTP_SLOTS) {
            eraseAllTotpSlots();
            key->writeTotpSlot(0, SLOT0_NAME, HEX_SECRET,
                               30, TEMP_ADMIN_PASSWORD);
            key->writeTotpSlot(1, SLOT1_NAME, HEX_SECRET,
                               30, TEMP_ADMIN_PASSWORD);
        }
    }
};

TEST_F(NitrokeyImplTest, CanConnectToNitrokey)
{
    // When
    auto isConnected = key->connect();

    // Then
    ASSERT_TRUE(isConnected);
}

TEST_F(NitrokeyImplTest, CanReadTOTPSlotName)
{
    // Given
    initiateTestSlots();

    // When
    auto slotName = key->getSlotName(0);

    // Then
    const auto expectedName = std::string(SLOT0_NAME);
    ASSERT_EQ(expectedName, slotName);
}

TEST_F(NitrokeyImplTest, CanReadAllSlots)
{
    // Given
    initiateTestSlots();

    // When
    auto aquiredSlots = key->getSlots();

    // Then
    ASSERT_EQ(aquiredSlots.size(), 2);
    ASSERT_EQ(aquiredSlots[0].slotNumber(), 0);
    ASSERT_EQ(aquiredSlots[0].slotName(), SLOT0_NAME);
    ASSERT_EQ(aquiredSlots[1].slotNumber(), 1);
    ASSERT_EQ(aquiredSlots[1].slotName(), SLOT1_NAME);
}

TEST_F(NitrokeyImplTest, CanReadTOTPCode)
{
    // Given
    const std::string EXPECTED_CODE = "577915";
    initiateTestSlots();

    auto aquiredSlots = key->getSlots();
    key->userAuth("USER_PIN", TEMP_USER_PASSWORD);

    // When
    auto code = key->getTOTPCode(aquiredSlots[0], "");

    // Then
    ASSERT_EQ(EXPECTED_CODE, code);
}

TEST_F(NitrokeyImplTest, CanEraseTOTPSlot)
{
    // Given
    initiateTestSlots();
    constexpr std::uint8_t SLOT_TO_BE_ERASED = 0;

    // When
    key->eraseTotpSlot(SLOT_TO_BE_ERASED, TEMP_ADMIN_PASSWORD);

    // Then
    auto obtainedSlots = key->getSlots();
    constexpr std::vector<TOTPSlot>::size_type
            EXPECTED_OBTAINED_SLOTS_SIZE = 1;
    constexpr std::uint8_t EXPECTED_SLOT_NUMBER = 1;
    ASSERT_EQ(EXPECTED_OBTAINED_SLOTS_SIZE, obtainedSlots.size());
    ASSERT_EQ(EXPECTED_SLOT_NUMBER, obtainedSlots[0].slotNumber());
    ASSERT_EQ(SLOT1_NAME, obtainedSlots[0].slotName());
}
