#pragma once

#include "base_http_client.h"
#include "messages.h"

#include <saas/library/daemon_base/module/module.h>
#include <saas/library/daemon_base/metrics/servicemetrics.h>
#include <saas/library/daemon_base/common/common.h>
#include <saas/library/daemon_base/protos/status.pb.h>
#include <saas/library/sharding/sharding.h>

#include <saas/util/transaction.h>
#include <saas/library/daemon_base/actions_engine/async_task_executor.h>

#include <library/cpp/mediator/global_notifications/system_status.h>
#include <library/cpp/http/misc/httpcodes.h>
#include <library/cpp/http/server/http.h>
#include <library/cpp/object_factory/object_factory.h>
#include <library/cpp/regex/pcre/regexp.h>

#include <library/cpp/cgiparam/cgiparam.h>
#include <util/system/event.h>
#include <util/stream/file.h>

class IServer {
public:
    virtual ~IServer() {}
    virtual void Run() = 0;
    virtual void Stop(ui32 rigidStopLevel, const TCgiParameters* cgiParams) = 0;

    virtual void CreateModulesContext(IDaemonModule::TStartContext&) {}

    template<class T>
    inline T& GetMeAs() {
        T* result = dynamic_cast<T*>(this);
        CHECK_WITH_LOG(result);
        return *result;
    }

    template<class T>
    inline const T& GetMeAs() const {
        const T* result = dynamic_cast<const T*>(this);
        CHECK_WITH_LOG(result);
        return *result;
    }
};

class TExtendedServer {
private:
    THolder<TDaemonModules> Modules;
    THolder<IServer> Server;

public:
    TExtendedServer(IServer* server, const IServerConfig& config)
        : Modules(new TDaemonModules(config))
        , Server(server)
    {
    }

    IServer& GetLogicServer() {
        return *Server;
    }

    void Run() {
        INFO_LOG << "Starting server..." << Endl;
        Server->Run();
        INFO_LOG << "Starting modules..." << Endl;
        IDaemonModule::TStartContext ctx;
        Server->CreateModulesContext(ctx);
        Modules->Start(ctx);
        NOTICE_LOG << "Starting finished" << Endl;
    }

    void Stop(ui32 rigidStopLevel = 0, const TCgiParameters* cgiParams = nullptr) {
        INFO_LOG << "Stopping modules..." << Endl;
        IDaemonModule::TStopContext context(rigidStopLevel, cgiParams);
        Modules->Stop(context);
        INFO_LOG << "Stopping server..." << Endl;
        Server->Stop(rigidStopLevel, cgiParams);
        INFO_LOG << "Stopping finished" << Endl;
    }

};

class TController : public THttpServer, public THttpServer::ICallBack, public IMessageProcessor {
public:
    template<class T, class G, class L>
    class TGuardPtr: public TPointerBase<TGuardPtr<T, G, L>, T> {
    public:
        TGuardPtr(const TAtomicSharedPtr<T>& pointer, const L& lock)
            : Lock(lock)
            , Guard(Lock)
            , Pointer(pointer)
        {}

        TGuardPtr(const TGuardPtr<T, G, L>& other)
            : Lock(other.Lock)
            , Guard(Lock)
            , Pointer(other.Pointer)
        {}

        inline T* Get() const {
            return Pointer.Get();
        }
    private:
        const L& Lock;
        G Guard;
        TAtomicSharedPtr<T> Pointer;
    };

    typedef TGuardPtr<TExtendedServer, TGuardIncompatibleAction, ITransaction> TGuardServer;
    typedef TGuardPtr<IServerConfig, TGuardIncompatibleAction, ITransaction> TGuardConfig;
    typedef TGuardPtr<TDaemonConfig, TGuardIncompatibleAction, ITransaction> TGuardDaemonConfig;

    class TClient : public TBaseHttpClient {
    public:
        TClient(TController& owner);
        virtual bool Reply(void* ThreadSpecificResource) override;

        inline const TServerRequestData& GetRD() const {
            return RD;
        }
        inline TBlob& PostBuffer() {
            return Buf;
        }

        virtual void GetMetrics(IOutputStream& out) const override {
            out << "HTTP/1.1 " << HttpCodeStrEx(HTTP_OK) << "\r\n\r\n";
            ::CollectMetrics(out);
        }
        virtual ~TClient() {
            AtomicDecrement(Owner.ClientsCount);
        }
    private:
        void LogRequest();

    protected:
        TController& Owner;
    };

    class TCommandProcessor : public TAsyncTaskExecutor::TTask {
    public:
        typedef THolder<TCommandProcessor> TPtr;
        typedef NObjectFactory::TObjectFactory<TCommandProcessor, TString> TFactory;

        static void RegisterCommonCommands(TFactory& factory);

    public:
        TCommandProcessor()
            : TTask("cc_" + Now().ToString() + "_" + ToString((ui64)this))
        {}

        virtual ~TCommandProcessor() override {}

        virtual TString GetInfoMessage() const = 0;
        void Init(TController& owner, TController::TClient* requester, bool async);
        void Write(const TString& key, const NJson::TJsonValue& value) {
            GetReply()->InsertValue(key, value);
        }

        virtual void Process(void* ts) override;

    protected:
        virtual TString GetName() const = 0;
        virtual void DoProcess() = 0;
        TGuardServer GetServer(bool mustBe = true);

        ui32 ParseRigidStopLevel(const TCgiParameters& params) const {
            if (params.Find("rigid_level") != params.end()) {
                return FromString<ui32>(params.Get("rigid_level"));
            }
            if (params.Find("rigid") != params.end() && FromString<bool>(params.Get("rigid"))) {
                return Max<ui32>();
            }
            return 0;
        }

        const TCgiParameters& GetCgi() const {
            return Async ? Cgi : Requester->GetRD().CgiParam;
        }

        TStringBuf GetPostBuffer() {
            return Async ? TStringBuf(Buf.Data(), Buf.Size()) : TStringBuf(Requester->PostBuffer().AsCharPtr(), Requester->PostBuffer().Size());
        }

        TController* Owner = nullptr;
        bool Async = false;

    private:
        TController::TClient* Requester = nullptr;
        TCgiParameters Cgi;
        TBuffer Buf;
    };

    typedef NObjectFactory::TObjectFactory<TController::TCommandProcessor, TString> TCommandFactory;

    class IServerDescriptor {
    public:
        virtual ~IServerDescriptor() {}
        virtual IServerConfig* CreateConfig(const TServerConfigConstructorParams& constParams) const = 0;
        virtual IServer* CreateServer(const IServerConfig& config) const = 0;
        virtual THolder<TCollectServerInfo> CreateInfoCollector(bool isHumanReadable) const = 0;
        virtual TController::TCommandProcessor* CreateCommandProcessor(const TString& command) const = 0;
        virtual TSet<TString> GetServerCommands() const = 0;
        virtual TString GetConfigString(TController& owner) const = 0;
        virtual bool CanSetConfig(TController::TGuardServer& gs) const = 0;
    };

    struct TRestartServerStatistics {
        ui64 StopTimeMilliseconds;
        ui64 StartTimeMilliseconds;
    };

public:
    void StoreConfigs() const;
    bool RestoreConfigs() const;
    NController::TSlotStatus GetServerDataStatus() const;
    bool SetServerDataStatus(const TString& info, const TSystemStatusMessage::ESystemStatus status, bool force = false) const;
    bool ClearServerDataStatus(bool force = false) const;

public:
    TController(TServerConfigConstructorParams& params, const IServerDescriptor& descriptor);
    virtual ~TController() override;

    // THttpServer::ICallBack
    virtual TClientRequest* CreateClient() override {
        return new TClient(*this);
    }

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

    void RestartServer(ui32 rigidStopLevel = 0, const TCgiParameters* cgiParams = nullptr, bool reread = false, TRestartServerStatistics* statistics = nullptr, const TDuration& sleepTimeout = TDuration());
    void DestroyServer(ui32 rigidStopLevel = 0, const TCgiParameters* cgiParams = nullptr, TRestartServerStatistics* statistics = nullptr, const TDuration& sleepTimeout = TDuration());
    void Reset(ui32 rigidStopLevel, const TCgiParameters* cgiParams = nullptr, TRestartServerStatistics* statistics = nullptr, const TDuration& sleepTimeout = TDuration(), bool checkConfig = false);
    bool SetConfigFields(const TCgiParameters& cgiParams, NJson::TJsonValue& result);
    bool GetConfigFields(const TCgiParameters& cgiParams, NJson::TJsonValue& result);
    bool CanSetConfig(bool& isExists);
    void Run();
    void Stop(ui32 rigidStopLevel);

    TGuardServer GetServer() {
        return TGuardServer(Server, ServerTransaction);
    }

    TGuardConfig GetConfig() {
        return TGuardConfig(Config, ConfigMutex);
    }

    TGuardDaemonConfig GetDaemonConfig() const {
        return TGuardDaemonConfig(ConfigParams.Daemon, ConfigMutex);
    }

    TLog& GetLog() {
        return Log;
    }

    const TString& GetConfigText() const {
        return ConfigParams.Text;
    }

    void WaitStopped() {
        Stopped.Wait();
    }

    NController::TServerStatusByController GetStatus() const {
        TReadGuard g(StatusMutex);
        return Status;
    }

    void SetStatus(NController::TServerStatusByController status) {
        TWriteGuard g(StatusMutex);
        Status = status;
    }

    void FillConfigsHashes(NJson::TJsonValue& result, TString configPath = TString(), const TRegExMatch* filter = nullptr) const ;

    TFsPath GetFullPath(const TString& path, const TString& root = Default<TString>()) {
        if (NoFileOperations)
            ythrow yexception() << "file operations locked";
        TFsPath fsPath(path);
        if (fsPath.IsAbsolute())
            return fsPath.Fix().GetPath();
        if (!root)
            return (TFsPath(ConfigParams.Daemon->GetController().ConfigsRoot) / fsPath).Fix();
        return (TFsPath(root) / fsPath).Fix();
    }

    TServerInfo GetServerInfo(bool controllerOnly, bool isHumanReadable);

    TSet<TString> GetCommands() const {
        return Descriptor.GetServerCommands();
    }

    TString GetCommandInfo(const TString &command) const {
        TCommandProcessor::TPtr object(Descriptor.CreateCommandProcessor(command));
        if (!object) {
            return "Unknown command";
        }
        return object->GetInfoMessage();
    }

    void WriteFile(const TString& filename, TStringBuf data, const TString& root = Default<TString>()) {
        TFsPath path(GetFullPath(filename, root));
        {
            TGuard<TMutex> g(MutexMakeDirs);
            path.Parent().MkDirs();
        }
        TFixedBufferFileOutput fo(path.GetPath());
        fo.Write(data.data(), data.size());
    }

    void ExecuteAsyncCommand(TAsyncTaskExecutor::TTask::TPtr worker, NJson::TJsonValue& result) {
        AsyncExecuter.AddTask(worker, result);
    }

    void GetAsyncCommondInfo(const TString& taskId, TAsyncTaskExecutor::TTask& requester) {
        TAsyncExecuter::TTask::TPtr task = AsyncExecuter.GetTask(taskId);
        if (!task)
            requester.GetReply()->InsertValue("result", NJson::JSON_MAP).InsertValue("task_status", ToString(TAsyncTaskExecutor::TTask::stsNotFound));
        else
            task->FillInfo(requester.GetReply()->InsertValue("result", NJson::JSON_MAP));
    }

    bool GetNoFileOperations() const {
        return NoFileOperations;
    }

    void SetNoFileOperations(bool value) {
        NoFileOperations = value;
    }

    virtual void CheckConfig(IServerConfig& /*config*/, bool /*onStart*/) const {
    }

    bool GetMustBeAlive() const {
        return MustByAlive;
    }

    bool DownloadConfigsFromDM(const TDaemonConfig::TControllerConfig::TDMOptions& dmOptions, const TString& version = TString(), const NSaas::TSlotInfo* slotInfo = nullptr);
    TString GetConfigDirMD5() const;

private:
    TMutex MutexMakeDirs;

    class TAsyncExecuter : public TAsyncTaskExecutor {
    public:
        TAsyncExecuter(ui32 threads)
            : TAsyncTaskExecutor("CtrlAsyncExec")
            , Threads(threads)
        {}

        virtual ui32 GetThreadsCountForTasks() const override {
            return Threads;
        }
    private:
        ui32 Threads;
    };

    virtual void StopServer(ui32 /*rigidStopLevel*/, const TCgiParameters* /*cgiParams*/) {
        Server->Stop();
    }

    void CreatePath(const TString& object);
    void LockSelfMemory() noexcept;

protected:
    TAtomicSharedPtr<IServerConfig> Config;
    TAtomicSharedPtr<TExtendedServer> Server;
    ITransaction ServerTransaction;
    ITransaction ConfigMutex;
    TRWMutex StatusMutex;
    TServerConfigConstructorParams& ConfigParams;
    NController::TServerStatusByController Status;
    TSystemEvent Stopped;
    TInstant ControllerStart;
    TInstant ServerStart;
    TAsyncExecuter AsyncExecuter;
    TLog Log;
    bool NoFileOperations;
    const IServerDescriptor& Descriptor;
    bool FirstStart;
    bool MustByAlive;

    struct TConfigHashes;
    THolder<TConfigHashes> ConfigHashes;
    TAtomic ClientsCount = 0;
};

namespace {
    template<class TConfig, bool HasToString>
    struct TConfigToString {
        static TString Get(TController& owner) {
            return owner.GetConfigText();
        }
    };

    template<class TConfig>
    struct TConfigToString <TConfig, true> {
        static TString Get(TController& owner) {
            TController::TGuardConfig g = owner.GetConfig();
            if (!!g)
                return g->GetMeAs<TConfig>().ToString();
            else
                return owner.GetConfigText();
        }
    };

    Y_HAS_MEMBER(ToString);

    template<class TBaseServer, bool HasCanSetConfig>
    struct TCanSetConfig {
        static bool Get(TController::TGuardServer& /*gs*/) {
            return true;
        }
    };

    template<class TBaseServer>
    struct TCanSetConfig <TBaseServer, true> {
        static bool Get(TController::TGuardServer& gs) {
            return gs->GetLogicServer().GetMeAs<TBaseServer>().CanSetConfig();
        }
    };
    Y_HAS_MEMBER(CanSetConfig);
}

template <class TBaseServer>
class TServerDescriptor : public TController::IServerDescriptor {
public:
    typedef typename TBaseServer::TConfig TConfig;
    typedef typename TBaseServer::TInfoCollector TInfoCollector;
    typedef NObjectFactory::TParametrizedObjectFactory<TController::TCommandProcessor, TString, TBaseServer*> TCommandFactory;

    virtual IServerConfig* CreateConfig(const TServerConfigConstructorParams& constParams) const override {
        return new TConfig(constParams);
    }

    virtual IServer* CreateServer(const IServerConfig& config) const override {
        return new TBaseServer(config.GetMeAs<TConfig>());
    }

    virtual THolder<TCollectServerInfo> CreateInfoCollector(bool isHumanReadable) const override {
        return MakeHolder<TInfoCollector>(isHumanReadable);
    }

    virtual TController::TCommandProcessor* CreateCommandProcessor(const TString& command) const override {
        TController::TCommandProcessor* result = TController::TCommandProcessor::TFactory::Construct(command);
        if (!result)
            result = TCommandFactory::Construct(command, nullptr);
        return result;
    }

    virtual TSet<TString> GetServerCommands() const override {
        TSet<TString> commands;
        TCommandFactory::GetRegisteredKeys(commands);
        return commands;
    }

    virtual TString GetConfigString(TController& owner) const override {
        return TConfigToString<TConfig, THasToString<TConfig>::value>::Get(owner);
    }

    virtual bool CanSetConfig(TController::TGuardServer& gs) const override {
        return TCanSetConfig<TBaseServer, THasCanSetConfig<TBaseServer>::value>::Get(gs);
    }
};

#define DEFINE_CONTROLLER_COMMAND(server, name, description) \
class T ## name ## server ## Command : public TController::TCommandProcessor {\
public:\
    T ## name ## server ## Command(server*)\
    {}\
    virtual TString GetInfoMessage() const { return description ; }\
protected:\
    virtual TString GetName() const { return #name ; }\
    virtual void DoProcess();\
};\
    TServerDescriptor<server>::TCommandFactory::TRegistrator<T ## name ## server ## Command> Registrator ## name ## server(#name);\
    void T ## name ## server ## Command::DoProcess() {

#define END_CONTROLLER_COMMAND }
