#include "ddk_config.h"
#include "ddk_fields.h"
#include "ddk_manager.h"
#include "ddk_parsed_entity.h"

#include <saas/rtyserver/factors/function.h>
#include <saas/rtyserver/config/const.h>
#include <saas/rtyserver/components/erf/erf_manager.h>
#include <saas/library/cgi/cgi.h>
#include <saas/library/proto_helper/proto_helper.h>

#include <library/cpp/containers/stack_vector/stack_vec.h>
#include <util/generic/cast.h>
#include <util/string/split.h>


namespace {
    /// Get field by its static index in ddkBlock
    template <class T, size_t Index>
    inline T GetFieldFromStorage(const TBasicFactorStorage& ddkBlock) {
        return *(const T*)(ddkBlock.Ptr(Index));
    }

}

TRTYDDKManager::TRTYDDKManager(IRTYErfManager* base, const TDDKComponentConfig& config, bool useUrlIdHash)
    : NRTYServer::IIndexComponentManager(DDK_COMPONENT_NAME)
    , Base(base)
    , Config(config)
    , IsStatic(Base ? Base->IsStatic() : false)
    , UseUrlIdHash(useUrlIdHash)
{
}

TRTYDDKManager::TRTYDDKManager(IRTYErfManager* base, const TDDKComponentConfig& config)
    : TRTYDDKManager(base, config, config.GetOwner().GetMeAs<TRTYServerConfig>().GetSearcherConfig().EnableUrlHash)
{
}

TRTYDDKManager::TRTYDDKManager(TAutoPtr<IRTYErfManager> base, const TDDKComponentConfig& config)
    : TRTYDDKManager(base.Get(), config)
{
    BaseHolder.Reset(base);
}

TRTYDDKManager::~TRTYDDKManager() {
}

bool TRTYDDKManager::CheckDeadLine(ui32 docId, ui32 currentTimeMinutes) const {
    const ui32 deadLine = GetDeadlineIfEnabled(docId);
    return deadLine ? (currentTimeMinutes < deadLine) : true;
}

void TRTYDDKManager::MarkForDelete(ui32 docId, ui32 marker) const {
    Update(docId, [marker](TBasicFactorStorage& data) {
        data[NRTYServer::NDDK::SourceWithNewVersionIndex] = BitCast<float>(marker);
    });
}

void TRTYDDKManager::UpdateVersion(ui32 docId, ui32 version) const {
    Update(docId, [version](TBasicFactorStorage& data) {
        data[NRTYServer::NDDK::VersionIndex] = BitCast<float>(version);
    });
}

void TRTYDDKManager::UpdateTimestamp(ui32 docId, ui32 timestamp) const {
    Update(docId, [timestamp](TBasicFactorStorage& data) {
        data[NRTYServer::NDDK::TimestampIndex] = BitCast<float>(timestamp);
    });
}

void TRTYDDKManager::UpdateVersionWithTimestamp(ui32 docId, ui32 version, ui32 timestamp) const {
    Update(docId, [version, timestamp](TBasicFactorStorage& data) {
        data[NRTYServer::NDDK::TimestampIndex] = BitCast<float>(timestamp);
        data[NRTYServer::NDDK::VersionIndex] = BitCast<float>(version);
    });
}

ui32 TRTYDDKManager::GetSourceWithNewVersion(ui32 docId) const {
    return GetField<ui32, NRTYServer::NDDK::SourceWithNewVersionIndex>(docId);
}

ui32 TRTYDDKManager::GetVersion(ui32 docId) const {
    return GetField<ui32, NRTYServer::NDDK::VersionIndex>(docId);
}

ui32 TRTYDDKManager::GetTimeLiveStart(ui32 docId) const {
    return GetField<ui32, NRTYServer::NDDK::TimestampIndex>(docId);
}

ui32 TRTYDDKManager::GetParsedEntitiesHash(ui32 docId) const {
    return GetField<ui32, NRTYServer::NDDK::ParsedEntitiesHashIndex>(docId);
}

ui16 TRTYDDKManager::GetStreamId(ui32 docId) const {
    return GetField<NRTYServer::NDDK::StreamIdIndex>(docId);
}

ui64 TRTYDDKManager::GetUrlId(ui32 docId) const {
    Y_ASSERT(UseUrlIdHash);
    if (Y_LIKELY(!IsStatic)) {
        return GenerateUrlIdHash(docId);
    } else if (Y_UNLIKELY(docId == Max<ui32>())) {
        return docId;
    } else {
        Y_ASSERT(docId < UrlIdHashes.size());
        return UrlIdHashes[docId];
    }
}

TDocSearchInfo::THash TRTYDDKManager::GetIdentifier(ui32 docId) const {
    return GetField<TDocSearchInfo::THash, NRTYServer::NDDK::HashIndex>(docId);
}

namespace {
    template <size_t Size>
    class TStackPool {
    public:
        void* Allocate(size_t size) {
            void* result = Array.begin() + Pos;
            Y_ASSERT(Pos + size < Array.size());
            Pos += size;
            return result;
        }

    private:
        std::array<ui8, Size> Array;
        size_t Pos = 0;
    };

    using TFactorStackPool = TStackPool<
        sizeof(float) * NRTYServer::NDDK::KeysCount +
        sizeof(NFactorSlices::TFactorDomain) +
        /*TGeneralResizeableFactorStorage::TInPoolStorageAlloc*/64
    >;
}

ui32 TRTYDDKManager::GetDeadlineIfEnabled(ui32 docId) const {
    return IsDeadlineEnabled() ? GetDeadline(docId) : 0;
}

ui32 TRTYDDKManager::GetDeadline(ui32 docId) const {
    TFactorStackPool pool;
    TBasicFactorStorage data(NRTYServer::NDDK::KeysCount, &pool);
    if (!Base || !Base->ReadRaw(data, docId))
        return 0;

    ui32 deadline = GetFieldFromStorage<ui32, NRTYServer::NDDK::DeadlineIndex>(data);

    const ui32 lifetime = Config.GetDefaultLifetimeMinutes();
    if (!deadline && lifetime) {
        const ui32 timeLiveStart = GetFieldFromStorage<ui32, NRTYServer::NDDK::TimestampIndex>(data);
        deadline = TDuration::Seconds(timeLiveStart).Minutes() + lifetime;
    }

    const i32 lifetimeOffset = Config.GetLifetimeMinutesOffset();
    if (deadline && lifetimeOffset) {
        deadline += lifetimeOffset;
    }

    return deadline;
}

template <class T, size_t Index>
T TRTYDDKManager::GetField(ui32 docId, T defaultValue /*= 0*/) const {
    TFactorStackPool pool;
    TBasicFactorStorage data(NRTYServer::NDDK::KeysCount, &pool);
    if (!Base || !Base->ReadRaw(data, docId))
        return defaultValue;
    return GetFieldFromStorage<T, Index>(data);
}

template <size_t Index>
ui16 TRTYDDKManager::GetField(ui32 docId, ui16 defaultValue /*= 0*/) const {
    TFactorStackPool pool;
    TBasicFactorStorage data(NRTYServer::NDDK::KeysCount, &pool);
    if (!Base || !Base->ReadRaw(data, docId))
        return defaultValue;

    return data[Index];
}

template <class F>
void TRTYDDKManager::Update(ui32 docId, F function) const {
    TGuard<TMutex> guard(PatchMutex[docId % PatchMutexCount]);
    TFactorStackPool pool;
    TBasicFactorStorage data(NRTYServer::NDDK::KeysCount, &pool);
    if (!Base || !Base->ReadRaw(data, docId))
        return;

    function(data);
    Base->Write(data, docId);
}

bool TRTYDDKManager::DoOpen() {
    Y_ASSERT(Base);
    if (!Base->Open()) {
        return false;
    }
    if (UseUrlIdHash && IsStatic) {
        UrlIdHashes.resize(GetDocumentsCount(), 0);
        for (ui32 i = 0; i < UrlIdHashes.size(); ++i) {
            UrlIdHashes[i] = GenerateUrlIdHash(i);
        }
    }
    return true;
}

bool TRTYDDKManager::DoClose() {
    Y_ASSERT(Base);
    return Base->Close();
}

ui32 TRTYDDKManager::GetDocumentsCount() const {
    Y_ASSERT(Base);
    return Base->GetDocumentsCount();
}

void TRTYDDKManager::InitInteractions(const NRTYServer::IIndexManagersStorage& storage) {
    Y_ASSERT(Base);
    Base->InitInteractions(storage);
}

bool TRTYDDKManager::GetDocInfo(const ui32 docId, NJson::TJsonValue& result) const {
    result.InsertValue("Version", GetVersion(docId));
    result.InsertValue("Deadline", GetDeadline(docId));
    result.InsertValue("Timestamp", GetTimeLiveStart(docId));
    result.InsertValue("StreamId", GetStreamId(docId));
    result.InsertValue("Identifier", GetIdentifier(docId).Quote());
    return true;
}

bool TRTYDDKManager::UpdateDoc(ui32 docId, const TParsedDocument::TPtr doc) {
    const TRTYDDKParsedEntity* patch = doc->GetComponentEntity<TRTYDDKParsedEntity>(ComponentName);
    if (Y_UNLIKELY(!patch))
        return false;
    Update(docId, [patch](TBasicFactorStorage& data) {
        if (patch->HasDeadlineMinutesUTC()) {
            data[NRTYServer::NDDK::DeadlineIndex] = BitCast<float>(patch->GetDeadlineMinutesUTC());
        }
        if (patch->GetTimestampUTC()) {
            data[NRTYServer::NDDK::TimestampIndex] = BitCast<float>(patch->GetTimestampUTC());
        }
        if (patch->GetVersion()) {
            data[NRTYServer::NDDK::VersionIndex] = BitCast<float>(patch->GetVersion());
        }
        if (patch->GetStreamId()) {
            data[NRTYServer::NDDK::StreamIdIndex] = BitCast<float>(patch->GetStreamId());
        }
    });
    return true;
}

ui32 TRTYDDKManager::MarkDocIdsForDeleteUnsafe(const TVector<ui32>& docids, ui32 marker) {
    Y_ASSERT(Base);
    for (auto i : docids) {
        MarkForDelete(i, marker);
    }
    return docids.size();
}

ERTYSearchResult TRTYDDKManager::DoSearch(const TRTYSearchRequestContext& context, ICustomReportBuilder& reportBuilder, const IIndexController& controller) const {
    const auto& cgi = context.CgiParams();
    const TString& kps = cgi.Get("sgkps");

    TStackVec<ui64, 8> prefixes;
    if (!kps.empty()) {
        StringSplitter(kps).Split(',').ParseInto(&prefixes);
    } else {
        prefixes.reserve(1);
        prefixes.push_back(0);
    }

    TVector<TDocSearchInfo> searchInfos;
    if (cgi.Has("saas_no_text_split")) {
        for (const auto& text: cgi.Range("text")) {
            for (ui64 prefix : prefixes) {
                if (!text.empty()) {
                    searchInfos.emplace_back(text, prefix);
                }
            }
        }
    } else {
        const TString& text = cgi.Get("text");
        for (const auto key : StringSplitter(text).Split(',').SkipEmpty()) {
            const TString keyStr = TString(key.Token());
            for (ui64 prefix : prefixes) {
                searchInfos.emplace_back(keyStr, prefix);
            }
        }
    }

    TVector<TDocIdCandidate> docids;
    if (!controller.RemapUrls2DocIdCandidates(searchInfos, docids)) {
        return SR_NOT_FOUND;
    }

    ui32 count = 0;
    const TGtaFilter gta(cgi);

    for (ui32 i = 0; i < docids.size(); ++i) {
        TDocIdCandidate docIdCandidate = docids[i];
        if (!docIdCandidate.IsDocIdSet() || controller.IsRemoved(docIdCandidate.GetDocId())) {
            continue;
        }
        ui32 docId = docIdCandidate.GetDocId();
        if (!docIdCandidate.IsVerified() && GetIdentifier(docId) != docIdCandidate.GetHash()) {
            continue;
        }
        const TDocSearchInfo& si = searchInfos[i];
        NMetaProtocol::TDocument doc;
        doc.SetDocId(ToString(docId));
        doc.SetUrl(si.GetUrl());
        doc.MutableArchiveInfo()->SetTitle(si.GetUrl());
        TSearchProtoHelper helper(doc);

        //Note(yrum@): the GTAs below is not available on the standard search results - you have to ask with &component=DDK to see them
        if (gta.NeedTimestamp()) {
            helper.AddProperty("_Timestamp", GetTimeLiveStart(docId));
        }
        if (gta.NeedVersion()) {
            helper.AddProperty("_Version", GetVersion(docId));
        }
        if (gta.NeedDeadline()) {
            helper.AddProperty("_Deadline", GetDeadline(docId));
        }

        reportBuilder.AddDocument(doc);
        ++count;
    }
    return count ? SR_OK : SR_NOT_FOUND;

}

void TRTYDDKManager::GetExportedFunctions(NRTYFeatures::TImportedFunctionsBuilder& exports) const {
    // Below is another set of GTAs, which may be exported to the ExternalSearch.
    // &fsgta=_DDK_Timestamp , &fsgta=_DDK_Version are enabled by the "user_functions" section in relev.conf
    using T = TRTYDDKManager;
    exports.AddGta(&T::GetTimeLiveStart, this, "DDK_Timestamp");
    exports.AddGta(&T::GetVersion, this, "DDK_Version");
    exports.AddGta(&T::GetDeadline, this, "DDK_Deadline");
}


ui32 TRTYDDKManager::RemoveDocids(const TVector<ui32>& docids) {
    Y_ASSERT(Base);
    return Base->RemoveDocids(docids);
}

bool TRTYDDKManager::RemapRequired() const {
    Y_ASSERT(Base);
    return Base->RemapRequired();
}

bool TRTYDDKManager::IsDeadlineEnabled() const {
    return Config.GetEnableLifetimeCheck();
}

bool TRTYDDKManager::IsReadOnly() const {
    return false;
}

ui64 TRTYDDKManager::GenerateUrlIdHash(const TDocSearchInfo::THash& hash) const {
    return hash.Head<ui64>();
}

ui64 TRTYDDKManager::GenerateUrlIdHash(ui32 docId) const {
    return GenerateUrlIdHash(GetIdentifier(docId));
}

ui64 TRTYDDKManager::GenerateUrlIdHash(const TDocSearchInfo& searchInfo) const {
    return GenerateUrlIdHash(searchInfo.GetHash());
}
