#pragma once

#include "broadcast.h"
#include "searchproxycgi.h"

#include <saas/searchproxy/common/serverinfo.h>
#include <saas/searchproxy/tvm/tvm_auth.h>
#include <saas/searchproxy/core/abc_resolver.h>
#include <saas/searchproxy/configs/searchproxyconfig.h>
#include <saas/library/authorization/auth.h>
#include <saas/library/daemon_base/metrics/servicemetrics.h>
#include <saas/library/daemon_base/daemon/controller.h>
#include <saas/library/flow/mirror.h>

#include <util/generic/map.h>

class ICgiCorrector;
using ICgiCorrectorPtr = TIntrusivePtr<ICgiCorrector>;

class TCollectSearchProxyServerInfo;
class TSearchConfig;
class TSearchProxyConfig;
class TSearchProxyHttpServer;
class TSearchProxyNehServer;

namespace NAppHost {
    class TLoop;
}

class TSearchProxyServer: public IMessageProcessor, public IServer {
public:
    using TInfoCollector = TCollectSearchProxyServerInfo;
    using TGlobalCorrectors = TMap<TString, ICgiCorrectorPtr>;
    using TConfig = TSearchProxyConfig;

    struct TTestTvmSuite {
        NSearchProxy::TTvmTraits::TClients TvmClients;
        TAtomicSharedPtr<NSearchProxy::IAbcResolver> AbcResolver;
    };

private:
    const TSearchProxyConfig& Config;

    TAtomicSharedPtr<NTvmAuth::TTvmClient> MainTvmClientImpl;
    NSearchProxy::TTvmTraits::TClients TvmClients;
    TAtomicSharedPtr<NSearchProxy::IAbcResolver> AbcResolver;

    THolder<TSearchProxyHttpServer> HttpServer;
    THolder<TSearchProxyNehServer>  NehServer;
    THolder<NAppHost::TLoop>        AppHostLoop;

    TMap<TString, IProxy::TPtr> Proxies;
    TSet<TString> Services;
    TMap<TString, TString> IncorrectServices;

    TGlobalCorrectors GlobalCgiCorrectors;
    NSaas::TAuthorization Authorizer;
    TStaticServiceMetrics ServiceMetrics;
    TFlowMirror FlowMirror;
    NSearchProxy::THostSet UnresolvedHosts;
    NSearchProxy::TBroadcaster Broadcaster;

private:
    void CreateSearchers();
    void InitTvmClient(const TTestTvmSuite*);
    void BuildTvmClients(const TSearchProxyConfig::TTvmParams&);
    void InitAppHost();
    TAtomicSharedPtr<NSearchProxy::IAbcResolver> CreateAbcResolver() const;

    ICgiCorrectorPtr CreateServiceCgiCorrector(const TString& service);
    ICgiCorrectorPtr CreateGlobalCgiCorrector(const TString& service);
    ICgiCorrectorPtr CreateMetaCgiCorrector(const TString& service);
    ICgiCorrectorPtr CreateUpperCgiCorrector();

public:
    TSearchProxyServer(const TSearchProxyConfig& config, const TTestTvmSuite* = nullptr);
    ~TSearchProxyServer();
    void ReopenLog() const;
    void Start();
    void Stop();

    TCommonSearch* GetMetaSearcherSafe(const TString& service) {
        auto it = Proxies.find("meta");
        if (it == Proxies.end())
            return nullptr;
        return it->second->GetCommonSearch(service);
    }

    bool HasService(const TString& service) const;
    bool IsGoodService(const TString& service) const;
    IProxy::TPtr SelectProxy(const TCgiParameters& cgi, const TString& defaultProxy);
    ISearchReplier::TPtr BuildReplier(const TString& service, IReplyContext::TPtr context);

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

    inline void Run() override {
        Start();
    }

    inline void Stop(ui32 /*rigidStopLevel*/, const TCgiParameters* /*cgiParams*/) override {
        Stop();
    }

    inline const TSearchProxyConfig& GetConfig() const {
        return Config;
    }

    void ApplyGlobalCorrections(const TString& serviceName, TCgiParameters& cgi);

    inline const NSearchProxy::TBroadcaster& GetBroadcaster() const {
        return Broadcaster;
    }

    inline const NSaas::TAuthorization& GetAuthorizer() const {
        return Authorizer;
    }

    inline TFlowMirror& GetFlowMirror() {
        return FlowMirror;
    }

    inline TStaticServiceMetrics& GetMetrics() {
        return ServiceMetrics;
    }

    inline const NSearchProxy::TTvmTraits::TClients& GetTvmClients() const {
        return TvmClients;
    }

    inline const NSearchProxy::IAbcResolver* GetAbcResolver() const {
        return AbcResolver.Get();
    }
};

struct IHostResolver {
    virtual ~IHostResolver() noexcept;
    virtual bool CanBeResolved(const TString& host, ui16 port) noexcept = 0;
};

struct TDefaultHostResolver : IHostResolver {
    bool CanBeResolved(const TString& host, ui16 port) noexcept override final;
};

class TSearchSourcesFiller {
public:
    TSearchSourcesFiller(const NSearchMapParser::TSearchMap& searchMap, IHostResolver& hostResolver);

    void FillSearchSources(const TString& service, TSearchConfig& config, const TString& suffix,
            TServiceConfig::TConsistencyPolicy consistencyPolicy, bool grouping, bool normalizeReplicas);

    const NSearchProxy::THostSet& GetUnresolvedHosts() const;

private:
    const NSearchMapParser::TSearchMap& SearchMap;
    IHostResolver& HostResolver;
    NSearchProxy::THostSet Unresolved;
};
