#include "builder.h"
#include "config.h"
#include "disk_manager.h"
#include "layers_normalizer.h"

#include <saas/rtyserver/config/layers.h>

#include <util/string/join.h>

namespace {
    class TParsedDocumentPatcher : public TParsedDocument::TProtectedFieldsSetter {
    private:
        using TBase = TParsedDocument::TProtectedFieldsSetter;
    public:
        TParsedDocumentPatcher(TParsedDocument& document)
            : Document(document)
        {
        }

        void SetVersion(ui64 value) {
            TBase::SetVersion(Document, value);
        }
        void SetStreamId(NRTYServer::TStreamId stream) {
            TBase::SetStreamId(Document, stream);
        }
        void SetTimestamp(NRTYServer::TTimestampValue timestamp) {
            TBase::SetTimestamp(Document, timestamp);
        }

    private:
        TParsedDocument& Document;
    };
}

TArchiveLayersNormalizer::TArchiveLayersNormalizer(const TRTYServerConfig& config, const TRTYFullArchiveConfig& componentConfig)
    : NRTYServer::INormalizer(config)
    , ComponentConfig(componentConfig)
{
}

const char* TArchiveLayersNormalizer::Name() const {
    return "FALayersNormalizer";
}

bool TArchiveLayersNormalizer::AllRight(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const auto& missingLayers = GetMissingLayers(context);
    if (missingLayers.empty()) {
        return true;
    } else {
        WARNING_LOG << "Missing " << JoinStrings(missingLayers.begin(), missingLayers.end(), ",") << " layers in " << context.Dir.BaseName() << Endl;
        return false;
    }
}

void TArchiveLayersNormalizer::Fix(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const TSet<TString> presentLayers = GetPresentLayers(context);
    if (TDiskFAManager::ExistsLayer(context.Dir.PathName(), NRTYServer::NFullArchive::FullLayer)
        && presentLayers.contains(NRTYServer::NFullArchive::FullLayer))
    {
        RestoreInternal(context);
    } else {
        const auto& missingLayers = GetMissingLayers(context);
        AssertCorrectConfig(
            !missingLayers.contains(NRTYServer::NFullArchive::FullLayer),
            "Cannot restore the missing Full layer (it is impossible)"
        );

        if (presentLayers.empty()) {
            RestoreExternal(context);
        } else {
            AssertCorrectConfig(
                presentLayers.contains(NRTYServer::NFullArchive::FullLayer) || presentLayers.contains(NRTYServer::NFullArchive::BaseLayer),
                "Cannot restore FullArchive without either Full layer or Base layer to read from"
            );
            RestoreInternal(context);
        }
    }
}

void TArchiveLayersNormalizer::RestoreExternal(const NRTYServer::TNormalizerContext& context) const {
    //
    // RestoreExternal() creates FullArchive anew with the data fetched from DocSearchInfoIterator and DDK manager
    //
    const auto& missingLayers = GetMissingLayers(context);
    const TString missingLayersStr = missingLayers.size() == 1 && missingLayers.contains(NRTYServer::NFullArchive::BaseLayer) ?
        "Base layer" : "layers " + JoinSeq(",", missingLayers);
    AssertCorrectConfig(context.Managers.HasIndexManager(), "Cannot restore FullArchive %s without IndexManager", missingLayersStr.c_str());
    const auto iterator = context.Managers.GetIndexManager()->GetDocSearchInfoIterator();
    const auto ddk = context.Managers.GetDDKManager();
    const ui32 size = context.Managers.GetDocumentsCount();
    AssertCorrectConfig(!!iterator, "Cannot restore FullArchive %s without DocSearchInfoIterator", missingLayersStr.c_str());
    AssertCorrectConfig(ddk, "Cannot restore FullArchive %s without DDK", missingLayersStr.c_str());

    INFO_LOG << "Starting external restore of FullArchive " << missingLayersStr << " for " << context.Dir.BaseName() << Endl;
    RemoveOldLayers(context, { NRTYServer::NFullArchive::BaseLayer });
    TDiskFAManager manager(context.Dir.PathName(), size, Config, 0, missingLayers, false, false, false);
    TRTYFullArchiveBuilder builder(&manager, FULL_ARCHIVE_COMPONENT_NAME);
    NRTYServer::TManagerGuard guard(manager);
    CHECK_WITH_LOG(builder.Start());

    INFO_LOG << "Starting iteration over " << context.Dir.BaseName() << Endl;
    for (; iterator->IsValid(); iterator->Next()) {
        if (iterator->IsDeleted()) {
            continue;
        }

        TParsedDocument document(Config);
        TParsedDocumentPatcher patcher(document);

        const TDocSearchInfo& docSearchInfo = iterator->Get();
        document.SetDocSearchInfo(docSearchInfo);

        const ui32 docId = docSearchInfo.GetDocId();
        patcher.SetStreamId(ddk->GetStreamId(docId));
        patcher.SetTimestamp(ddk->GetTimeLiveStart(docId));
        patcher.SetVersion(ddk->GetVersion(docId));

        builder.Index(/*threadID=*/0, document, docId);
    }
    INFO_LOG << "Finished iteration over " << context.Dir.BaseName() << Endl;

    CHECK_WITH_LOG(builder.Stop());
    CHECK_WITH_LOG(builder.Close(GetCloseContext(context, size)));
    INFO_LOG << "Finished external restore of FullArchive " << missingLayersStr << " for " << context.Dir.BaseName() << Endl;
}

void TArchiveLayersNormalizer::RestoreInternal(const NRTYServer::TNormalizerContext& context) const {
    //
    // RestoreInternal() rebuilds the missing FullArc layers (other than "full") from the full layer.
    // If only the "base" layer is present, and the "full" layer is disabled, RestoreInternal() reads from "base"
    // and fills the missing "special" layers with almost-empty docs.
    //
    INFO_LOG << "Starting internal FullArchive restore for " << context.Dir.BaseName() << Endl;

    // open FullArc (and ensure that it opens)
    TDiskFAManager source(context.Dir.PathName(), 0, Config, 0, GetPresentLayers(context), true, false, false);
    if (!source.Open()) {
        AssertCorrectIndex(source.Repair(), "FullArchive restore failed");
        AssertCorrectIndex(source.Open(), "FullArchive open after restore failed");
    }
    CHECK_WITH_LOG(source.IsOpened());
    const ui32 size = source.GetDocumentsCount();

    // check preconditions of RestoreInternal() method (should have been checked before the call)
    const auto present = GetPresentLayers(context);
    const auto missing = GetMissingLayers(context);
    const TString srcLayer = present.contains(NRTYServer::NFullArchive::FullLayer) ? NRTYServer::NFullArchive::FullLayer : NRTYServer::NFullArchive::BaseLayer;
    CHECK_WITH_LOG(present.contains(srcLayer));
    CHECK_WITH_LOG(!missing.contains(NRTYServer::NFullArchive::FullLayer));

    RemoveOldLayers(context, missing);
    TDiskFAManager manager(context.Dir.PathName(), size, Config, 0, missing, false, false, false);
    TRTYFullArchiveBuilder builder(&manager, FULL_ARCHIVE_COMPONENT_NAME);
    NRTYServer::TManagerGuard guard(manager);
    CHECK_WITH_LOG(builder.Start());

    const bool onlyPropIsMissing = missing.size() == 1 && *missing.begin() == "PROP";

    // Don't try to actually restore PROP, its normalizer will do the job for us.
    // NB(eeight) Cannot include components/prop/global.h here because that would bring a cyclic dependency,
    // so just hardcode component name here.
    if (!onlyPropIsMissing) {
        INFO_LOG << "Starting iteration over FullArchive " << srcLayer << " layer for " << context.Dir.BaseName() << Endl;
        for (auto iterator = source.CreateIterator(srcLayer); iterator->IsValid(); iterator->Next()) {
            const ui32 docId = iterator->GetDocId();
            const auto document = iterator->GetDocument();

            CHECK_WITH_LOG(document);
            builder.Index(/*threadID=*/0, *document, docId);
        }
        INFO_LOG << "Finished iteration over FullArchive " << srcLayer << " layer for " << context.Dir.BaseName() << Endl;
    }

    CHECK_WITH_LOG(builder.Stop());
    CHECK_WITH_LOG(builder.Close(GetCloseContext(context, size)));
    CHECK_WITH_LOG(source.Close());
    INFO_LOG << "Finished internal FullArchive restore for " << context.Dir.BaseName() << Endl;
}

NRTYServer::TBuilderCloseContext TArchiveLayersNormalizer::GetCloseContext(const NRTYServer::TNormalizerContext& context, ui32 docCount) const {
    RemapTable.resize(docCount);
    for (ui32 i = 0; i < RemapTable.size(); ++i) {
        RemapTable[i] = i;
    }
    return NRTYServer::TBuilderCloseContext(context.Dir, context.Dir, &RemapTable, nullptr);
}

TSet<TString> TArchiveLayersNormalizer::GetMissingLayers(const NRTYServer::TNormalizerContext& context) const {
    TSet<TString> result;
    for (auto&& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (!TDiskFAManager::ExistsLayer(context.Dir.PathName(), layer)) {
            result.insert(layer);
        }
    }
    return result;
}

TSet<TString> TArchiveLayersNormalizer::GetPresentLayers(const NRTYServer::TNormalizerContext& context) const {
    TSet<TString> result;
    for (auto&& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (TDiskFAManager::ExistsLayer(context.Dir.PathName(), layer)) {
            result.insert(layer);
        }
    }
    return result;
}

void TArchiveLayersNormalizer::RemoveOldLayers(const NRTYServer::TNormalizerContext& context, const TSet<TString>& layers) const {
    for (auto&& layer : layers) {
        INFO_LOG << "Removing debris of old FullArchive " << layer << " layer in " << context.Dir.BaseName() << Endl;
        TDiskFAManager::RemoveLayer(context.Dir.PathName(), layer);
    }
}
