#include "component.h"
#include "parsed_entity.h"
#include "builder.h"
#include "manager.h"
#include "config.h"
#include "globals.h"
#include "files_normalizer.h"
#include "data_normalizer.h"
#include "layers_normalizer.h"
#include "name_normalizer.h"
#include "disk_manager.h"
#include "memory_manager.h"
#include "light_layers_registry.h"

#include <saas/rtyserver/indexer_core/index_metadata_processor.h>
#include <saas/rtyserver/indexer_core/index_component_storage.h>
#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/const.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/layers.h>
#include <search/request/data/reqdata.h>

#include <kernel/multipart_archive/config/config.h>

#include <library/cpp/digest/md5/md5.h>

TRTYFullArchive::TRTYFullArchive(const TRTYServerConfig& config)
    : TBaseIndexComponent(config, /* isUsed = */ true)
    , ComponentConfig(*config.ComponentsConfig.Get<TRTYFullArchiveConfig>(FULL_ARCHIVE_COMPONENT_NAME))
    , Normalizers(config)
{
    for (const auto& layer : ComponentConfig.GetActiveLayersFinal()) {
        const auto& layerConf = ComponentConfig.GetLayer(layer);
        TIndexFile::EPrefetchPolicy policy = layerConf.LockFATFile ? TIndexFile::ppLock : TIndexFile::ppPrefetch;
        IndexFiles.insert(TIndexFile(NRTYArchive::GetFatPath(FULL_ARC_FILE_NAME_PREFIX + layer), true, policy));
    }
    IndexFiles.insert(TIndexFile("index.meta", true, TIndexFile::ppDisable));

    Normalizers.Register(new TArchiveNamesNormalizer(config));
    Normalizers.Register(new TArchiveLayersNormalizer(config, ComponentConfig));
    Normalizers.Register(new TArchiveDataNormalizer(config, ComponentConfig));
    Normalizers.Register(new TArchiveFilesNormalizer(config, ComponentConfig));
}

TString TRTYFullArchive::GetName() const {
    return FULL_ARCHIVE_COMPONENT_NAME;
}

THolder<NRTYServer::IIndexComponentBuilder> TRTYFullArchive::CreateBuilder(const NRTYServer::TBuilderConstructionContext& context) const {
    if (context.Config.GetType() == "disk") {
        return MakeHolder<TRTYFullArchiveBuilder>(
            new TDiskFAManager(
                context.TempDir.PathName(),
                context.Config.MaxDocuments,
                context.Config.Common.Owner,
                /*writeSpeedBytes=*/0,
                ComponentConfig.GetActiveLayersTemp(),
                /*enableFastUpdates=*/true,
                /*readOnly=*/false,
                /*final=*/false),
            GetName());
    }
    if (context.Config.GetType() == "memory") {
        return MakeHolder<TRTYFullArchiveBuilder>(new TMemoryFAManager(context.Config.Common.Owner), GetName());
    }
    return nullptr;
}

THolder<NRTYServer::IIndexComponentManager> TRTYFullArchive::CreateManager(const NRTYServer::TManagerConstructionContext& context) const {
    switch (context.IndexType) {
        case IIndexController::PREPARED:
        case IIndexController::FINAL:
            return MakeHolder<TDiskFAManager>(context.Dir.PathName(), 0, Config, 0, ComponentConfig.GetActiveLayersFinal(), false, context.IsReadOnly, true);
        case IIndexController::DISK:
            return MakeHolder<TDiskFAManager>(context.Dir.PathName(), 0, Config, 0, ComponentConfig.GetActiveLayersFinal(), false, context.IsReadOnly, false);
        case IIndexController::MEMORY:
            return nullptr;
        default:
            break;
    }
    FAIL_LOG("invalid IndexType");
}

NRTYServer::IComponentParser::TPtr TRTYFullArchive::BuildParser() const {
    if (Config.IndexGenerator == GetName())
        return new TRTYFullArchiveComponentParser{};
    else
        return nullptr;
}

NRTYServer::IParsedEntity::TPtr TRTYFullArchive::BuildParsedEntity(NRTYServer::IParsedEntity::TConstructParams& params) const {
    if (Config.IndexGenerator == GetName())
        return new TRTYFullArchiveParsedEntity(params);
    else
        return nullptr;
}

bool TRTYFullArchive::HasIndexFiles(const TString& path) const {
    for (const auto& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (TDiskFAManager::ExistsLayer(path, layer)) {
            return true;
        }
    }
    return false;
}

bool TRTYFullArchive::IsEmptyIndex(const TString& path) const {
    ui32 version = TIndexMetadataProcessor(path)->GetFullArcHeader().GetVersion();
    if (version > 0 && version < FULL_ARC_VERSION)
        return false;
    for (const auto& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (!TDiskFAManager::LayerIsEmpty(path, layer))
            return false;
    }
    return true;
}

bool TRTYFullArchive::IsFinalIndex(const TString& path) const {
    for (const auto& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (!TDiskFAManager::ExistsLayer(path, layer)) {
            INFO_LOG << "Layer " << layer << " does not exist in " << path << Endl;
            return false;
        }
    }

    return true;
}

bool TRTYFullArchive::DoMerge(const NRTYServer::TMergeContext& context) const {
    for (const auto& layer : ComponentConfig.GetActiveLayersFinal()) {
        if (!TRTYFullArchiveLayer::Merge(layer, ComponentConfig, context)) {
            return false;
        }
    }

    for (ui32 d = 0; d < context.Context.Dests.size(); ++d) {
        TIndexMetadataProcessor metaData(context.Context.Dests[d]);
        metaData->MutableFullArcHeader()->SetVersion(FULL_ARC_VERSION);
        metaData->MutableFullArcHeader()->SetStage(NRTYServer::TIndexMetadata::TFullArchiveHeader::FINISHED);
        metaData->MutableFullArcHeader()->SetDocsCount(context.Context.Decoder->GetNewDocsCount(d));
    }
    return true;
}

bool TRTYFullArchive::GetInfoChecker(NRTYServer::TInfoChecker& info) const {
    info.Version = FULL_ARC_VERSION;
    TStringStream ss;
    ComponentConfig.ToString(ss);
    info.AdditionalHash = MD5::Calc(ss.Str());
    return true;
}

bool TRTYFullArchive::CheckConfig() const {
    return TBaseIndexComponent::CheckConfig();
}

bool TRTYFullArchive::DoAllRight(const NRTYServer::TNormalizerContext& context) const {
    return Normalizers.AllRight(context, THolder<TFileMap>());
}

void TRTYFullArchive::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    Normalizers.CheckAndFix(context, THolder<TFileMap>());
}

bool TRTYFullArchive::CheckAlways() const {
    return true;
}

void TRTYFullArchive::SearchCustom(const TVector<IIndexController::TPtr>& controllers, ICustomReportBuilder& builder, const TRTYSearchRequestContext& context) const {
    const auto& cgi = context.CgiParams();
    ui32 border = Max<ui32>();
    if (!cgi.Has("key_name")) {
        ui32 keysCount;
        if (!cgi.Has("saas_no_text_split")) {
            keysCount = StringSplitter(cgi.Get("text")).Split(',').SkipEmpty().Count();
        } else {
            auto range = cgi.Range("text");
            keysCount = static_cast<ui32>(std::count_if(range.begin(), range.end(), [](const auto& txt) { return !txt.empty(); }));
        }
        if (keysCount == 0) {
            return;
        }
        const ui32 kpsCount = StringSplitter(cgi.Get("sgkps")).Split(',').SkipEmpty().Count();
        border = (kpsCount == 0) ? keysCount : keysCount * kpsCount;
    }

    for (auto& controller : controllers) {
        controller->SearchCustom(builder, context);
        if (builder.GetDocumentsCount() >= border)
            return;
    }
}
