/*
 *
 *    Copyright (c) 2021-2022 Project CHIP Authors
 *    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.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License 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.
 */

#pragma once

#include <algorithm>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/SafeInt.h>
#include <lib/support/logging/CHIPLogging.h>
#include <map>
#include <set>
#include <string>
#include <vector>

namespace chip {

/**
 * Implementation of PersistentStorageDelegate suitable for unit tests,
 * where persistence lasts for the object's lifetime and where all data is retained
 * is memory.
 *
 * This version also has "poison keys" which, if accessed, yield an error. This can
 * be used in unit tests to make sure a module making use of the PersistentStorageDelegate
 * does not access some particular keys which should remain untouched by underlying
 * logic.
 */
class TestPersistentStorageDelegate : public PersistentStorageDelegate
{
public:
    enum class LoggingLevel : unsigned
    {
        kDisabled            = 0,
        kLogMutation         = 1,
        kLogMutationAndReads = 2,
    };

    TestPersistentStorageDelegate() {}

    CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override
    {
        if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
        {
            ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncGetKeyValue: Get key '%s'", StringOrNullMarker(key));
        }

        CHIP_ERROR err = SyncGetKeyValueInternal(key, buffer, size);

        if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
        {
            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
            {
                ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' not found",
                              StringOrNullMarker(key));
            }
            else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
            {
                ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' is a poison key",
                              StringOrNullMarker(key));
            }
        }

        return err;
    }

    CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override
    {
        if (mLoggingLevel >= LoggingLevel::kLogMutation)
        {
            ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncSetKeyValue, Set key '%s' with data size %u", key,
                          static_cast<unsigned>(size));
        }

        CHIP_ERROR err = SyncSetKeyValueInternal(key, value, size);

        if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
        {
            if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
            {
                ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncSetKeyValue: Key '%s' is a poison key",
                              StringOrNullMarker(key));
            }
        }

        return err;
    }

    CHIP_ERROR SyncDeleteKeyValue(const char * key) override
    {
        if (mLoggingLevel >= LoggingLevel::kLogMutation)
        {
            ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncDeleteKeyValue, Delete key '%s'", StringOrNullMarker(key));
        }
        CHIP_ERROR err = SyncDeleteKeyValueInternal(key);

        if (mLoggingLevel >= LoggingLevel::kLogMutation)
        {
            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
            {
                ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' not found",
                              StringOrNullMarker(key));
            }
            else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
            {
                ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' is a poison key",
                              StringOrNullMarker(key));
            }
        }

        return err;
    }

    /**
     * @brief Adds a "poison key": a key that, if read/written, implies some bad
     *        behavior occurred.
     *
     * @param key - Poison key to add to the set.
     */
    virtual void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); }

    /**
     * Allows subsequent writes to be rejected for unit testing purposes.
     */
    virtual void SetRejectWrites(bool rejectWrites) { mRejectWrites = rejectWrites; }

    /**
     * @brief Clear all "poison keys"
     *
     */
    virtual void ClearPoisonKeys() { mPoisonKeys.clear(); }

    /**
     * @brief Reset entire contents back to empty. This does NOT clear the "poison keys"
     *
     */
    virtual void ClearStorage() { mStorage.clear(); }

    /**
     * @return the number of keys currently written in storage
     */
    virtual size_t GetNumKeys() { return mStorage.size(); }

    /**
     * @return a set of all the keys stored
     */
    virtual std::set<std::string> GetKeys()
    {
        std::set<std::string> keys;

        for (auto it = mStorage.begin(); it != mStorage.end(); ++it)
        {
            keys.insert(it->first);
        }

        return keys;
    }

    /**
     * @brief Determine if storage has a given key
     *
     * @param key - key to find (case-sensitive)
     * @return true if key is present in storage, false otherwise
     */
    virtual bool HasKey(const std::string & key) { return (mStorage.find(key) != mStorage.end()); }

    /**
     * @brief Set the logging verbosity for debugging
     *
     * @param loggingLevel - logging verbosity level to set
     */
    virtual void SetLoggingLevel(LoggingLevel loggingLevel) { mLoggingLevel = loggingLevel; }

    /**
     * @brief Dump a list of all storage keys, sorted alphabetically
     */
    virtual void DumpKeys()
    {
        ChipLogError(Test, "TestPersistentStorageDelegate::DumpKeys: %u keys", static_cast<unsigned>(GetNumKeys()));

        auto allKeys = GetKeys();
        std::vector<std::string> allKeysSorted(allKeys.cbegin(), allKeys.cend());
        std::sort(allKeysSorted.begin(), allKeysSorted.end());

        for (const std::string & key : allKeysSorted)
        {
            (void) key.c_str(); // Guard against log level disabling  error logging which would make `key` unused.
            ChipLogError(Test, "  -> %s", key.c_str());
        }
    }

protected:
    virtual CHIP_ERROR SyncGetKeyValueInternal(const char * key, void * buffer, uint16_t & size)
    {
        ReturnErrorCodeIf(((buffer == nullptr) && (size != 0)), CHIP_ERROR_INVALID_ARGUMENT);

        // Making sure poison keys are not accessed
        if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
        {
            return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
        }

        bool contains = HasKey(key);
        VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);

        std::vector<uint8_t> & value = mStorage[key];
        size_t valueSize             = value.size();
        if (!CanCastTo<uint16_t>(valueSize))
        {
            return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
        }

        uint16_t valueSizeUint16 = static_cast<uint16_t>(valueSize);
        ReturnErrorCodeIf(size == 0 && valueSizeUint16 == 0, CHIP_NO_ERROR);
        ReturnErrorCodeIf(buffer == nullptr, CHIP_ERROR_BUFFER_TOO_SMALL);

        uint16_t sizeToCopy = std::min(size, valueSizeUint16);

        size = sizeToCopy;
        memcpy(buffer, value.data(), size);
        return size < valueSizeUint16 ? CHIP_ERROR_BUFFER_TOO_SMALL : CHIP_NO_ERROR;
    }

    virtual CHIP_ERROR SyncSetKeyValueInternal(const char * key, const void * value, uint16_t size)
    {
        // Make sure writes are allowed and poison keys are not accessed
        if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
        {
            return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
        }

        // Handle empty values
        if (value == nullptr)
        {
            if (size == 0)
            {
                mStorage[key] = std::vector<uint8_t>();
                return CHIP_NO_ERROR;
            }

            return CHIP_ERROR_INVALID_ARGUMENT;
        }
        // Handle non-empty values

        const uint8_t * bytes = static_cast<const uint8_t *>(value);
        mStorage[key]         = std::vector<uint8_t>(bytes, bytes + size);
        return CHIP_NO_ERROR;
    }

    virtual CHIP_ERROR SyncDeleteKeyValueInternal(const char * key)
    {
        // Make sure writes are allowed and poison keys are not accessed
        if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
        {
            return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
        }

        bool contains = HasKey(key);
        VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
        mStorage.erase(key);
        return CHIP_NO_ERROR;
    }

    std::map<std::string, std::vector<uint8_t>> mStorage;
    std::set<std::string> mPoisonKeys;
    bool mRejectWrites         = false;
    LoggingLevel mLoggingLevel = LoggingLevel::kDisabled;
};

} // namespace chip
