#pragma once

#include "metrics.h"
#include <saas/rtyserver/search/source/search_source.h>
#include <saas/rtyserver/search/prefetch/common.h>

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/common/search_area_modifier.h>
#include <saas/library/searchserver/replier.h>
#include <search/session/scache.h>

#include <library/cpp/eventlog/eventlog.h>

class TSearchRequestData;

namespace NRTYServer {
    struct TTiersInfo;
}

class TSearchEnginesManager : public ISearchAreaModifier, public IMessageProcessor {
public:
    using TSearchersVectorRef = TSearchersVector&;
    using TSearchersVectorConstRef = const TSearchersVector&;
    using TCommonSearchHolder = THolder<TCommonSearch>;
    using TCommonSearchRef = TCommonSearchHolder&;
    using TCommonSearchConstRef = const TCommonSearchHolder&;

private:
    std::atomic<bool> IsActive;
    TEventLogPtr EventLog;
    TSearchers Searchers;

    const TRTYServerConfig& Config;
    TSearchCacheRef CommonCache;
    TSearchCacheRef CommonFastCache;

    THolder<NRTYServer::TPrefetcher> Prefetcher;

private:
    void RemoveSearcher(const TString& dir);
    void AddSearcher(const TString& dir, IIndexController::TPtr indexController);
    NRTYServer::TTiersInfo BuildTiersInfo(const TSearchersVector& searchersVector, ui64 minDocsCount, double crossPart) const;
    TString GetSearchersDataHash(const TSearchersVector& searchersVector) const;

public:
    class TGuardedSearch : public ILock {
        const TSearchEnginesManager& SEM;
        const TCommonSearch* MetaSearch;
        IIndexController::TPtr IndexController;
        bool IsMetaSearch;
        ui32 SearchVersion;
        ui32 SearchIdx;
        ui64 PathIndex;
    public:

        inline TGuardedSearch(const TSearchEnginesManager& sem)
            : SEM(sem)
            , MetaSearch(nullptr)
            , IsMetaSearch(true)
            , SearchIdx(0)
            , PathIndex(0)
        {
            SearchVersion = SEM.LockCurrentMetaSearch();
            MetaSearch = SEM.GetMetaSearch(SearchVersion).Get();
        }

        inline TGuardedSearch(const TSearchEnginesManager& sem, ui64 index, bool getCurrent)
            : SEM(sem)
            , MetaSearch(nullptr)
            , IsMetaSearch(false)
            , SearchVersion(getCurrent ? sem.SearchVersion : ui32(index >> 32))
            , SearchIdx(ui32(index & 0xFFFFFFFF))
            , PathIndex(index)
        {
            SEM.LockBaseSearch(SearchVersion);

            if (Y_LIKELY(SearchVersion == SEM.SearchVersion)) {
                // Normal pipeline
            } else if (SearchVersion == AtomicGet(SEM.PrefetchedVersion)) {
                // Prefetching next metasearch
            } else if (SearchVersion == SEM.UnusedVersion) {
                WARNING_LOG << "Deprecated search with dead meta search: " << SearchIdx << ", " << SearchVersion << ", " << SEM.SearchVersion << Endl;
                return;
            } else if ((SearchVersion + 1) < SEM.SearchVersion) {
                WARNING_LOG << "We use VERY old version index for search: " << SEM.SearchVersion << " >> " << SearchVersion << Endl;
                return;
            } else if (SearchVersion < SEM.SearchVersion) {
                NOTICE_LOG << "We use old version index for search: " << SEM.SearchVersion << " > " << SearchVersion << Endl;
            } else {
                FAIL_LOG("Incorrect search version %d, current %d", SearchVersion, SEM.SearchVersion);
            }

            const TSearchersVector& searchersVector = SEM.GetSearchersVector(SearchVersion);
            if (SearchIdx >= searchersVector.size()) {
                if ((SearchVersion != SEM.SearchVersion) || getCurrent) {
                    WARNING_LOG << "Deprecated search configuration for request processing: " << SearchIdx << ", " << searchersVector.size() << ", " << SearchVersion << ", " << SEM.SearchVersion << Endl;
                    return;
                } else {
                    FAIL_LOG("Incorrect search usage: incorrect source index (%u >= %lu), %u, %u", SearchIdx, searchersVector.size(), SearchVersion, SEM.SearchVersion);
                }
            }
            IndexController.Reset(searchersVector[SearchIdx]->GetIndexController());
        }

        inline const TCommonSearch* GetMetaSearch() const {
            CHECK_WITH_LOG(IsMetaSearch);
            return MetaSearch;
        }

        inline bool GetIsMetaSearch() const {
            return IsMetaSearch;
        }

        inline const IIndexController::TPtr GetIndexController() const {
            CHECK_WITH_LOG(!IsMetaSearch);
            return IndexController;
        }

        inline ui64 GetPathIndex() const {
            VERIFY_WITH_LOG(!IsMetaSearch, "GetPath called by meta search guard");
            return PathIndex;
        }

        inline ui32 GetVersion() const {
            return SearchVersion;
        }

        ISearchReplier::TPtr GetSearchReplier(const TString& customMetaSearch, ui64 defKps, IReplyContext::TPtr context);

        ~TGuardedSearch() override {
            if (IsMetaSearch) {
                SEM.UnlockCurrentMetaSearch(SearchVersion);
            } else {
                SEM.UnlockCurrentBaseSearch(SearchVersion);
            }
        }
    };

    using TGuardedSearchPtr = TSimpleSharedPtr<TGuardedSearch>;

public:
    TSearchEnginesManager(const TRTYServerConfig& config);
    ~TSearchEnginesManager();

    void Start();
    void Stop();

    // ISearchAreaModifier
    bool IsRemoved(const size_t indexId, const TDocHandle& docId) const override;
    void StartTransaction(bool checkMain) override;
    void FinishTransaction() override;

    ui32 GetDocumentsCount(size_t indexId, bool withDeleted) const override;
    ui32 GetTimestamp(size_t indexId, const TDocHandle& docIdFromMessage) const override;
    bool DecodeDocId(const TDocHandle& docIdFromMessage, ui32& indexId, ui32& docId) const override;
    ui32 DecodeDocId(const TDocHandle& docIdFromMessage, size_t indexId) const override;

    // ISearchLocker
    ILock* GetSearchLock() override;

    IEventLog& GetEventLog();
    /// return dirname of index by index_id as indexes mentioned in "mss"
    const TString& GetIndexDirName(size_t indexID) const;
    const TRTYServerConfig& GetConfig() const {
        return Config;
    }

    TGuardedSearchPtr GetAppropriateSearcher(const TSearchRequestData& rd) const;
    TGuardedSearchPtr GetMetaSearch() const;
    TGuardedSearchPtr GetActiveSearch() const;
    TGuardedSearchPtr GetCommonSearch(ui64 index, bool getCurrent = false) const;
    void PrefetchAtStart() const;

    bool IsMemorySearchEnabled() const {
        return EnableMemorySearch;
    }

    // IMessageProcessor
    bool Process(IMessage* message) override;
    TString Name() const override {
        return "SearchEngineManager";
    }

    const TSearchHandlers* GetBaseSearchHandlers() const {
        return BaseSearchHandlers.Get();
    }
    const TSearchHandlers* GetMetaSearchHandlers() const {
        return MetaSearchHandlers.Get();
    }
    TSearchServerMetrics* GetBaseSearchServerMetrics() const {
        return &BaseSearchServerMetrics;
    }
    TSearchServerMetrics* GetMainSearchServerMetrics() const {
        return &MainSearchServerMetrics;
    }

    TCommonSearchConstRef GetMetaSearchCurrent() const {
        return MetaSearch[SearchVersion % 2];
    }

    TCommonSearchRef GetMetaSearchCurrent() {
        return MetaSearch[SearchVersion % 2];
    }

    std::pair<TString, ui16> GetBaseHostAndPort() const;

private:
    bool EnableMemorySearch;

    mutable std::array<ITransaction, 2> ConnectionsLocker;
    mutable std::array<ITransactionEQ, 2> MetaLocker;
    mutable ITransaction TransactionLocker;
    mutable ITransaction SwitchVersionsTransaction;
    std::array<TSearchersVector, 2> SearchersVector;
    std::array<TCommonSearchHolder, 2> MetaSearch;
    std::array<TAtomic, 2> ActiveSearcher;

private:
    void StartMetaSearch(TCommonSearchRef metaSearchP, TSearchersVectorRef searchersVector, ui32 searchVersion);
    void StopMetaSearch(TCommonSearchRef metaSearch);

    ui32 GetActiveSearchersCount() const;
    ui64 GetActiveSearcherIndex(TSearchersVectorConstRef searchers) const;

    TCommonSearchRef GetMetaSearchNext() {
        return MetaSearch[(SearchVersion + 1) % 2];
    }

    TCommonSearchConstRef GetMetaSearch(ui32 searchVersion) const {
        return MetaSearch[searchVersion % 2];
    }

    TSearchersVector& GetSearchersVectorNext() {
        return SearchersVector[(SearchVersion + 1) % 2];
    }

    const TSearchersVector& GetSearchersVector(ui32 searchVersion) const {
        return SearchersVector[searchVersion % 2];
    }

    TSearchersVector& GetSearchersVectorCurrent() {
        return SearchersVector[SearchVersion % 2];
    }

    const TSearchersVector& GetSearchersVectorCurrent() const {
        return SearchersVector[SearchVersion % 2];
    }

    ui64 GetActiveSearcherIndex(ui32 searchVersion) const {
        return ActiveSearcher[searchVersion % 2];
    }

    void SetActiveSearcherIndex(ui32 searchVersion, ui64 value) {
        ActiveSearcher[searchVersion % 2] = value;
    }

    void LockBaseSearch(ui32 version) const {
        ConnectionsLocker[version % 2].StartIncompatibleAction();
    }

    ui32 LockCurrentMetaSearch() const {
        TGuardIncompatibleAction gia(SwitchVersionsTransaction);
        MetaLocker[SearchVersion % 2].StartIncompatibleAction();
        return SearchVersion;
    }

    void UnlockCurrentBaseSearch(ui32 searchVersion) const {
        ConnectionsLocker[searchVersion % 2].FinishIncompatibleAction();
    }

    void UnlockCurrentMetaSearch(ui32 searchVersion) const {
        MetaLocker[searchVersion % 2].FinishIncompatibleAction();
    }

    ITransaction& GetMetaLocker(ui32 searchVersion) const {
        return MetaLocker[searchVersion % 2];
    }

    ITransaction& GetBaseLocker(ui32 searchVersion) const {
        return ConnectionsLocker[searchVersion % 2];
    }

private:
    TAtomic PrefetchedVersion;
    ui32 SearchVersion;
    ui32 UnusedVersion;

    i32 ActiveTransactions;
    TString StartConfiguration;

    THolder<TSearchHandlers> BaseSearchHandlers;
    THolder<TSearchHandlers> MetaSearchHandlers;

    mutable TAutoGlobal<TSearchServerMetrics> BaseSearchServerMetrics;
    mutable TAutoGlobal<TSearchServerMetrics> MainSearchServerMetrics;
    mutable TAutoGlobal<TIndexMetrics> SearchableIndexMetrics;
    TMutex MutexSearchers;
};
