#pragma once

#include "configs.h"
#include "searchmap_storage.h"

#include <saas/library/persqueue/writer/writer.h>
#include <saas/library/searchmap/searchmap.h>
#include <saas/util/logging/messages.h>

#include <library/cpp/http/misc/httpcodes.h>
#include <library/cpp/http/server/http_ex.h>

#include <util/system/thread.h>
#include <util/generic/variant.h>

namespace NSaasPush {
    using TPQLibPtr = TAtomicSharedPtr<NPersQueue::TPQLib>;

    struct TWriteRequestInfo;

    class TServiceWriter {
    public:
        TServiceWriter(
            const TServiceInfo& config,
            NSaas::TPQLibPtr pqLib,
            std::shared_ptr<NPersQueue::ICredentialsProvider> credentialsProvider,
            const NSearchMapParser::TSearchMap& searchMap
        );

        std::pair<NSaas::TPersQueueWriter::TWriteResult, TActionPtr> Write(const TString& data);

        NJson::TJsonValue GetStatus() const;

        bool GetLoggingEnabled() const;
        const TString& GetServiceName() const;
        const TString& GetCtype() const;
        const TString& GetAlias() const;

        void UpdateSearchMap(const NSearchMapParser::TSearchMap& searchMap);

    private:
        const TServiceInfo& Config;
        TDataParser Parser;

        THolder<NSaas::TPersQueueWriter> PQWriter;
    };

    using TServiceWriterPtr = TAtomicSharedPtr<TServiceWriter>;

    struct IServer : public THttpServer, THttpServer::ICallBack {
    public:
        using THttpServer::THttpServer;
    public:
        virtual void ReopenLog() = 0;
        virtual NJson::TJsonValue GetStatus() const = 0;
        void WaitStopped();
        void Shutdown();
        virtual void BeforeShutdown();
        virtual void OnShutdown();
        virtual void AfterShutdown();

    protected:
        NAtomic::TBool NeedStop;

    private:
        TManualEvent Stopped;
    };

    class TWriter;
    class IControllerServer;

    class IRequest : public THttpClientRequestExtImpl<TServerRequestData> {
    protected:
        TString GetPostData() const;
    };

    class TWriteRequest: public IRequest {
    public:
        TWriteRequest(TWriter& writer);
        bool Reply(void*) override;
    private:
        TWriter& Writer;
    };

    class TControllerRequest: public IRequest {
    public:
        TControllerRequest(IControllerServer& owner);
        bool Reply(void*) override;
    private:
        IControllerServer& Owner;
    };

    class TWriter : public IServer {
    public:
        TWriter(
            const TWriterConfig& config,
            TPQLibPtr pqLib,
            TSearchMapStorage& searchMapStorage,
            TLog& tvmLog
        );
        ~TWriter();

        bool IsInitialized() const;

        NSaas::TPersQueueWriter::TWriteResult Write(const TString& alias, const TWriteRequestInfo& request);
        NSaas::TPersQueueWriter::TWriteResult Write(const TString& alias, const TString& data);

        void ReopenLog() override;
        NJson::TJsonValue GetStatus() const override;

        void OnShutdown() override;

        TClientRequest* CreateClient() override;

        std::shared_ptr<NPersQueue::ICredentialsProvider> GetAnyCredentialsProvider() const;
        std::shared_ptr<NPersQueue::ICredentialsProvider> GetCredentialsProvider(const TString& alias) const;
    private:
        std::variant<NSaas::TPersQueueWriter::TWriteResult, TServiceWriterPtr> GetWriter(const TString& alias);
        void InitTvm();
        void Init();
        void SetInitializationStatus(bool finished, TStringBuf comment);
        void LogAndPushMetrics(
                const TWriteRequestInfo& request,
                const NSaas::TPersQueueWriter::TWriteResult& writeResult,
                const TServiceWriterPtr& service,
                const TActionPtr& action);

    private:
        THolder<TThread> InitializationThread;
        NAtomic::TBool Initialized;
        TString LastError;

        THashMap<TString, std::shared_ptr<NPersQueue::ICredentialsProvider>> CredentialsProviders;
        NSaas::TPQLibPtr PQLib;
        THashMap<TString, TServiceWriterPtr> ServiceWriters;

        TSearchMapStorage& SearchMapStorage;

        TLog& TvmLog;
        TLog MessagesLog;

        const TWriterConfig& Config;
    };

    class IControllerServer
        : public IServer
        , public IMessageProcessor
    {
    public:
        IControllerServer(const TControllerConfig& config);
        ~IControllerServer();

        TClientRequest* CreateClient() override;

        virtual const TConfig& GetConfig() const = 0;
        TString GetStringConfig() const;
        NJson::TJsonValue GetJsonConfig() const;

        // IMessageProcessor
        TString Name() const override;
        bool Process(IMessage* message) override;
    };

    class TDaemon : public IControllerServer
    {
    public:
        TDaemon(const TConfig& config);

        void BeforeShutdown() override;
        void ReopenLog() override;
        NJson::TJsonValue GetStatus() const override;

        const TConfig& GetConfig() const override;

    private:
        TLog LogbrokerLog;
        TLog TvmLog;
        TSearchMapStorage SearchMapStorage;
        const TConfig& Config;

        TPQLibPtr PQLib;
        THolder<TWriter> Writer;
        THolder<NSaas::ITelemetry> Telemetry;
    };

    TConfig ReadConfig(const TString& path, ui16 basePort = 0, ui16 controllerPort = 0);
}
