#pragma once

#include "query.h"
#include "tvm.h"

#include <search/idl/meta.pb.h>
#include <search/fetcher/fetcher.h>

#include <saas/library/sender_neh/sender_neh.h>
#include <library/cpp/eventlog/eventlog.h>
#include <library/cpp/tvmauth/client/facade.h>

#include <util/datetime/cputimer.h>
#include <util/generic/ptr.h>
#include <util/generic/vector.h>
#include <util/memory/blob.h>

namespace NNeh {
    class THandle;
    using THandleRef = TIntrusivePtr<THandle>;
    class IMultiClient;
}

namespace NSaas {

    class TSearchReply {
    private:
        class IImpl {
        public:
            virtual ~IImpl() {}

            virtual bool IsSucceeded() const = 0;
            virtual i32 GetCode() const = 0;
            virtual const NMetaProtocol::TReport& GetReport() const = 0;
            virtual const TString& GetRawReport() const = 0;
            virtual const TString& GetErrorMessage() const = 0;
        };

        class TSearchAsyncReplyImpl;
        class TSearchNoReplyImpl;
        class TSearchErrorReplyImpl;
        class TSearchNoResultsReplyImpl;
        class TSearchProtoReplyImpl;

        TSimpleSharedPtr<IImpl> Impl;

    private:
        TSearchReply(IImpl* impl)
            : Impl(impl)
        {
            Y_ASSERT(Impl);
        }

    public:
        bool IsSucceeded() const {
            return Impl->IsSucceeded();
        }
        i32 GetCode() const {
            return Impl->GetCode();
        }
        const NMetaProtocol::TReport& GetReport() const {
            return Impl->GetReport();
        }
        const TString& GetRawReport() const {
            return Impl->GetRawReport();
        }
        const TString& GetErrorMessage() const {
            return Impl->GetErrorMessage();
        }

        void ScanDocs(std::function<void(const NMetaProtocol::TDocument&)> processor) const {
            for (ui32 grouping = 0; grouping < GetReport().GroupingSize(); ++grouping) {
                for (ui32 group = 0; group < GetReport().GetGrouping(grouping).GroupSize(); ++group) {
                    for (ui32 doc = 0; doc < GetReport().GetGrouping(grouping).GetGroup(group).DocumentSize(); ++doc) {
                        processor(GetReport().GetGrouping(grouping).GetGroup(group).GetDocument(doc));
                    }
                }
            }
        }

        static TSearchReply FromAsyncHandle(NNeh::THandleRef handle, TDuration timeout, bool alwaysUseDataAsResponse=false);
        static TSearchReply FromAsyncResponse(const NNeh::TResponseRef& response, bool alwaysUseDataAsResponse = false);
        static TSearchReply FromAsyncResponse(const NNeh::TResponse& response, bool alwaysUseDataAsResponse = false);
        static TSearchReply FromNoReply();
        static TSearchReply FromErrorReply(ui32 code, const TString& message);
        static TSearchReply FromNoResultsReply(ui32 code);
        static TSearchReply FromProtoReply(ui32 code, const char* data, size_t size);
        static TSearchReply FromProtoReply(ui32 code, const NMetaProtocol::TReport& report);
    };

    class TSearchClient {
    private:
        TString ServiceName;
        ui16 Port;
        TString Host;

        std::shared_ptr<NSaas::TTvmManager> TvmManager;

    protected:
        static const TDuration DefaultTimeout;

    public:
        TSearchClient(const TString& serviceName, const TString& host, const ui16 port,
            std::shared_ptr<NSaas::TTvmManager> tvmManager = nullptr)
            : ServiceName(serviceName)
            , Port(port)
            , Host(host)
            , TvmManager(std::move(tvmManager))
        {}

        TString GetFullUrl(const TQuery& query, TDuration timeout = DefaultTimeout) const;
        TString GetFullQueryString(const TQuery& query, TDuration timeout = DefaultTimeout) const;

        const TString& GetServiceName() const {
            return ServiceName;
        }
        ui16 GetPort() const {
            return Port;
        }
        const TString& GetHost() const {
            return Host;
        }

        TSearchReply SendAsyncQuery(const TQuery& query, TDuration timeout = DefaultTimeout, bool alwaysUseDataAsResponse = false) const;
        TSearchReply SendQuery(
            const TQuery& query,
            TDuration timeout = DefaultTimeout,
            const TSelfFlushLogFramePtr& logFrame = nullptr,
            const TString& id = TString()) const;

    private:
        class TSearchContext {
        public:
            THttpFetcher& Fetcher;
            size_t Hash;
            size_t Index;
            TSimpleTimer Timer;

            TSearchContext(THttpFetcher& fetcher, size_t hash, size_t index)
                : Fetcher(fetcher)
                , Hash(hash)
                , Index(index)
            {}
        };

        using TSearchContextPtr = TSimpleSharedPtr<TSearchContext>;

        TSearchContextPtr CreateSearchContext(THttpFetcher& fetcher, const TQuery& query, TDuration timeout, const TString& id) const;
        TSearchReply GetSearchReply(const TSearchContextPtr& context) const;
    };

    class TAsyncSearchClient : public TSearchClient {
    public:
        class ICallback {
        public:
            virtual ~ICallback() = default;
            virtual void OnReply(TSearchReply reply) = 0;
            virtual bool ReplyIsOk(const NRTYServer::TQueryInfo& /*info*/, const TSearchReply& /*reply*/) const {
                return true;
            }

            virtual bool OnResend(TSearchReply reply) {
                Y_UNUSED(reply);
                return false;
            };
        };

    public:
        TAsyncSearchClient(const TString& serviceName, const TString& host, const ui16 port, ui32 attemps, const TVector<TDuration>& resendDurations = {}, std::shared_ptr<NSaas::TTvmManager> tvmManager = nullptr);
        void Stop();
        void Start(ui32 threads);
        void AddQuery(const TQuery& query, THolder<ICallback>&& callback, TDuration timeout = DefaultTimeout, bool alwaysUseDataAsResponse = false) const;
        ui32 GetInFly() const {
            return Requester->QueueSize();
        }

    private:
        class THandleListener;

    private:
        mutable THolder<NRTYServer::TMultiRequesterBase> Requester;
    };
}

namespace NRTY { // OBSOLETE
    using TSortPolicy = NSaas::TSortPolicy;
    using TSearchReply = NSaas::TSearchReply;
    using TSearchClient = NSaas::TSearchClient;
    using TQuery = NSaas::TQuery;
    using TGrouping = NSaas::TGrouping;
}
