#pragma once
#include <saas/deploy_manager/scripts/common/global_names/global_names.h>
#include <saas/deploy_manager/storage/abstract.h>
#include <saas/library/daemon_base/actions_engine/controller_script.h>
#include <saas/util/tasks_graph/script.h>
#include <library/cpp/cgiparam/cgiparam.h>
#include <util/system/event.h>
#include <util/system/hostname.h>
#include <util/thread/pool.h>

namespace NRTYDeploy {

    class TClusterAsyncAction : public NDaemonController::TSimpleAsyncAction {
    public:

        TClusterAsyncAction() {}

        TClusterAsyncAction(NDaemonController::TAsyncPolicy policy)
            : NDaemonController::TSimpleAsyncAction(policy) {

        }

        TClusterAsyncAction(const TString& waitActionName)
            : NDaemonController::TSimpleAsyncAction(waitActionName)
        {}

        virtual TString GetCustomUriStart() const = 0;

        virtual TString GetCustomUri() const {
            if (!HasFlag(asStarted))
                return GetCustomUriStart();
            else
                return "deploy_info";
        }

        virtual TString DoBuildCommandWait() const {
            return "id=" + IdTask + "&wait_only=yes";
        }

        void DoInterpretResultStart(const TString& result) {
            TWriteGuard g(RWMutex);
            VERIFY_WITH_LOG(!IdTask, "IdTask must be empty, but %s, %s", IdTask.data(), ActionName().data());
            TStringInput(result).ReadLine(IdTask);
            if (AsyncPolicy() == NDaemonController::apStart) {
                if (!!IdTask)
                    Success(IdTask);
                else
                    Fail("Empty reply");
            }
        }

        void DoInterpretResultWait(const TString& result) {
            CHECK_WITH_LOG(!!IdTask);
            TStringInput si(result);
            NJson::TJsonValue value;
            if (NJson::ReadJsonTree(&si, &value)) {
                NJson::TJsonValue::TMapType map;
                if (!value.GetMap(&map)) {
                    Fail("Incorrect reply format - not map");
                }
                else {
                    if (map.find("is_finished") == map.end()) {
                        Fail("Incorrect reply format - is_finished absent");
                    }
                    else {
                        if (map["is_finished"].GetBooleanRobust()) {
                            if (map["result_code"].GetStringRobust() == "FINISHED")
                                Success("OK");
                            else
                                Fail("Fail");
                        }
                    }
                }
            }
            else {
                Fail("Incorrect reply: " + result);
            }
        }

    };

#define WAIT_ACTION_NAME "WAIT_ACTION"

    class TWaitAsyncAction : public TClusterAsyncAction {
    public:
        static TFactory::TRegistrator<TWaitAsyncAction> Registrator;

        TWaitAsyncAction() {}

        TWaitAsyncAction(const TString& waitActionName)
            : TClusterAsyncAction(waitActionName)
        {}

        virtual TString GetCustomUriStart() const {
            FAIL_LOG("Incorrect behaviour");
            return "deploy_info";
        }

        NJson::TJsonValue DoSerializeToJson() const {
            return NJson::JSON_MAP;
        }

        void DoDeserializeFromJson(const NJson::TJsonValue& /*json*/) {
        }

        virtual TString DoBuildCommandStart() const {
            FAIL_LOG("Incorrect behaviour");
            return "deploy_info";
        }

        virtual TString DoBuildCommandWait() const {
            if (!IdTask) {
                Fail("Can't read id_task: " + WaitActionName);
                return "";
            }
            else
                return "id=" + IdTask;
        }

        virtual TString ActionName() const {
            return WAIT_ACTION_NAME;
        }
    };

    class TClusterTaskId {
    protected:
        TString Service;
        TString CType;
        TString TaskType;
        TString ServiceType;
    public:

        TClusterTaskId() {
        }

        TString GetLockName() const {
            return Service + "_" + CType;
        }

        const TString& GetService() const {
            return Service;
        }

        const TString& GetCType() const {
            return CType;
        }

        bool IsCorrect() const {
            return !!CType && !!Service && !!TaskType;
        }

        ui64 GetTimestamp() const {
            Y_VERIFY(GetTSDiscr() > 0);
            return Now().Seconds() / GetTSDiscr();
        }

    protected:
        virtual ui64 GetTSDiscr() const {
            return 10;
        }

        TString BuildId(const TString& hash) const;
        TString BuildDescription(const TString& hash) const;
        static bool ExtractTimestampFromId(const TString& id, TInstant& result);
    };

    class TClusterTask : public TClusterTaskId, public IObjectInQueue, public NDaemonController::IControllerAgentCallback {
    public:
        enum TCurrentStatus { ctsFailed, ctsNormally, ctsLoaded, ctsConstructed, ctsSaved, ctsEnqueued, ctsSuspended };
        enum TResultStatus { rsSuccess, rsInProgress, rsDuplication, rsSaved, rsEnqueued, rsSuspended, rsFailedOnProcess, rsFailedOnConstruction, rsLoadingFailed, rsSavingFailed, rsCantEnqueue, rsUndefined };
        typedef TAtomicSharedPtr<TClusterTask> TPtr;
    protected:
        virtual void DoBuildTask();
    private:
        NRTYScript::TRTYClusterScript::TPtr Script;
        mutable TString Id;
        mutable TCurrentStatus CurrentStatus;
        mutable TResultStatus ResultStatus;
        TString ExecutorHost;
        mutable TString ResultStatusInfo;
        mutable TString CurrentProgress;
        TRTYMtpQueue SaveQueue;
        ui32 ExecuteScriptThreads;
        TMutex MutexLocks;
        TMap<TString, TVector<NSaas::TAbstractLock::TPtr>> ActionLocks;
        bool CreateSuspended;
    protected:
        NRTYDeploy::ICommonData* CommonData;

    public:

        struct TCgiContext {
            TString CType;
            TString Service;
            TString Parent;
            TString ServiceType;
            bool CreateSuspended;
            static TCgiContext Parse(const TCgiParameters& cgi, const TString& defaultServiceType = RTYSERVER_SERVICE);
        };

        TClusterTask(const TCgiContext& context, NRTYDeploy::ICommonData* commonData, const TString& taskType) {
            ExecuteScriptThreads = 1 << 10;
            CurrentStatus = ctsNormally;
            ResultStatus = rsEnqueued;
            Service = context.Service;
            CType = context.CType;
            CommonData = commonData;
            ServiceType = context.ServiceType;
            Script.Reset(new NRTYScript::TRTYClusterScript(context.Parent + "_" + taskType + "_" + Service + "_" + CType, context.Parent));
            TaskType = taskType;
            CreateSuspended = context.CreateSuspended;
        }

        TClusterTask(const TString& id, NRTYDeploy::ICommonData* commonData, ui32 executeScriptThreads);

        ~TClusterTask() {
            SaveQueue.Stop();
        }

        virtual bool IsExecutable() const override;

        void BuildTask();
        void EnqueueTask(bool forceSave = true);

        void SetStatus(TCurrentStatus cStatus, TResultStatus rStatus, const TString& message = "") const;

        void InitExecutorHost();

        TString GetNotificationInfo() const {
            NJson::TJsonValue value = Serialize();
            TStringStream ss;
            NJson::WriteJson(&ss, &value, true, true, false);
            return ss.Str();
        }

        TString GetNotificationShortInfo() const {
            return Id;
        }

        const TString& GetStatusInfo() const {
            return ResultStatusInfo;
        }

        TResultStatus GetResultStatus() const {
            return ResultStatus;
        }

        TCurrentStatus GetCurrentStatus() const {
            return CurrentStatus;
        }

        NRTYScript::TRTYClusterScript::TPtr& GetScript() {
            return Script;
        }

        virtual void OnAfterActionStep(const NDaemonController::IControllerAgentCallback::TActionContext& context, NDaemonController::TAction& action) override;

        const TString& GetId() const;

        virtual void OnBeforeActionStep(const NDaemonController::IControllerAgentCallback::TActionContext& context, NDaemonController::TAction& action) override;
        virtual void OnPing(const NDaemonController::IControllerAgentCallback::TActionContext& context, NDaemonController::TAction& action) override;

        NJson::TJsonValue Serialize(bool forLogging = false) const;
        bool IsFinished() const;
        bool IsFailed() const;
        void Save(bool atFirst) const;
        void Load(const TString& id);

        static void RemoveExpiredTasks(NRTYDeploy::ICommonData* commonData, const TDuration& lifetime, TManualEvent& active);

        void Process(void* /*ThreadSpecificResource*/) override;

        virtual TString GetTaskType() const {
            return TaskType;
        };
    };

    TString RSToString(TClusterTask::TResultStatus status);
    TClusterTask::TResultStatus RSFromString(const TString& status);
}
