#include "common_action.h"
#include "metrics.h"

#include <saas/rtyserver/indexer_core/guarded_document.h>
#include <saas/rtyserver/indexer_server/indexers_list.h>
#include <saas/rtyserver/logging/rty_index.h>

#include <util/system/guard.h>

void TCommonAction::DoComplete() {
    DEBUG_LOG << "OK ProcessUpdateAndDelete(document) id=" << Document->GetMessageId() << ";url=" << Document->GetDocument().GetDocSearchInfo().GetUrl() << Endl;
    CHECK_WITH_LOG(!Guard);
    TReadShardIndexGuard gShard(*IndexersThread->IndexersList, Shard);

    if (IndexerMetrics) {
        if (Document->GetStatus() == NRTYServer::TReply::INCORRECT_DOCUMENT) {
            IndexerMetrics->DocumentFail.Inc();
        } else {
            switch (Document->GetCommand()) {
            case NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT:
            case NRTYServer::TMessage::MODIFY_DOCUMENT:
                IndexerMetrics->DocumentUpdate.Inc();
                break;
            case NRTYServer::TMessage::DELETE_DOCUMENT:
                IndexerMetrics->DocumentDelete.Inc();
                break;
            default:
                break;
            }
        }
    }
}

void TCommonAction::LockIndexer() {
    if (!Guard && !Indexer) {
        Guard.Reset(new TReadGuard(IndexersThread->IndexersList->GetMutex(Shard)));
        Indexer = IndexersThread->GetIndexer(Shard);
    } else {
        CHECK_WITH_LOG(!!Guard && !!Indexer);
    }
}

void TCommonAction::UnLockIndexer() {
    if (!!Guard && !!Indexer) {
        Guard.Reset(nullptr);
        Indexer.Reset(nullptr);
    } else {
        CHECK_WITH_LOG(!Guard && !Indexer);
    }
}

void TCommonAction::Initialize(TQueuedDocument document, TIndexersThread& indexersThread)
{
    IndexersThread = &indexersThread;
    IndexerMetrics = IndexersThread->GetIndexerMetrics();
    Document = document;
    DEBUG_LOG << "Start document id=" << document->GetMessageId() << ";url=" << document->GetDocument().GetDocSearchInfo().GetUrl() << " processing. Index is not determined yet." << Endl;
    Shard = document->GetDocument().GetShard();
}

void TCommonAction::DoIndexDocument() {
    CHECK_WITH_LOG(!!Indexer);
    Indexer->Index(Document, IndexersThread->GetThreadID(), IndexerMetrics, IndexersThread->GetServiceName(), ModifyResult);
}

void TCommonAction::UpdateTimestampsOnDelete() {
    CHECK_WITH_LOG(!!Indexer);
    const ui32 docIdent = Document->GetMessageId();
    const TString docUrl = Document->GetDocument().GetDocSearchInfo().GetUrl();
    try {
        if (Document->IsEmpty()) {
            return;
        }
        Indexer->UpdateTimestampsOnDelete(Document, IndexersThread->GetThreadID());
    } catch (...) {
        ERROR_LOG << "Exception while document id=" << docIdent << ";url=" << docUrl << " that updating timestamps for " << GetFirstIndexerName() << " (" << CurrentExceptionMessage() << ")" << Endl;
    }
}

void TCommonAction::DoOutdated() {
    const TParsedDocument& doc = Document->GetDocument();
    const ui64 version = doc.GetVersion();
    const ui64 timestamp = doc.GetTimestamp();

    DEBUG_LOG << "Obsolete document id=" << Document->GetMessageId() << ";url=" << doc.GetDocSearchInfo().GetUrl()
        << " with version " << version << "/" << timestamp << " , current doc version is " << ModifyResult.Version << "/" << ModifyResult.Timestamp << Endl;
    QueueDocumentLog(IndexersThread->GetServiceName(), doc, GetFirstIndexerName(), "DEPRECATED");

    Document->SetStatus(NRTYServer::TReply::DEPRECATED,
        "Incoming document is obsolete (" + ToString(version) + "/" + ToString(timestamp) + "), current (" + ToString(ModifyResult.Version) + "/" + ToString(ModifyResult.Timestamp) + ")");
    if (IndexerMetrics) {
        IndexerMetrics->DocumentOutdated.Inc();
    }
}

void TCommonAction::DoDuplicated() {
    const TParsedDocument& doc = Document->GetDocument();
    DEBUG_LOG << "Duplicated document id=" << Document->GetMessageId() << ";url=" << doc.GetDocSearchInfo().GetUrl()
        << " with entities hash " << doc.GetEntitiesHash() << Endl;
    QueueDocumentLog(IndexersThread->GetServiceName(), doc, GetFirstIndexerName(), "DUPLICATED");

    Document->SetStatus(NRTYServer::TReply::OK, "DUPLICATED");
    if (IndexerMetrics) {
        IndexerMetrics->DocumentDuplicated.Inc();
    }
}

void TCommonAction::DoRedundant() {
    Document->SetStatus(NRTYServer::TReply::NOTNOW, "REDUNDANT. Index size limit exceeded");
    if (IndexerMetrics) {
        IndexerMetrics->DocumentRedundant.Inc();
    }
}

class TQueuedDocumentTryLocker {
public:
    static inline bool TryAcquire(TQueuedDocument* t) noexcept {
        return (*t)->TryLockIndexer();
    }

    static inline void Release(TQueuedDocument* t) noexcept {
        (*t)->UnLockIndexers();
    }
};

TString TCommonAction::GetFirstIndexerName() {
    if (!!Indexer) {
        return Indexer->GetFirstIndexerName();
    } else {
        return "";
    }
}

void TCommonAction::Execute(TQueuedDocument document, TIndexersThread& indexersThread) {
    Initialize(document, indexersThread);
    CHECK_WITH_LOG(IndexersThread);
    CHECK_WITH_LOG(!!Document);
    const bool isDelete = document->GetCommand() == NRTYServer::TMessage::DELETE_DOCUMENT;
    try {
        LockIndexer();
        TTryGuard<TQueuedDocument, TQueuedDocumentTryLocker> tgia(Document);
        ModifyResult.Status = TModifyResult::ERROR;
        if (!tgia.WasAcquired()) {
            ERROR_LOG << "Can't lock document " << Document->GetDocument().GetDocSearchInfo().GetUrl() << " for indexation" << Endl;
            QueueDocumentLog(IndexersThread->GetServiceName(), Document->GetDocument(), "indexer", "CANTLOCK");
            Document->SetStatus(NRTYServer::TReply::NOTNOW, "Server locked now for indexation");
            return;
        }
        DoPrepare();
        if (!(ModifyResult.Status == TModifyResult::REINDEX || ModifyResult.Status == TModifyResult::COMPLETED && isDelete)) {
            UnLockIndexer();
        }
    } catch (...) {
        Document->SetStatus(NRTYServer::TReply::INTERNAL_ERROR, CurrentExceptionMessage());
        ERROR_LOG << "Error while processing MODIFY and DELETE, url = " << Document->GetDocument().GetDocSearchInfo().GetUrl()
                  << ": " << CurrentExceptionMessage() << Endl;
        return;
    }

    switch (ModifyResult.Status) {
    case TModifyResult::REINDEX:
        LockIndexer();
        DoIndexDocument();
        break;
    case TModifyResult::COMPLETED:
        if (isDelete) {
            UpdateTimestampsOnDelete();
            UnLockIndexer();
        }
        DoComplete();
        break;
    case TModifyResult::OUTDATED:
        DoOutdated();
        break;
    case TModifyResult::DUPLICATED:
        DoDuplicated();
        break;
    case TModifyResult::REDUNDANT:
        DoRedundant();
        break;
    case TModifyResult::ERROR:
        /* do nothing */
        break;
    default:
        FAIL_LOG("unexpected behaviour");
        break;
    }
}
