#include "component.h"
#include "builder.h"
#include "manager.h"
#include "parsed_entity.h"
#include "factors_calcer.h"

#include <saas/library/ptr_cast/ptr_cast.h>
#include <saas/rtyserver/config/const.h>
#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/key_inv_processor/ki_maker.h>
#include <saas/rtyserver/common/should_stop.h>
#include <saas/rtyserver/components/generator/index.h>
#include <saas/rtyserver/components/fullarchive/config.h>
#include <saas/rtyserver/components/fullarchive/disk_manager.h>
#include <saas/rtyserver/components/erf/erf_disk.h>
#include <saas/rtyserver/components/suggest/config/config.h>

TSuggestIndexComponent::TSuggestIndexComponent(const TRTYServerConfig& config)
    : TBaseIndexComponent(config, config.IndexGenerator == SUGGEST_COMPONENT_NAME || config.ComponentsSet.contains(SUGGEST_COMPONENT_NAME))
{
    ClearIndexFiles();
    RegisterIndexFile(TIndexFile(TRTYKIReader::GetKeyFilename(NRTYServer::NSuggest::KeyInvName), true, IIndexComponent::TIndexFile::ppDisable));
    RegisterIndexFile(TIndexFile(TRTYKIReader::GetInvFilename(NRTYServer::NSuggest::KeyInvName), true, IIndexComponent::TIndexFile::ppDisable));

    RegisterIndexFile(TIndexFile(TSuggestIndexManager::TArchive::GetDirFilename(NRTYServer::NSuggest::ArchivePrefix), true, IIndexComponent::TIndexFile::ppDisable));
    RegisterIndexFile(TIndexFile(TSuggestIndexManager::TArchive::GetArcFilename(NRTYServer::NSuggest::ArchivePrefix), true, IIndexComponent::TIndexFile::ppDisable));

    RegisterIndexFile(TIndexFile(TSuggestIndexManager::TArchive::GetDirFilename(NRTYServer::NSuggest::SortedArchivePrefix), true, IIndexComponent::TIndexFile::ppDisable));
    RegisterIndexFile(TIndexFile(TSuggestIndexManager::TArchive::GetArcFilename(NRTYServer::NSuggest::SortedArchivePrefix), true, IIndexComponent::TIndexFile::ppDisable));

    RegisterIndexFile(TIndexFile(NRTYServer::NSuggest::ErfFileName, true, IIndexComponent::TIndexFile::ppDisable));
}

THolder<NRTYServer::IIndexComponentBuilder> TSuggestIndexComponent::CreateBuilder(const NRTYServer::TBuilderConstructionContext& context) const {
    if (context.Config.GetType() == "memory") {
        return nullptr;
    }

    return MakeHolder<TSuggestIndexBuilder>(context.DirConfig, context.Config.Common.Owner, GetName());
}

THolder<NRTYServer::IIndexComponentManager> TSuggestIndexComponent::CreateManager(const NRTYServer::TManagerConstructionContext& context) const {
    switch (context.IndexType) {
        case IIndexController::FINAL:
        case IIndexController::PREPARED:
        case IIndexController::DISK:
            return MakeHolder<TSuggestIndexManager>(context, SUGGEST_COMPONENT_NAME);
        case IIndexController::MEMORY:
            return nullptr;
        default:
            FAIL_LOG("Incorrect index type");
    }
}

bool TSuggestIndexComponent::DoMerge(const NRTYServer::TMergeContext& context) const {
    NOTICE_LOG << "TSuggestIndexComponent::DoMerge starting..." << Endl;
    auto* configSuggest = Config.ComponentsConfig.Get<TSuggestComponentConfig>(GetName());
    CHECK_WITH_LOG(configSuggest);
    CHECK_WITH_LOG(context.Context.Dests.size() == 1 || (Config.ComponentsSet.contains(FULL_ARCHIVE_COMPONENT_NAME) && !configSuggest->GetClearUnusefulData()));
    if (context.Context.Dests.size() != 1) {
        NOTICE_LOG << "Detach from index for suggest is impossible. Suggest will be constructed from full archive" << Endl;
        return true;
    }
    if (context.Context.Sources.size() == 1) {
        TString pathFrom = context.Context.Sources[0];
        TString pathTo = context.Context.Dests[0];
        for (const auto& file : GetIndexFiles())
            NFs::Copy(pathFrom + "/" + file.Name, pathTo + "/" + file.Name);
        return true;
    }

    TDirConf dc;
    dc.TempDir = context.Context.Dests[0];
    TSuggestIndexBuilder::TPtr builder(new TSuggestIndexBuilder(dc, Config, GetName()));
    builder->SetDocsCount(context.Context.Decoder->GetNewDocsCount(0));

    NOTICE_LOG << "TSuggestIndexComponent::DoMerge starting...OK" << Endl;
    for (ui32 i = 0; i < context.Context.Sources.size(); ++i) {
        NOTICE_LOG << "TSuggestIndexComponent::DoMerge process " << context.Context.Sources[i] << Endl;
        TIndexControllerPtr finalIndex = context.Merger->GetIndexStorage().GetIndexController(context.Context.Sources[i]);
        const NRTYServer::IIndexManagersStorage& managers = finalIndex->GetManagers();
        const NRTYServer::TRealmConfig& realmConfig = Config.GetRealmListConfig().GetRealmConfigByConfigName(context.RealmName);
        NRTYServer::TManagerConstructionContext mcc(realmConfig.GetIndexerConfigDisk(), finalIndex->Directory(), finalIndex->GetIndexOwner(), IIndexController::FINAL, false, Config.IsReadOnly, NRTYServer::EExecutionContext::Merge);
        auto manager = DynamicHolderCast<TSuggestIndexManager>(CreateManager(mcc));
        CHECK_WITH_LOG(manager.Get());
        manager->InitInteractions(managers);
        manager->Open();

        TVector<ui32> docIdsForRemove;
        for (ui32 docId = 0; context.Context.Decoder->Check(i, docId); ++docId) {
            if (context.Context.Decoder->Decode(i, docId).DocId == REMAP_NOWHERE && !manager->IsRemoved(docId)) {
                docIdsForRemove.push_back(docId);
            }
        }
        manager->RemoveDocids(docIdsForRemove);

        for (ui32 j = 0; j < manager->GetRecordsCount(); ++j) {
            TSuggestRecord sr = manager->GetRecord(j);
            if (sr.GetCount())
                builder->Index(sr);
            if (ShouldStop(context.RigidStopSignal)) {
                NOTICE_LOG << "TSuggestIndexComponent::DoMerge cancelled...1" << Endl;
                break;
            }
        }
        manager->Close();
        NOTICE_LOG << "TSuggestIndexComponent::DoMerge process " << context.Context.Sources[i] << "...OK" << Endl;
        if (ShouldStop(context.RigidStopSignal)) {
            NOTICE_LOG << "TSuggestIndexComponent::DoMerge cancelled...2" << Endl;
            break;
        }
    }

    if (!ShouldStop(context.RigidStopSignal)) {
        builder->CloseImpl(dc.TempDir, nullptr, context.RigidStopSignal);
    }

    return !ShouldStop(context.RigidStopSignal);
}

NRTYServer::IComponentParser::TPtr TSuggestIndexComponent::BuildParser() const {
    return new TSuggestComponentParser(Config);
}

NRTYServer::IParsedEntity::TPtr TSuggestIndexComponent::BuildParsedEntity(NRTYServer::IParsedEntity::TConstructParams& params) const {
    return new TSuggestParsedEntity(params);
}

namespace {
    bool CheckArchive(const TString& path, const TString& fileName) {
        TArchiveReader<TSuggestRecord> arc;
        if (!arc.Open(path, fileName)) {
            ERROR_LOG << "can't open archive " << fileName << " on " << path;
            return false;
        }
        if (!arc.Size())
            return true;
        if (!arc.Check(arc.Size() - 1)) {
            ERROR_LOG << "archive " << fileName << " is corrupted on " << path;
            return false;
        }
        return true;
    }
}

bool TSuggestIndexComponent::DoMergeMeta(const NRTYServer::TMergeContext& context) const {
    auto* configSuggest = Config.ComponentsConfig.Get<TSuggestComponentConfig>(GetName());
    CHECK_WITH_LOG(configSuggest);
    for (ui32 i = 0; i < context.Context.Dests.size(); ++i) {
        TIndexMetadataProcessor proc(context.Context.Dests[i]);
        proc->SetSuggestClearUnusefulData(configSuggest->GetClearUnusefulData());
    }
    return TBaseIndexComponent::DoMergeMeta(context);
}

bool TSuggestIndexComponent::DoCheckMeta(const NRTYServer::TIndexMetadata& metaData) const {
    auto* configSuggest = Config.ComponentsConfig.Get<TSuggestComponentConfig>(GetName());
    CHECK_WITH_LOG(configSuggest);
    if (metaData.HasSuggestClearUnusefulData() && metaData.GetSuggestClearUnusefulData() != configSuggest->GetClearUnusefulData() && !configSuggest->GetClearUnusefulData()) {
        FATAL_LOG << "Incorrect index usage for current config: key Suggest.ClearUnusefulData different: " << configSuggest->GetClearUnusefulData() << Endl;
        return false;
    }
    return TBaseIndexComponent::DoCheckMeta(metaData);
}

bool TSuggestIndexComponent::DoAllRight(const NRTYServer::TNormalizerContext& context) const {
    TFsPath path(context.Dir.PathName());
    for (const auto& file : GetIndexFiles()) {
        if (!(path / file.Name).Exists())
            return false;
    }

    if (!NRTYServer::GetYndex(path, TRTYKIReader::GetIndexPrefix(NRTYServer::NSuggest::KeyInvName))) {
        return false;
    }
    if (!CheckArchive(path, NRTYServer::NSuggest::ArchivePrefix)) {
        return false;
    }
    if (!CheckArchive(path, NRTYServer::NSuggest::SortedArchivePrefix)) {
        return false;
    }

    {
        TRTYErfDiskHeader edh(&FactorsDescription, NRTYServer::NSuggest::ErfFileName);
        VERIFY_WITH_LOG(edh.LoadHeader(context.Dir.PathName()), "Broken erf header");
        if (edh.NeedInRemap())
            return false;
    }
    return true;
}

bool TSuggestIndexComponent::CheckConfig() const {
    bool result = TBaseIndexComponent::CheckConfig();

    const NRTYFactors::TFactorsList& factors = Config.GetSearcherConfig().Factors->GetSuggestFactors();
    const TSuggestFactorsCalcer::TAvailableFactors& available = TSuggestFactorsCalcer::TAvailableFactors::Instance();
    for (const auto& factor : factors) {
        if (available.find(factor.Name) == available.end()) {
            ERROR_LOG << "Unknown suggest factor " << factor.Name << Endl;
            result = false;
        }
    }

    auto fullArchiveConfig = Config.ComponentsConfig.Get<TRTYFullArchiveConfig>(FULL_ARCHIVE_COMPONENT_NAME);
    if (!fullArchiveConfig) {
        ERROR_LOG << "FullArchive config is required for " << GetName() << Endl;
        result = false;
    }
    if (fullArchiveConfig && !fullArchiveConfig->GetActiveLayersFinal().contains(NRTYServer::NFullArchive::FullLayer)) {
        ERROR_LOG << "FullArchive full layer is required for " << GetName() << Endl;
        result = false;
    }

    return result;
}

bool TSuggestIndexComponent::IsFinalIndex(const TString& path) const {
    auto yndex = NRTYServer::GetYndex(path, TRTYKIReader::GetIndexPrefix(NRTYServer::NSuggest::KeyInvName));
    return !!yndex;
}

bool TSuggestIndexComponent::IsEmptyIndex(const TString& path) const {
    return !NFs::Exists(TFsPath(path) / TRTYKIReader::GetInvFilename(NRTYServer::NSuggest::KeyInvName)) ||
           !NFs::Exists(TFsPath(path) / TRTYKIReader::GetKeyFilename(NRTYServer::NSuggest::KeyInvName)) ||
           !TFile(TFsPath(path) / TRTYKIReader::GetKeyFilename(NRTYServer::NSuggest::KeyInvName), RdOnly).GetLength();
}

bool TSuggestIndexComponent::HasIndexFiles(const TString& path) const {
    return NRTYServer::HasYndex(path, TRTYKIReader::GetIndexPrefix(NRTYServer::NSuggest::KeyInvName));
}

void TSuggestIndexComponent::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    if (AllRight(context))
        return;
    TFsPath path(context.Dir.PathName());
    for (const auto& file : GetIndexFiles())
        (path / file.Name).ForceDelete();
    const TDiskFAManager* fullArc = context.Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
    VERIFY_WITH_LOG(fullArc, "there is no fullarchive in %s", path.GetName().data());
    VERIFY_WITH_LOG(fullArc->IsOpened(), "fullarchive not opened in %s", path.GetName().data());
    TDirConf dirConfig;
    dirConfig.TempDir = path.GetPath();
    TSuggestIndexBuilder dest(dirConfig, Config, GetName());
    for (auto iter = fullArc->CreateIterator(); iter->IsValid(); iter->Next())
        dest.Index(0, *iter->GetDocument(), iter->GetDocId());
    TPathName pathName(path);
    NRTYServer::TBuilderCloseContext closeContext(pathName, pathName, nullptr, nullptr);
    dest.Close(closeContext);

    {
        TRTYErfDiskHeader edh(&FactorsDescription, NRTYServer::NSuggest::ErfFileName);
        VERIFY_WITH_LOG(edh.LoadHeader(context.Dir.PathName()), "Broken erf header");
        if (edh.NeedInRemap()) {
            NRTYServer::IIndexOwner::TGuardIndexModification g(context.Index);
            edh.Remap();
        }
    }
}
