#include "parsed_document.h"
#include "abstract_model.h"
#include "index_component_storage.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/layers.h>
#include <saas/rtyserver/config/shards_config.h>
#include <saas/rtyserver/common/sharding.h>

#include <util/digest/fnv.h>

// TParsedEntity
TParsedDocument::TParsedEntity::TParsedEntity(TConstructParams& params)
    : Component(params.Component)
    , Owner(params.Owner)
{}

void TParsedDocument::TParsedEntity::MergeToProto(NRTYServer::TParsedDoc& /*pd*/, const NRTYServer::TDocSerializeContext& /*context*/) const {
}

void TParsedDocument::TParsedEntity::ApplyPatch(const TParsedDocument& doc) {
    DoApplyPatch(doc);
}

// TParsedDocument
TParsedDocument::TParsedDocument(const TRTYServerConfig& config)
    : Config(config)
    , DocInfo("", 0)
{
    TIndexComponentsStorage::Instance().CreateParsedEntities(*this);
}

void TParsedDocument::TProtectedFieldsSetter::Clear(TParsedDocument& document) {
    document.NeedRestore = false;
    document.QueryDel.clear();
}

void TParsedDocument::CreateParsedEntity(const NRTYServer::IIndexComponent* component) {
    NRTYServer::IParsedEntity::TConstructParams params(*component, *this);
    NRTYServer::IParsedEntity::TPtr entity(component->BuildParsedEntity(params));
    if (!entity)
        entity.Reset(new TParsedEntity(params));
    ComponentsEntitiesStorage[component->GetName()] = entity;
}

ui32 TParsedDocument::CalcEntitiesFullHash() const {
    NRTYServer::TParsedDoc parsedDoc;
    TVector<ui32> hashes;
    for (TComponentsEntitiesStorage::const_iterator i = ComponentsEntitiesStorage.begin(); i != ComponentsEntitiesStorage.end(); ++i) {
        const NRTYServer::IIndexComponent* component = TIndexComponentsStorage::Instance().GetComponent(i->first);
        Y_ENSURE(component);
        ui32 entityHash = i->second->GetHash();
        if (entityHash != 0) {
            hashes.push_back(entityHash);
        }
    }
    return FnvHash<ui32>(hashes.data(), hashes.size() * sizeof(ui32));
}

bool TParsedDocument::IsEmpty() const {
    auto* entity = GetComponentEntity<TParsedDocument::TParsedEntity>(Config.IndexGenerator);
    CHECK_WITH_LOG(entity);
    return entity->IsEmpty();
}

void TParsedDocument::MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const {
    pd.MutableDocument()->SetRealtime(Realtime);
    pd.MutableDocument()->SetVersion(Version);
    pd.MutableDocument()->SetStreamId(StreamId);
    pd.MutableDocument()->SetModificationTimestamp(SafeIntegerCast<ui64>(Timestamp));
    pd.MutableDocument()->SetVersionTimestamp(VersionTimestamp);
    pd.MutableDocument()->SetKeyPrefix(DocInfo.GetKeyPrefix());
    pd.MutableDocument()->SetUrl(DocInfo.GetUrl());
    pd.MutableDocument()->SetUpdateType(UpdateType);

    if (Position) {
        auto p = pd.MutableDocument()->MutablePosition();
        p->SetKey(Position->GetKey());
        p->SetValue(Position->GetValue());
    }

    for (auto&& position : ExtraPositions) {
        auto p = pd.MutableDocument()->AddExtraPositions();
        p->SetKey(position.GetKey());
        p->SetValue(position.GetValue());
    }

    for (auto&& timestamp : ExtraTimestamps) {
        const NRTYServer::TStreamId stream = timestamp.first;
        const NRTYServer::TTimestampValue value = timestamp.second;

        auto p = pd.MutableDocument()->AddTimestamps();
        p->SetStream(stream);
        p->SetValue(GetLow(value));
        p->SetValueEx(GetHigh(value));
    }

    pd.SetIsUpdate(IsUpdateFlag);

    if (context.GetLayer() != NRTYServer::NFullArchive::BaseLayer) {
        for (const auto& [_, entity]: ComponentsEntitiesStorage) {
            entity->MergeToProto(pd, context);
        }
    }
}

bool TParsedDocument::FillFromProto(const NRTYServer::TParsedDoc& pd, NRTYServer::TMessage::TMessageType command, const NRTYServer::TDocParseContext& context) {
    IsUpdateFlag = pd.GetIsUpdate();
    TIndexComponentsStorage::Instance().GetDocumentParser().Parse(*this, pd.GetDocument(), command, context);
    for (auto [_, entity]: ComponentsEntitiesStorage) {
        if (!entity->FillFromProto(pd, context)) {
            return false;
        }
    }
    return true;
}

void TParsedDocument::SetDocSearchInfo(const TDocSearchInfo& docInfo) {
    DocInfo = docInfo;
    for (TComponentsEntitiesStorage::iterator i = ComponentsEntitiesStorage.begin(); i != ComponentsEntitiesStorage.end(); ++i)
        i->second->SetDocSearchInfo(DocInfo);
}

void TParsedDocument::SetTempDiskInfo(const TString& indexName) {
    DocInfo.SetIndexName(indexName);
}

void TParsedDocument::SetTempDiskInfo(ui32 docid) {
    DocInfo.SetDocId(docid);
    for (TComponentsEntitiesStorage::iterator i = ComponentsEntitiesStorage.begin(); i != ComponentsEntitiesStorage.end(); ++i)
        i->second->SetDocId(docid);
}

bool TParsedDocument::GetGroupAttrValue(const TString& name, i64& result) const {
    for (TComponentsEntitiesStorage::const_iterator i = ComponentsEntitiesStorage.begin(); i != ComponentsEntitiesStorage.end(); ++i)
        if (i->second->GetGroupAttrValue(name, result))
            return true;
    return false;
}

void TParsedDocument::FillFactorStorage(TFactorView& factorStorage) const {
    for (TComponentsEntitiesStorage::const_iterator i = ComponentsEntitiesStorage.begin(); i != ComponentsEntitiesStorage.end(); ++i)
        if (i->second->FillFactorStorage(factorStorage))
            return;
}

const TString& TParsedDocument::GetUrl() const {
    return DocInfo.GetUrl();
}

ui64 TParsedDocument::GetKeyPrefix() const {
    return DocInfo.GetKeyPrefix();
}

void TParsedDocument::ApplyPatch(const TParsedDocument& doc) {
    NeedRestore = true;

    MergeTimestamp(doc.GetStreamId(), doc.GetTimestamp());
    MergeExtraTimestamps(doc.GetExtraTimestamps());

    for (auto& component : ComponentsEntitiesStorage)
        component.second->ApplyPatch(doc);
}

NRTYServer::TShard TParsedDocument::GetShard() const {
    return NRTYServer::GetShard(DocInfo.GetKeyPrefix(), Config.GetShardsConfig().Number);
}

void TParsedDocument::MergeTimestamp(const NRTYServer::TStreamId stream, const NRTYServer::TTimestampValue value) {
    if (GetStreamId() != stream) {
        UpdateExtraTimestamp(GetStreamId(), GetTimestamp());
        StreamId = stream;
    }
    Timestamp = Max<NRTYServer::TTimestampValue>(GetTimestamp(), value);
}

void TParsedDocument::MergeExtraTimestamps(const TTimestamps& other) {
    for (auto&& timestamp : other) {
        UpdateExtraTimestamp(timestamp.first, timestamp.second);
    }
}

void TParsedDocument::UpdateExtraTimestamp(const NRTYServer::TStreamId stream, const NRTYServer::TTimestampValue value) {
    ExtraTimestamps[stream] = Max(ExtraTimestamps[stream], value);
}

