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

#include <saas/rtyserver/factors/factors_abstract.h>
#include <saas/rtyserver/factors/factor.h>
#include <saas/rtyserver/indexer_core/document_parser.h>

#include <util/digest/fnv.h>

TRTYDDKComponentParser::TRTYDDKComponentParser(const TDDKComponentConfig& config)
    : ComponentConfig(config)
    , StreamLimit((1 << GetDDKFields().GetFactor(NRTYServer::NDDK::StreamIdIndex).Width) - 1)
{
}

void TRTYDDKComponentParser::Parse(TParsingContext& context) const {
    auto result = context.Result.GetComponentEntity<TRTYDDKParsedEntity>(DDK_COMPONENT_NAME);
    VERIFY_WITH_LOG(result, "DDK entity does not exist");

    if (context.Document.HasDeadlineMinutesUTC()) {
        result->SetDeadlineMinutesUTC(context.Document.GetDeadlineMinutesUTC());
    }

    if (!context.Document.HasModificationTimestamp()) {
        ythrow yexception() << "document has no timestamp";
    }

    const ui32 timestamp = context.Document.GetModificationTimestamp();
    const ui32 current = Seconds();
    if (timestamp > current && ComponentConfig.GetValidateTimestamp()) {
        throw yexception() << "incorrect timestamp " << timestamp << ": server timestamp " << current;
    }

    const ui32 stream = context.Document.GetStreamId();
    if (stream > StreamLimit) {
        throw yexception() << "stream id exceeds the maximum of " << StreamLimit;
    }

    const ui64 version = context.Document.GetVersion();
    if (version > Max<ui32>()) {
        throw yexception() << "version exceeds maximum of " << Max<ui32>();
    }

    result->SetTimestampUTC(timestamp);
    result->SetVersion(static_cast<ui32>(version));
    result->SetSize(context.Document.ByteSize());
    result->SetStreamId(stream);

    DEBUG_LOG << context.Result.GetDocSearchInfo().GetUrl()
              << " version: " << result->GetVersion()
              << " timestamp: " << result->GetTimestampUTC()
              << " deadline: " << (result->GetDeadlineMinutesUTC() ? ToString(result->GetDeadlineMinutesUTC()) : "NODEADLINE") << Endl;

    if (ComponentConfig.GetEnableLifetimeCheck()) {
        if (context.Document.HasDeadlineMinutesUTC()) {
            const ui64 deadline = TDuration::Minutes(context.Document.GetDeadlineMinutesUTC()).Seconds();
            const ui64 offset = TDuration::Minutes(ComponentConfig.GetLifetimeMinutesOffset()).Seconds();
            if (deadline && (deadline + offset) < current) {
                context.Result.SignalDeprecated();
            }
        }
        else {
            const ui64 lifetime = TDuration::Minutes(ComponentConfig.GetDefaultLifetimeMinutes()).Seconds();
            const bool isUpdate = context.Command == NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT;
            const bool updateWithZeroTs = isUpdate && timestamp == 0;
            if (!updateWithZeroTs && lifetime && (timestamp + lifetime) < current) {
                context.Result.SignalDeprecated();
            }
        }
    }
}

TRTYDDKParsedEntity::TRTYDDKParsedEntity(TConstructParams& params)
    : TParsedDocument::TParsedEntity(params)
    , TimestampUTC(0)
    , Version(0)
    , Size(0)
    , StreamId(0)
{}

ui32 TRTYDDKParsedEntity::GetHash() const {
    if (DeadlineMinutesUTC.Defined()) {
        ui32 values[2];
        values[0] = Version;
        values[1] = DeadlineMinutesUTC.GetRef();
        return FnvHash<ui32>(values, 2 * sizeof(ui32));
    }
    return FnvHash<ui32>(&Version, sizeof(ui32));
}

void TRTYDDKParsedEntity::MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const {
    TParsedEntity::MergeToProto(pd, context);
    if (DeadlineMinutesUTC.Defined()) {
        pd.MutableDocument()->SetDeadlineMinutesUTC(DeadlineMinutesUTC.GetRef());
    }
    pd.MutableDocument()->SetModificationTimestamp(TimestampUTC);
    pd.MutableDocument()->SetVersion(Version);
}

void TRTYDDKParsedEntity::DoApplyPatch(const TParsedDocument& doc) {
    const TRTYDDKParsedEntity* patch = doc.GetComponentEntity<TRTYDDKParsedEntity>(DDK_COMPONENT_NAME);
    if (!patch)
        return;
    if (patch->HasDeadlineMinutesUTC()) {
        DeadlineMinutesUTC = patch->DeadlineMinutesUTC;
    }
    if (patch->TimestampUTC)
        TimestampUTC = patch->TimestampUTC;
    if (patch->Version)
        Version = patch->Version;
    if (patch->StreamId)
        StreamId = patch->StreamId;
}
