#include "verifier.h"

#include <saas/library/metasearch/helpers/rearrange.h>
#include <saas/rtyserver/common/port.h>
#include <saas/rtyserver/components/ddk/ddk_component.h>
#include <saas/rtyserver/components/fullarchive/config.h>
#include <saas/rtyserver/components/indexer/index_config.h>
#include <saas/rtyserver/components/jupi/const.h>
#include <saas/rtyserver/components/prop/globals.h>
#include <saas/rtyserver/components/prop/config.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/config/shards_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/config/repair_config.h>
#include <saas/rtyserver/docfetcher/docfetcher.h>
#include <saas/rtyserver/factors/factors_config.h>
#include <saas/rtyserver/factors/rank_model.h>
#include <saas/rtyserver/search/source/searchers.h>

#include <search/web/core/rule_factory.h>

#include <robot/library/oxygen/indexer/processor/protos/config.pb.h>

#include <library/cpp/charset/wide.h>

#include <util/folder/dirut.h>
#include <util/folder/filelist.h>
#include <util/system/execpath.h>
#include <util/system/fs.h>
#include <util/generic/algorithm.h>

#define CHECK_ERROR(condition, text) \
    if (condition) {\
    ERROR_LOG << text << Endl;\
    result = text;\
    }

#define CHECK_WARNING(condition, text) \
    if (condition) {\
    WARNING_LOG << text << Endl;\
    }

void CheckFileInDirList(const TString& file, const TString& fileDesc, const TMap<TString, TString>& dirList, TMaybe<TString>& result) {
    if (file.empty() || !NFs::Exists(TFsPath(file).Dirname()))
        return;
    for (TMap<TString, TString>::const_iterator i = dirList.begin(), e = dirList.end(); i != e; ++i) {
        CHECK_ERROR(TFsPath(file).IsSubpathOf(TFsPath(i->second)), "Errors in paths: " + fileDesc + " in " + i->first);
    }
}

void CheckDirListIsIndependent(const TMap<TString, TString>& dirList, TMaybe<TString>& result) {
    for (TMap<TString, TString>::const_iterator i = dirList.begin(), e = dirList.end(); i != e; ++i)
        for (TMap<TString, TString>::const_iterator j = dirList.begin(); j != i; ++j)  {
            CHECK_ERROR(TFsPath(i->second).IsContainerOf(TFsPath(j->second)), "Errors in dirs: " + j->first + " is subdir of " + i->first);
            CHECK_ERROR(TFsPath(i->second).IsSubpathOf(TFsPath(j->second)), "Errors in dirs: " + i->first + " is subdir of " + j->first);
        }
}

void AddDirToList(TMap<TString, TString>& dirList, const TString& description, const TString& dir) {
    VERIFY_WITH_LOG(!description.empty(), "invalid usage");
    if (!dir.empty())
        dirList[description] = dir;
}

TMaybe<TString> DoVerify(TRTYServerConfig& config, bool checkPortsIsBusy) {
    NOTICE_LOG << "Check config: " << config.ToString() << Endl;
    TMaybe<TString> result = Nothing();

    //---------------------------------------------------------------
    // Abort if no IndexDir
    if (config.GetRealmListConfig().AutoGenerated) {
        CHECK_ERROR(!config.IndexDir || !NFs::Exists(config.IndexDir), "IndexDir not found : (" + config.IndexDir + ")");
    }
    if (config.GetIndexerMemoryConfig().Enabled) {
        if (config.GetIndexerMemoryConfig().RealTimeExternalFilesPath.size()) {
            CHECK_ERROR(!NFs::Exists(config.GetIndexerMemoryConfig().RealTimeExternalFilesPath), "RealTimeExternalFilesPath not found : (" + config.GetIndexerMemoryConfig().RealTimeExternalFilesPath + ")");
        }
    }

    //---------------------------------------------------------------
    // check correctness of the structure of directories
    TMap<TString, TString> dirList;
    if (config.GetRealmListConfig().AutoGenerated) {
        AddDirToList(dirList, "IndexDir", config.IndexDir);
    }
    if (config.GetSearcherConfig().CacheOptions.UseCache)
        AddDirToList(dirList, "CacheDir", config.GetSearcherConfig().CacheOptions.CacheDir);
    AddDirToList(dirList, "ConfigsRoot", config.GetDaemonConfig().GetController().ConfigsRoot);
    AddDirToList(dirList, "StateRoot", config.GetDaemonConfig().GetController().StateRoot);
    CheckDirListIsIndependent(dirList, result);

    CHECK_ERROR(TFsPath(GetExecPath()).Parent().IsSubpathOf(TFsPath(config.GetDaemonConfig().GetController().StateRoot)), "Incorrect directories structure: StateRoot and BinDir");
    CHECK_ERROR(TFsPath(GetExecPath()).Parent().IsSubpathOf(TFsPath(config.GetDaemonConfig().GetController().ConfigsRoot)), "Incorrect directories structure: ConfigsRoot and BinDir");

    CheckFileInDirList(config.GetCommonIndexers().IndexLog, "IndexLog", dirList, result);
    CheckFileInDirList(config.GetSearcherConfig().AccessLog, "AccessLog", dirList, result);
    CheckFileInDirList(config.DaemonConfig.GetLoggerType(), "GlobalLog", dirList, result);

    //---------------------------------------------------------------
    // Prepare directories
    if (config.GetMergerConfig().TmpfsDir) {
        if (config.VerificationPolicy == TRTYServerConfig::vpRelease) {
            INFO_LOG << "ForceDelete " << config.GetMergerConfig().TmpfsDir << Endl;
            TFsPath(config.GetMergerConfig().TmpfsDir).ForceDelete();
        }
        TFsPath(config.GetMergerConfig().TmpfsDir).MkDirs();
    }
    if (config.GetRealmListConfig().AutoGenerated) {
        CHECK_ERROR(config.GetIndexerDiskConfig().SearchEnabled && !config.GetIndexerDiskConfig().SearchObjectsDirectory, "SearchObjectsDirectory can't be empty if Disk.SearchEnabled");
        if (config.GetIndexerDiskConfig().SearchEnabled) {
            TFsPath sod = TFsPath(config.GetIndexerDiskConfig().SearchObjectsDirectory).Fix();
            TFsPath id = TFsPath(config.IndexDir).Fix();
            CHECK_ERROR(sod.IsContainerOf(id) || id.IsContainerOf(sod), "Incorrect directories relation");
            if (config.VerificationPolicy == TRTYServerConfig::vpRelease) {
                INFO_LOG << "ForceDelete " << config.GetIndexerDiskConfig().SearchObjectsDirectory << Endl;
                TFsPath(config.GetIndexerDiskConfig().SearchObjectsDirectory).ForceDelete();
                TFsPath(config.GetIndexerDiskConfig().SearchObjectsDirectory).MkDirs();
            }
        }
    } else {
        TVector<TFsPath> realmsPaths;
        for (auto&& [realmName, realmConfig] : config.GetRealmListConfig().RealmsConfig) {
            if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
                continue;
            }
            TFsPath currentRealmDir = TFsPath(realmConfig.RealmDirectory).Fix();
            for (const TFsPath& realmDir : realmsPaths) {
                CHECK_ERROR(realmDir.IsContainerOf(currentRealmDir) || currentRealmDir.IsContainerOf(realmDir), "Incorrect directories relation");
            }
            if (config.VerificationPolicy == TRTYServerConfig::vpRelease && realmConfig.ClearOnStart) {
                INFO_LOG << "ForceDelete" << currentRealmDir << Endl;
                currentRealmDir.ForceDelete();
                currentRealmDir.MkDirs();
            }
            realmsPaths.push_back(currentRealmDir);
        }
    }

    //---------------------------------------------------------------
    // Verify the cross-component restrictions, and perform global checks
    CHECK_ERROR(!config.IsPrefixedIndex && (config.GetShardsConfig().Number != 1 || !config.GetShardsConfig().IsLocalShard(0)), "'ShardsNumber' must be 1 and the local shard must be 0 if not IsPrefixedIndex");
    CHECK_ERROR(!config.GetShardsConfig().Number , "'ShardsNumber' value must be positive");
    CHECK_ERROR(!config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().Threads, "Incorrect number of threads: 0 for disk indexer");
    CHECK_ERROR(config.GetSearcherConfig().DelegateRequestOptimization && (!!config.GetSearcherConfig().ReArrangeOptions || config.IsPrefixedIndex), "we can't delegate request when ReArrange is used");
    CHECK_ERROR(config.GetSearcherConfig().DelegateRequestOptimization && (config.SearchersCountLimit != 1), "we can't delegate request when more than one base searcher is used");
    CHECK_ERROR(config.SearchersCountLimit && (config.SearchersCountLimit < config.GetMergerConfig().MaxSegments), "Searchable segments count must not be less then MaxSegments for merger");
    CHECK_ERROR(config.GetSearcherConfig().DelegateRequestOptimization && config.GetIndexerMemoryConfig().Enabled, "we can't delegate request when the memory search is used");
    CHECK_ERROR(config.GetSearcherConfig().DelegateRequestOptimization && config.GetShardsConfig().Number != 1, "we can't delegate request in the case of multiple shards");
    if (config.GetRealmListConfig().AutoGenerated) {
        CHECK_ERROR(config.GetIndexerDiskConfig().PreparatesMode && config.SearchersCountLimit != 1, "we can't use PreparatesMode with more than one searcher");
    } else {
        for (auto&& [realmName, realmConfig] : config.GetRealmListConfig().RealmsConfig) {
            if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
                continue;
            }
            CHECK_ERROR(realmConfig.GetIndexerConfigDisk().PreparatesMode && config.SearchersCountLimit != 1, "we can't use PreparatesMode with more than one searcher for realm " + realmConfig.ConfigName);
        }
    }

    CHECK_ERROR(config.IsReadOnly && config.GetCommonIndexers().Enabled, "we can't run indexer in read-only mode");
    CHECK_ERROR(config.IsReadOnly && config.GetMergerConfig().Enabled, "we can't run merger in read-only mode");
    CHECK_ERROR(config.IsReadOnly && config.GetRepairConfig().Enabled, "we can't repair index in read-only mode");
    if (config.VerificationPolicy == TRTYServerConfig::vpRelease) {

        if (config.ModulesConfig.Get<NFusion::TDocFetcherConfig>(NFusion::DocfetcherModuleName) && config.ModulesConfig.Get<NFusion::TDocFetcherConfig>(NFusion::DocfetcherModuleName)->Enabled)
            CHECK_ERROR(config.GetRepairConfig().Enabled, "we cannot use repair and docfetcher modules at the same time");

        if (config.GetRealmListConfig().AutoGenerated) {
            CHECK_ERROR(config.GetIndexerDiskConfig().SearchEnabled && config.GetIndexerMemoryConfig().Enabled, "config.IndexerDiskConfig.SearchEnabled && config.GetIndexerMemoryConfig().Enabled");
            if (config.GetIndexerDiskConfig().SearchEnabled) {
                CHECK_ERROR(!config.GetMergerConfig().Enabled, "config.IndexerDiskConfig.SearchEnabled && !config.GetMergerConfig().Enabled");
                CHECK_ERROR(config.GetMergerConfig().Threads <= 1, "config.IndexerDiskConfig.SearchEnabled && (config.GetMergerConfig().Threads <= 1)");
            }
        } else {
            for (auto&& [realmName, realmConfig] : config.GetRealmListConfig().RealmsConfig) {
                if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
                    continue;
                }
                if (realmConfig.GetIndexerConfigDisk().SearchEnabled) {
                    CHECK_ERROR(!realmConfig.GetMergerConfig().Enabled, "realmConfig.GetIndexerConfigDisk().SearchEnabled && !realmConfig.GetMergerConfig().Enabled for realm: " + realmConfig.ConfigName);
                    CHECK_ERROR(realmConfig.GetMergerConfig().Threads <= 1, "realmConfig.GetIndexerConfigDisk().SearchEnabled && (realmConfig.GetMergerConfig().Threads <= 1) for realm: " + realmConfig.ConfigName);
                }
            }
        }

        if (!config.ModulesConfig.Get<TPluginConfig<IDaemonModuleConfig>>("EXTBUILDER")) {
            // oxy restrictions without EXTBUILDER
            if (config.IndexGenerator == OXY_COMPONENT_NAME) {
                CHECK_ERROR(config.Pruning->ToString() != "oxy", "Inconsistent options PruneAttrSort and IndexGenerator");
            }
        }
    }

    if (config.GetSearcherConfig().CacheOptions.UseCache) {
        if (!config.GetSearcherConfig().CacheOptions.CacheLifeTime.Enabled() &&
            (config.GetSearcherConfig().ReArrangeOptions.find("CacheSupporter") == TString::npos)) {
            CHECK_ERROR(true, "Incorrect cache usage policy");
        }
    }

    if (config.GetMergerConfig().MaxDeadlineDocs) {
        CHECK_ERROR(!TRTYDDKIndexComponent::IsUsedStatic(config), "Incorrect usage: DDK component was not found, but MaxDeadlineDocs is set.");
        CHECK_ERROR(config.GetMergerConfig().MaxDeadlineDocs < config.GetIndexerDiskConfig().MaxDocuments, "incorrect MaxDeadlineDocs < MaxDocuments");
        CHECK_ERROR(config.GetMergerConfig().MaxDeadlineDocs > config.GetMergerConfig().MaxDocumentsToMerge, "incorrect MaxDeadlineDocs > MaxDocumentsToMerge");
    }

    if (config.ComponentsSet.contains(NRTYServer::JupiComponentName)) {
        CHECK_ERROR(config.GetMergerConfig().SegmentsSort != NRTYServer::TMergerConfig::ESegmentsSort::Id,
            NRTYServer::JupiComponentName + " component requires Merger.SegmentsSort = ID");
    }

    CHECK_ERROR(config.GetMergerConfig().WriteOption.GetUnitsPerSecond() < 1 << 20, "Incorrect WriteOption - too small");
    CHECK_ERROR(config.GetCommonIndexers().DefaultCharset < 0, "DefaultCharset is not set - utf8 is recommended");
    CHECK_ERROR(UnknownLanguage(config.GetCommonIndexers().DefaultLanguage), "DefaultLanguage is not set. 'ru' is recommended");
    CHECK_ERROR(UnknownLanguage(config.GetCommonIndexers().DefaultLanguage2), "DefaultLanguage2 is not set. 'ru' is recommended");

    if (config.DaemonConfig.GetLoggerType() != "console" && !NFs::Exists(TFsPath(config.DaemonConfig.GetLoggerType()).Dirname()))
        CHECK_ERROR(true, "GlobalLog settings is incorrect");
    if (!!config.GetSearcherConfig().AccessLog && !NFs::Exists(TFsPath(config.GetSearcherConfig().AccessLog).Dirname()))
        CHECK_ERROR(true, "AccessLog settings is incorrect");
    if (!!config.GetCommonIndexers().IndexLog && !NFs::Exists(TFsPath(config.GetCommonIndexers().IndexLog).Dirname()))
        CHECK_ERROR(true, "IndexLog settings is incorrect");

    //---------------------------------------------------------------
    // Convert deprecated params
    if (auto df = config.ModulesConfig.Get<NFusion::TDocFetcherConfig>(NFusion::DocfetcherModuleName)) {
        if (df->Enabled && df->WatchdogOptionsFile) {
            if (!config.WatchdogOptionsFiles) {
                WARNING_LOG << "DOCFETCHER.WatchdogOptionsFile is deprecated (and will be removed soon), please fix" << Endl;
                config.WatchdogOptionsFiles = {df->WatchdogOptionsFile};
            } else if (config.WatchdogOptionsFiles.size() != 1 && config.WatchdogOptionsFiles.front() != df->WatchdogOptionsFile) {
                CHECK_ERROR(true, "DOCFETCHER.WatchdogOptionsFile (deprecated) cannot be different from Server.WatchdogOptionsFile");
            } else {
                WARNING_LOG << "DOCFETCHER.WatchdogOptionsFile (deprecated) has no effect in your configuration" << Endl;
            }
        }
    }

    //---------------------------------------------------------------
    // check Indexer options (not specific to the particular generator component)
    const auto fullArchiveConfig = config.ComponentsConfig.Get<TRTYFullArchiveConfig>(FULL_ARCHIVE_COMPONENT_NAME);

    if (fullArchiveConfig) {
        for (const auto &[_, config] : fullArchiveConfig->GetLayers()) {
            CHECK_ERROR(config.PreallocateParts && config.PartSizeLimit > NRTYArchive::TMultipartConfig::MAX_PART_SIZE_FOR_PREALLOCATION, "Part size is too large for preallocation");
        }
    }

    CHECK_ERROR(config.GetCommonIndexers().StoreUpdateData && !config.GetCommonIndexers().UseSlowUpdate, "Incorrect StoreUpdateData without UseSlowUpdate");
    CHECK_ERROR(config.GetCommonIndexers().UseSlowUpdate && (!fullArchiveConfig || (fullArchiveConfig->GetActiveLayersFinal().size() == 1 && fullArchiveConfig->GetActiveLayersFinal().contains(NRTYServer::NFullArchive::BaseLayer))),
        "FullArchive 'full' layer is required for slow updates");

    INFO_LOG << "Cache policy = " << config.GetCachePolicy() << Endl;
    CHECK_ERROR((config.GetSearcherConfig().CacheOptions.CacheLifeTime.Enabled()) && !config.GetSearcherConfig().CacheOptions.UseCache, "Error: CacheLifeTime is set, but the cache is disabled");
    CHECK_ERROR((config.GetSearcherConfig().ReArrangeOptions.find("CacheSupporter") != TString::npos) && !config.GetSearcherConfig().CacheOptions.UseCache, "Error: CacheSupporter options  is set, but the cache is disabled");
    CHECK_ERROR(config.GetSearcherConfig().CacheOptions.CacheLifeTime.ValidityPeriod.Seconds() < 30, "Error: CacheLifeTime is too small");
    CHECK_ERROR(config.GetCachePolicy() == TRTYServerConfig::cpUnknown, "CachePolicy is undefined");

    CHECK_ERROR(!!config.GetCommonIndexers().RecognizeLibraryFile && !NFs::Exists(config.GetCommonIndexers().RecognizeLibraryFile),
                        "Morphology is not properly configured, can't find " + config.GetCommonIndexers().RecognizeLibraryFile );

    TSet<ui16> ports;
    CHECK_ERROR(ports.find(config.GetCommonIndexers().ServerOptions.Port) != ports.end(), "indexer port is in use by another indexer");
    CHECK_ERROR(checkPortsIsBusy && !IsPortFree(config.GetCommonIndexers().ServerOptions.Port), "indexer port is busy. (some other process)");
    ports.insert(config.GetCommonIndexers().ServerOptions.Port);

    CHECK_ERROR(config.GetRealmListConfig().CloseThreads == 0 || config.GetRealmListConfig().CloseThreads > config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().Threads, "CloseThreads value is incorrect. 1 is recommended.");
    CHECK_ERROR(!!config.GetSearcherConfig().SnippetsDeniedZones && config.IndexGenerator != INDEX_COMPONENT_NAME, TString("SnippetsDeniedZones can be used only with ") + TString(INDEX_COMPONENT_NAME) + " component");
    CHECK_WARNING(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().PortionDocCount > 20000, "PortionDocCount is too big");
    CHECK_ERROR(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().MaxDocuments > 1 << DOC_LEVEL_Bits, "MaxDocuments is too big ( " + ToString(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().MaxDocuments) + " > " + ToString(1 << DOC_LEVEL_Bits) + ")");
    CHECK_ERROR(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().MaxDocuments == 0, "MaxDocuments is zero");
    CHECK_ERROR(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().TimeToLiveSec && (config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().TimeToLiveSec < 60), "TimeToLiveSec is too small (less than 60 seconds).");

    if (config.VerificationPolicy == TRTYServerConfig::vpRelease) {
        CHECK_ERROR(config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().DbgMaxDocumentsTotal > 0, "MainRealmConfig.GetIndexerConfigDisk().DbgMaxDocumentsTotal is only for tests");
        CHECK_ERROR(config.GetIndexerMemoryConfig().Enabled && config.GetIndexerMemoryConfig().DbgMaxDocumentsTotal > 0, "IndexerMemoryConfig.DbgMaxDocumentsTotal is only for tests");
    }

    // IndexerMemory
    if (config.GetIndexerMemoryConfig().Enabled) {
        CHECK_ERROR(!config.GetIndexerMemoryConfig().GarbageCollectionTime || config.GetIndexerMemoryConfig().GarbageCollectionTime > 100, "Memory.GarbageCollectionTime has no effect");
        CHECK_ERROR(config.GetIndexerMemoryConfig().MaxDocumentsReserveCapacityCoeff < 2, "Memory.MaxDocumentsReserveCapacityCoeff should be at least 2 (4 is recommended by default)");
        CHECK_ERROR(!config.GetIndexerMemoryConfig().Threads, "Incorrect threads number: 0 for memory indexer");
        VERIFY_WITH_LOG(config.GetIndexerMemoryConfig().MaxDocuments == Max<ui32>(500, config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().MaxDocuments * config.GetIndexerMemoryConfig().MaxDocumentsReserveCapacityCoeff), "Memory.MaxDocuments is incorrect");
    }
    if (config.VerificationPolicy == TRTYServerConfig::vpIndexingUniqueUrls) {
        CHECK_ERROR(config.GetIndexerMemoryConfig().AllowSameUrls, "We shouldn't allow same urls (Memory.AllowSameUrls) in this mode (IndexingUniqueUrls)");
    }

    // IndexerDisk
    if (config.VerificationPolicy == TRTYServerConfig::vpIndexingUniqueUrls) {
        for (auto&& [realmName, realmConfig] : config.GetRealmListConfig().RealmsConfig) {
            if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
                continue;
            }
            CHECK_ERROR((config.DDKManager == "FULLARC") && realmConfig.GetIndexerConfigDisk().AllowSameUrls, "We shouldn't allow same urls (Disk.AllowSameUrls) in this mode (IndexingUniqueUrls)");
        }
    }

    TSet<TString> existingRemappers;
    IUrlToDocIdManager::TRemapperFactory::GetRegisteredKeys(existingRemappers);
    CHECK_ERROR(!existingRemappers.contains(config.UrlToDocIdManager), "This UrlToDocIdManager doesn't exist!");

    //---------------------------------------------------------------
    // check Merger options
    CHECK_ERROR(config.GetMergerConfig().MaxDocumentsToMerge > 1 << DOC_LEVEL_Bits, "MaxDocumentsToMerge is too big ( > " + ToString(1 << DOC_LEVEL_Bits) + ")");
    if (config.GetMergerConfig().MergerCheckPolicy == NRTYServer::TMergerConfig::mcpTime) {
        CHECK_ERROR(config.GetMergerConfig().TimingCheckIntervalMilliseconds == 0, "TimingCheckIntervalMilliseconds must not be zero for TIME MergerCheckPolicy or TimingCheckTimeRanges");
    }

    //---------------------------------------------------------------
    // check Searcher options
    CHECK_ERROR(ports.find(config.GetSearcherConfig().ServerOptions.Port) != ports.end(), "Search port must be different from the indexer's");
    CHECK_ERROR(checkPortsIsBusy && !IsPortFree(config.GetSearcherConfig().ServerOptions.Port), "Search port is occupied");
    ports.insert(config.GetSearcherConfig().ServerOptions.Port);
    CHECK_ERROR(ports.find(config.GetBaseSearcherConfig().Port) != ports.end(), "Base search port is in use by an indexer or searcher");
    CHECK_ERROR(checkPortsIsBusy && !IsPortFree(config.GetBaseSearcherConfig().Port), "Base search port is occupied");
    ports.insert(config.GetBaseSearcherConfig().Port);
    CHECK_ERROR(ports.find(config.DaemonConfig.GetController().Port) != ports.end(), "Controller port is in use by an indexer or searcher");


    TVector<TString> registeredRearranges;
    NRearr::TRearrangeFactory::ListRules(registeredRearranges);

    NUtil::TRearrangeOptionsHelper helper(config.GetSearcherConfig().ReArrangeOptions);
    for (auto&& rule: helper.GetRules())
        CHECK_ERROR(!IsIn(registeredRearranges, rule), "Unknown rearrange rule " + rule);

    TVector<TString> attrStrings;
    Split(config.GetSearcherConfig().QueryLanguage, "\n", attrStrings);
    for (TVector<TString>::const_iterator i = attrStrings.begin(), e = attrStrings.end(); i != e; ++i) {
        TVector<TString> parts;
        Split(*i, ":", parts);
        TUtf16String name = parts.empty() ? TUtf16String() : CharToWide(parts.front(), csYandex);
        Strip(name);
        if (!name)
            continue;
        if (const TReqAttrList::TAttrData* data = config.GetSearcherConfig().ReqAttrList->GetAttrData(name.data()))
            CHECK_ERROR(TReqAttrList::IsAttr(data->Type) && (data->Target == RA_TARG_UNDEF), "Attrtarget is undefined: " + *i);
    }

    for (auto&& path : config.GetSearcherConfig().SearchPath) {
        CHECK_ERROR(IsNumber(path), "one of the SearchPaths is a number");
    }

    //---------------------------------------------------------------
    // check options for the implementation of IndexGenerator
    CHECK_ERROR(!NRTYServer::IIndexComponent::TFactory::Has(config.IndexGenerator), "Incorrect IndexGenerator name " + config.IndexGenerator);
    if (config.IndexGenerator == FULL_ARCHIVE_COMPONENT_NAME) {
        CHECK_ERROR(config.GetSearcherConfig().TwoStepQuery, "There is no reason use two step queries with fullarc index generator");
    }

    CHECK_ERROR(config.IndexGenerator == OXY_COMPONENT_NAME && config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().Threads > 1, "We cannot use the multithread mode for OXY index generator");

    if (config.IndexGenerator == OXY_COMPONENT_NAME) {
        CHECK_ERROR(!NFs::Exists(config.GetCommonIndexers().OxygenOptionsFile), "Incorrect oxy options file");
        CHECK_ERROR(ArcTypeFromProto(config.GetCommonIndexers().OxygenOptions->GetArcOptions().GetArchiveType()) != config.GetSearcherConfig().ArchiveType, "Discordant ArchiveType in main and oxy config: " + ToString(config.GetSearcherConfig().ArchiveType) + " in config");
        CHECK_ERROR(config.GetCommonIndexers().OxygenOptions->HasIncrementalOptions(), "Don't use incremental options!");
    }

    if (config.ComponentsSet.contains(NRTYServer::PropComponentName)) {
        CHECK_ERROR(!config.ComponentsSet.contains(FULL_ARCHIVE_COMPONENT_NAME), "PROP cannot be used without FULLARC");
        CHECK_ERROR(
            !fullArchiveConfig->GetActiveLayersFinal().contains(NRTYServer::PropComponentName),
            "FULLARC must have PROP layer enabled in order for the PROP component to work");
        CHECK_ERROR(
            config.GetIndexerMemoryConfig().Enabled && config.IndexGenerator != FULL_ARCHIVE_COMPONENT_NAME && config.IndexGenerator != INDEX_COMPONENT_NAME,
            "Cannot enable IndexerMemory with PROP component when IndexGenerator is not either FULLARC or INDEX");
    }

    if (config.IndexGenerator == INDEX_COMPONENT_NAME) {
        const auto indexerConfig = config.ComponentsConfig.Get<TRTYIndexComponentConfig>(INDEX_COMPONENT_NAME);
        if (indexerConfig->GetExcludeProperties()) {
            const auto propConfig = config.ComponentsConfig.Get<NRTYServer::TPropConfig>(NRTYServer::PropComponentName);
            CHECK_ERROR(
                !config.ComponentsSet.contains(NRTYServer::PropComponentName) ||
                !propConfig->ShouldContainProperty("*"),
                "When properties are disabled for Indexer, they should be enabled in PROP");
            CHECK_ERROR(
                !propConfig->GetEnableGta(), "PROP.EnableGta must be set, otherwise you cannot get the stored properties through gta at all");
        }
    }

    try {
        if (config.Pruning->GetType() == TPruningConfig::FORMULA) {
            const auto factorsConfig = config.GetSearcherConfig().Factors;
            const auto formulaName = config.Pruning->ToString().substr(8);
            const auto formula = factorsConfig->GetRankModel(formulaName);
            CHECK_ERROR(!formula, "no pruning formula"); // never happens, because GetRankModel returns default
            CHECK_ERROR(!factorsConfig->IsStaticOnly(formula->GetUsedFactors()), TString("pruning formula is not static: '") + formula->GetName() + "' (resolved from '" + formulaName + "')");
        }
        if (!config.NoMorphology) {
            NLanguageMasks::CreateFromList(config.MorphologyLanguages);
            NLanguageMasks::CreateFromList(config.PreferedMorphologyLanguages);
        }
    } catch (const yexception& e) {
        CHECK_ERROR(true, e.what());
    }

    CHECK_ERROR(config.ComponentsSet.contains(OXY_COMPONENT_NAME) && config.ComponentsSet.contains(INDEX_COMPONENT_NAME),
                 "OXY and INDEX components should not be used at the same time!");

    //---------------------------------------------------------------
    // call the per-component routines
    CHECK_ERROR(!config.ComponentsConfig.Check(), "Errors in ComponentsConfig");
    CHECK_ERROR(!config.ModulesConfig.Check(), "Errors in ModulesConfig");
    CHECK_ERROR(!IRTYSearcher::TFactory::Has(config.GetSearcherConfig().CustomSearcher), "Unknown CustomSearcher " + config.GetSearcherConfig().CustomSearcher);

    return result;
}

#undef CHECK_ERROR
#undef CHECK_WARNING
