/*
 *    Copyright (c) 2023 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.
 */

/**
 *    @file
 *      This file defines a basic implementation of SubscriptionResumptionStorage that
 *      persists subscriptions in a flat list in TLV.
 */

#include <app/SimpleSubscriptionResumptionStorage.h>

#include <lib/support/Base64.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <lib/support/logging/CHIPLogging.h>

namespace chip {
namespace app {

constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kPeerNodeIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kFabricIndexTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kSubscriptionIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kMinIntervalTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kMaxIntervalTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kFabricFilteredTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributePathsListTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathsListTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributePathTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEndpointIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kClusterIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributeIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventIdTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTypeTag;
constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kResumptionRetriesTag;

SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::SimpleSubscriptionInfoIterator(
    SimpleSubscriptionResumptionStorage & storage) :
    mStorage(storage)
{
    mNextIndex = 0;
}

size_t SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Count()
{
    return static_cast<size_t>(mStorage.Count());
}

bool SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Next(SubscriptionInfo & output)
{
    for (; mNextIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; mNextIndex++)
    {
        CHIP_ERROR err = mStorage.Load(mNextIndex, output);
        if (err == CHIP_NO_ERROR)
        {
            // increment index for the next call
            mNextIndex++;
            return true;
        }

        if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
        {
            ChipLogError(DataManagement, "Failed to load subscription at index %u error %" CHIP_ERROR_FORMAT,
                         static_cast<unsigned>(mNextIndex), err.Format());
            mStorage.Delete(mNextIndex);
        }
    }

    return false;
}

void SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Release()
{
    mStorage.mSubscriptionInfoIterators.ReleaseObject(this);
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Init(PersistentStorageDelegate * storage)
{
    VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
    mStorage = storage;

    uint16_t countMax;
    uint16_t len = sizeof(countMax);
    CHIP_ERROR err =
        mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName(), &countMax, len);
    // If there's a previous countMax and it's larger than CHIP_IM_MAX_NUM_SUBSCRIPTIONS,
    // clean up subscriptions beyond the limit
    if ((err == CHIP_NO_ERROR) && (countMax != CHIP_IM_MAX_NUM_SUBSCRIPTIONS))
    {
        for (uint16_t subscriptionIndex = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex < countMax; subscriptionIndex++)
        {
            Delete(subscriptionIndex);
        }
    }

    // Always save the current CHIP_IM_MAX_NUM_SUBSCRIPTIONS
    uint16_t countMaxToSave = CHIP_IM_MAX_NUM_SUBSCRIPTIONS;
    ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName(),
                                                   &countMaxToSave, sizeof(uint16_t)));

    return CHIP_NO_ERROR;
}

SubscriptionResumptionStorage::SubscriptionInfoIterator * SimpleSubscriptionResumptionStorage::IterateSubscriptions()
{
    return mSubscriptionInfoIterators.CreateObject(*this);
}

uint16_t SimpleSubscriptionResumptionStorage::Count()
{
    uint16_t subscriptionCount = 0;
    for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++)
    {
        if (mStorage->SyncDoesKeyExist(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName()))
        {
            subscriptionCount++;
        }
    }

    return subscriptionCount;
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Delete(uint16_t subscriptionIndex)
{
    return mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName());
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Load(uint16_t subscriptionIndex, SubscriptionInfo & subscriptionInfo)
{
    Platform::ScopedMemoryBuffer<uint8_t> backingBuffer;
    backingBuffer.Calloc(MaxSubscriptionSize());
    ReturnErrorCodeIf(backingBuffer.Get() == nullptr, CHIP_ERROR_NO_MEMORY);

    uint16_t len = static_cast<uint16_t>(MaxSubscriptionSize());
    ReturnErrorOnFailure(mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName(),
                                                   backingBuffer.Get(), len));

    TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), len);

    ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));

    TLV::TLVType subscriptionContainerType;
    ReturnErrorOnFailure(reader.EnterContainer(subscriptionContainerType));

    // Node ID
    ReturnErrorOnFailure(reader.Next(kPeerNodeIdTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mNodeId));

    // Fabric index
    ReturnErrorOnFailure(reader.Next(kFabricIndexTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mFabricIndex));

    // Subscription ID
    ReturnErrorOnFailure(reader.Next(kSubscriptionIdTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mSubscriptionId));

    // Min interval
    ReturnErrorOnFailure(reader.Next(kMinIntervalTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mMinInterval));

    // Max interval
    ReturnErrorOnFailure(reader.Next(kMaxIntervalTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mMaxInterval));

    // Fabric filtered boolean
    ReturnErrorOnFailure(reader.Next(kFabricFilteredTag));
    ReturnErrorOnFailure(reader.Get(subscriptionInfo.mFabricFiltered));

    // Attribute Paths
    ReturnErrorOnFailure(reader.Next(TLV::kTLVType_List, kAttributePathsListTag));
    TLV::TLVType attributesListType;
    ReturnErrorOnFailure(reader.EnterContainer(attributesListType));

    size_t pathCount = 0;
    ReturnErrorOnFailure(reader.CountRemainingInContainer(&pathCount));

    // If a stack struct is being reused to iterate, free the previous paths ScopedMemoryBuffer
    subscriptionInfo.mAttributePaths.Free();
    if (pathCount)
    {
        subscriptionInfo.mAttributePaths.Calloc(pathCount);
        ReturnErrorCodeIf(subscriptionInfo.mAttributePaths.Get() == nullptr, CHIP_ERROR_NO_MEMORY);
        for (size_t pathIndex = 0; pathIndex < pathCount; pathIndex++)
        {
            ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, kAttributePathTag));
            TLV::TLVType attributeContainerType;
            ReturnErrorOnFailure(reader.EnterContainer(attributeContainerType));

            ReturnErrorOnFailure(reader.Next(kEndpointIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mEndpointId));

            ReturnErrorOnFailure(reader.Next(kClusterIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mClusterId));

            ReturnErrorOnFailure(reader.Next(kAttributeIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mAttributeId));

            ReturnErrorOnFailure(reader.ExitContainer(attributeContainerType));
        }
    }
    ReturnErrorOnFailure(reader.ExitContainer(attributesListType));

    // Event Paths
    ReturnErrorOnFailure(reader.Next(TLV::kTLVType_List, kEventPathsListTag));
    TLV::TLVType eventsListType;
    ReturnErrorOnFailure(reader.EnterContainer(eventsListType));

    ReturnErrorOnFailure(reader.CountRemainingInContainer(&pathCount));

    // If a stack struct is being reused to iterate, free the previous paths ScopedMemoryBuffer
    subscriptionInfo.mEventPaths.Free();
    if (pathCount)
    {
        subscriptionInfo.mEventPaths.Calloc(pathCount);
        ReturnErrorCodeIf(subscriptionInfo.mEventPaths.Get() == nullptr, CHIP_ERROR_NO_MEMORY);
        for (size_t pathIndex = 0; pathIndex < pathCount; pathIndex++)
        {
            ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, kEventPathTag));
            TLV::TLVType eventContainerType;
            ReturnErrorOnFailure(reader.EnterContainer(eventContainerType));

            EventPathType eventPathType;
            ReturnErrorOnFailure(reader.Next(kEventPathTypeTag));
            ReturnErrorOnFailure(reader.Get(eventPathType));

            subscriptionInfo.mEventPaths[pathIndex].mIsUrgentEvent = (eventPathType == EventPathType::kUrgent);

            ReturnErrorOnFailure(reader.Next(kEndpointIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mEndpointId));

            ReturnErrorOnFailure(reader.Next(kClusterIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mClusterId));

            ReturnErrorOnFailure(reader.Next(kEventIdTag));
            ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mEventId));

            ReturnErrorOnFailure(reader.ExitContainer(eventContainerType));
        }
    }
    ReturnErrorOnFailure(reader.ExitContainer(eventsListType));

#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    // If the reader cannot get resumption retries, set it to 0 for subscriptionInfo
    if (reader.Next(kResumptionRetriesTag) == CHIP_NO_ERROR)
    {
        ReturnErrorOnFailure(reader.Get(subscriptionInfo.mResumptionRetries));
    }
    else
    {
        subscriptionInfo.mResumptionRetries = 0;
    }
#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION

    ReturnErrorOnFailure(reader.ExitContainer(subscriptionContainerType));

    return CHIP_NO_ERROR;
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Save(TLV::TLVWriter & writer, SubscriptionInfo & subscriptionInfo)
{
    TLV::TLVType subscriptionContainerType;
    ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, subscriptionContainerType));
    ReturnErrorOnFailure(writer.Put(kPeerNodeIdTag, subscriptionInfo.mNodeId));
    ReturnErrorOnFailure(writer.Put(kFabricIndexTag, subscriptionInfo.mFabricIndex));
    ReturnErrorOnFailure(writer.Put(kSubscriptionIdTag, subscriptionInfo.mSubscriptionId));
    ReturnErrorOnFailure(writer.Put(kMinIntervalTag, subscriptionInfo.mMinInterval));
    ReturnErrorOnFailure(writer.Put(kMaxIntervalTag, subscriptionInfo.mMaxInterval));
    ReturnErrorOnFailure(writer.Put(kFabricFilteredTag, subscriptionInfo.mFabricFiltered));

    // Attribute paths
    TLV::TLVType attributesListType;
    ReturnErrorOnFailure(writer.StartContainer(kAttributePathsListTag, TLV::kTLVType_List, attributesListType));
    for (size_t pathIndex = 0; pathIndex < subscriptionInfo.mAttributePaths.AllocatedSize(); pathIndex++)
    {
        TLV::TLVType attributeContainerType = TLV::kTLVType_Structure;
        ReturnErrorOnFailure(writer.StartContainer(kAttributePathTag, TLV::kTLVType_Structure, attributeContainerType));

        ReturnErrorOnFailure(writer.Put(kEndpointIdTag, subscriptionInfo.mAttributePaths[pathIndex].mEndpointId));
        ReturnErrorOnFailure(writer.Put(kClusterIdTag, subscriptionInfo.mAttributePaths[pathIndex].mClusterId));
        ReturnErrorOnFailure(writer.Put(kAttributeIdTag, subscriptionInfo.mAttributePaths[pathIndex].mAttributeId));

        ReturnErrorOnFailure(writer.EndContainer(attributeContainerType));
    }
    ReturnErrorOnFailure(writer.EndContainer(attributesListType));

    // Event paths
    TLV::TLVType eventsListType;
    ReturnErrorOnFailure(writer.StartContainer(kEventPathsListTag, TLV::kTLVType_List, eventsListType));
    for (size_t pathIndex = 0; pathIndex < subscriptionInfo.mEventPaths.AllocatedSize(); pathIndex++)
    {
        TLV::TLVType eventContainerType = TLV::kTLVType_Structure;
        ReturnErrorOnFailure(writer.StartContainer(kEventPathTag, TLV::kTLVType_Structure, eventContainerType));

        if (subscriptionInfo.mEventPaths[pathIndex].mIsUrgentEvent)
        {
            ReturnErrorOnFailure(writer.Put(kEventPathTypeTag, EventPathType::kUrgent));
        }
        else
        {
            ReturnErrorOnFailure(writer.Put(kEventPathTypeTag, EventPathType::kNonUrgent));
        }
        ReturnErrorOnFailure(writer.Put(kEndpointIdTag, subscriptionInfo.mEventPaths[pathIndex].mEndpointId));
        ReturnErrorOnFailure(writer.Put(kClusterIdTag, subscriptionInfo.mEventPaths[pathIndex].mClusterId));
        ReturnErrorOnFailure(writer.Put(kEventIdTag, subscriptionInfo.mEventPaths[pathIndex].mEventId));

        ReturnErrorOnFailure(writer.EndContainer(eventContainerType));
    }
    ReturnErrorOnFailure(writer.EndContainer(eventsListType));
#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
    ReturnErrorOnFailure(writer.Put(kResumptionRetriesTag, subscriptionInfo.mResumptionRetries));
#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION

    ReturnErrorOnFailure(writer.EndContainer(subscriptionContainerType));

    return CHIP_NO_ERROR;
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Save(SubscriptionInfo & subscriptionInfo)
{
    // Find empty index or duplicate if exists
    uint16_t subscriptionIndex;
    uint16_t firstEmptySubscriptionIndex = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; // initialize to out of bounds as "not set"
    for (subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++)
    {
        SubscriptionInfo currentSubscriptionInfo;
        CHIP_ERROR err = Load(subscriptionIndex, currentSubscriptionInfo);

        // if empty and firstEmptySubscriptionIndex isn't set yet, then mark empty spot
        if ((firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS) && (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
        {
            firstEmptySubscriptionIndex = subscriptionIndex;
        }

        // delete duplicate
        if (err == CHIP_NO_ERROR)
        {
            if ((subscriptionInfo.mNodeId == currentSubscriptionInfo.mNodeId) &&
                (subscriptionInfo.mFabricIndex == currentSubscriptionInfo.mFabricIndex) &&
                (subscriptionInfo.mSubscriptionId == currentSubscriptionInfo.mSubscriptionId))
            {
                Delete(subscriptionIndex);
                // if duplicate is the first empty spot, then also set it
                if (firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS)
                {
                    firstEmptySubscriptionIndex = subscriptionIndex;
                }
            }
        }
    }

    // Fail if no empty space
    if (firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS)
    {
        return CHIP_ERROR_NO_MEMORY;
    }

    // Now construct subscription state and save
    Platform::ScopedMemoryBuffer<uint8_t> backingBuffer;
    backingBuffer.Calloc(MaxSubscriptionSize());
    ReturnErrorCodeIf(backingBuffer.Get() == nullptr, CHIP_ERROR_NO_MEMORY);

    TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), MaxSubscriptionSize());

    ReturnErrorOnFailure(Save(writer, subscriptionInfo));

    const auto len = writer.GetLengthWritten();
    VerifyOrReturnError(CanCastTo<uint16_t>(len), CHIP_ERROR_BUFFER_TOO_SMALL);

    writer.Finalize(backingBuffer);

    ReturnErrorOnFailure(
        mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(firstEmptySubscriptionIndex).KeyName(),
                                  backingBuffer.Get(), static_cast<uint16_t>(len)));

    return CHIP_NO_ERROR;
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::Delete(NodeId nodeId, FabricIndex fabricIndex, SubscriptionId subscriptionId)
{
    bool subscriptionFound   = false;
    CHIP_ERROR lastDeleteErr = CHIP_NO_ERROR;

    uint16_t remainingSubscriptionsCount = 0;
    for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++)
    {
        SubscriptionInfo subscriptionInfo;
        CHIP_ERROR err = Load(subscriptionIndex, subscriptionInfo);

        // delete match
        if (err == CHIP_NO_ERROR)
        {
            if ((nodeId == subscriptionInfo.mNodeId) && (fabricIndex == subscriptionInfo.mFabricIndex) &&
                (subscriptionId == subscriptionInfo.mSubscriptionId))
            {
                subscriptionFound    = true;
                CHIP_ERROR deleteErr = Delete(subscriptionIndex);
                if (deleteErr != CHIP_NO_ERROR)
                {
                    lastDeleteErr = deleteErr;
                }
            }
            else
            {
                remainingSubscriptionsCount++;
            }
        }
    }

    // if there are no persisted subscriptions, the MaxCount can also be deleted
    if (remainingSubscriptionsCount == 0)
    {
        DeleteMaxCount();
    }

    if (lastDeleteErr != CHIP_NO_ERROR)
    {
        return lastDeleteErr;
    }

    return subscriptionFound ? CHIP_NO_ERROR : CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::DeleteMaxCount()
{
    return mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName());
}

CHIP_ERROR SimpleSubscriptionResumptionStorage::DeleteAll(FabricIndex fabricIndex)
{
    CHIP_ERROR deleteErr = CHIP_NO_ERROR;

    uint16_t count = 0;
    for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++)
    {
        SubscriptionInfo subscriptionInfo;
        CHIP_ERROR err = Load(subscriptionIndex, subscriptionInfo);

        if (err == CHIP_NO_ERROR)
        {
            if (fabricIndex == subscriptionInfo.mFabricIndex)
            {
                err = Delete(subscriptionIndex);
                if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
                {
                    deleteErr = err;
                }
            }
            else
            {
                count++;
            }
        }
    }

    // if there are no persisted subscriptions, the MaxCount can also be deleted
    if (count == 0)
    {
        CHIP_ERROR err = DeleteMaxCount();

        if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
        {
            deleteErr = err;
        }
    }

    return deleteErr;
}

} // namespace app
} // namespace chip
