#include "dbg_helper.h"

#include <saas/rtyserver_jupi/library/extbuilder/log_impl.h>
#include <saas/rtyserver_jupi/library/extbuilder/info.h>
#include <library/cpp/logger/global/global.h>
#include <library/cpp/mediator/global_notifications/system_status.h>

#include <util/folder/path.h>
#include <util/stream/file.h>
#include <util/string/strip.h>
#include <util/system/fs.h>
#include <util/datetime/base.h>

namespace NFusion {
    class TExtBuilderDebugHelper::TImpl {
    public:
        TString StatePrefix;
        TAssertPolicy AssertPolicy;

    public:
        void Init(const TString& prefix, const TAssertPolicy& assertPolicy) {
            if (prefix) {
                TFsPath(prefix).Parent().MkDirs();
            }
            StatePrefix = prefix;
            AssertPolicy = assertPolicy;
        }

        inline bool PersistencyOn() const {
            return !!StatePrefix;
        }

    private:
        static TFsPath GetStateFile(const TString& prefix, const TStringBuf& builderStatName, const TStringBuf& suffix) {
            return TFsPath(prefix + builderStatName + suffix);
        }

        static TVector<TString> GetAllNames(const TString& prefix, const TStringBuf& suffix) {
            TFsPath fsPrefix(prefix);
            TFsPath stateDir;
            TString statePrefix;
            if (fsPrefix.IsDirectory() || prefix.EndsWith(TPathSplit::MainPathSep)) {
                stateDir = fsPrefix;
            } else {
                stateDir = fsPrefix.Parent();
                statePrefix = fsPrefix.Basename();
            }

            TVector<TString> result;
            TVector<TFsPath> files;
            stateDir.List(files);
            for (const TFsPath& file : files) {
                const TString& basename = file.Basename();
                if (basename.StartsWith(statePrefix) && basename.EndsWith(suffix) && !file.IsDirectory()) {
                    result.push_back(basename.substr(statePrefix.size(), basename.size() - statePrefix.size() - suffix.size()));
                }
            }
            return result;
        }

        static constexpr TStringBuf MergerFlag = ".builder_active";

    public:
        bool HasIncompleteMergeFlags() {
            if (!PersistencyOn())
                return false;

            return !GetAllNames(StatePrefix, MergerFlag).empty();
        }

        void ClearIncompleteMergeFlags() {
            if (!PersistencyOn())
                return;

            auto statNames = GetAllNames(StatePrefix, MergerFlag);
            for (const TString& builderStatName : statNames) {
                GetStateFile(StatePrefix, builderStatName, MergerFlag).DeleteIfExists();
            }
        }

        void SetIncompleteMergeFlag(const TStringBuf& builderStatName, const TString& builderTaskPbTxt) {
            if (!PersistencyOn() || !builderStatName || !builderTaskPbTxt)
                return;

            Y_ASSERT(builderTaskPbTxt && TFsPath(builderTaskPbTxt).Exists());

            TFsPath stateFile = GetStateFile(StatePrefix, builderStatName, MergerFlag);
            TFileOutput output(stateFile);
            output.SetFinishPropagateMode(true);
            output << builderTaskPbTxt << Endl;
            output.Finish();
        }

        void DropIncompleteMergeFlag(const TStringBuf& builderStatName) {
            if (!PersistencyOn() || !builderStatName)
                return;

            GetStateFile(StatePrefix, builderStatName, MergerFlag).DeleteIfExists();
        }

        TFsPath GetIncompleteMergeTask(const TStringBuf& builderStatName) {
            if (!PersistencyOn() || !builderStatName)
                return TFsPath();

            TFsPath input(GetStateFile(StatePrefix, builderStatName, MergerFlag));
            TFsPath builderTaskPbTxt(StripString(TFileInput(input).ReadAll()));
            return builderTaskPbTxt.Exists() ? builderTaskPbTxt : TFsPath();
        }

        TVector<TFsPath> GetIncompleteMergeTasks() {
            TVector<TFsPath> result;
            if (!PersistencyOn())
                return result;

            auto statNames = GetAllNames(StatePrefix, MergerFlag);
            for (const TString& builderStatName : statNames) {
                TFsPath builderTaskPbTxt = GetIncompleteMergeTask(builderStatName);
                if (builderTaskPbTxt.IsDefined()) {
                    result.emplace_back(std::move(builderTaskPbTxt));
                }
            }
            return result;
        }
    };

    namespace {
        void SetFailedIndex(const TVector<TFsPath>& builderTaskPbTxts, bool waitDebugger) {
            for (auto&& txt : builderTaskPbTxts) {
                FATAL_LOG << "Failed ExtBuilder task found, stopping: " << txt << Endl;
            }

            if (waitDebugger) {
                // This only works if OnFail: STOP
                WARNING_LOG << "The failed index is in memory. Will sleep for 3 minutes (awating debugger?)" << Endl;
                Sleep(TDuration::Minutes(3));
            }

            AbortFromCorruptedIndex("Failed ExtBuilder task found");
        }

        static bool IsShmStop(const TFsPath& builderTaskPbTxt) {
            const bool isMemory = builderTaskPbTxt.IsSubpathOf("/run/shm");
            return isMemory;
        }

        static bool IsConfigured(const NRtDoc::TBuilderTask& task) noexcept {
            const bool taskIsConfigured = task.HasOutput() && task.GetOutput().GetTempDir() && task.HasId() && task.GetId().GetBuilderStatName();
            return taskIsConfigured;
        }

        static TString GetStatName(const NRtDoc::TBuilderTask& task) {
            return IsConfigured(task) ? task.GetId().GetBuilderStatName() : Default<TString>();
        }

        static TString GetShortStatName(const NRtDoc::TBuilderTask& task) {
            if (task.HasOutput() && task.GetRealm() == "Realtime") {
                return "RT";
            }

            TString shortName = IsConfigured(task) ? task.GetId().GetDiagName() : Default<TString>();
            if (!shortName && task.GetOutput().HasTrgDir()) {
                // leave last 4 digits in index dir
                const size_t desiredLen = 4;
                shortName = TFsPath(task.GetOutput().GetTrgDir()).Basename();
                constexpr TStringBuf mergeSuff = "_merge";
                size_t pRight = shortName.size();
                if (shortName.EndsWith(mergeSuff))
                    pRight -= mergeSuff.Size();
                size_t pLeft = 0;
                if (pRight > desiredLen)
                    pLeft = pRight - desiredLen;
                return shortName.substr(pLeft, pRight - pLeft);
            }
            return Default<TString>();
        }

        TFsPath GetPbTxt(const NRtDoc::TBuilderTask& task) {
            if (!IsConfigured(task))
                return TFsPath();

            return TFsPath(task.GetOutput().GetTempDir()) / "builder_task.pb.txt";
        }
    }

    TExtBuilderComponentLogger::TExtBuilderComponentLogger(TString shortDiagName) {
        if (shortDiagName) {
            TStringStream prefix;
            prefix << "[" << shortDiagName << "] ";
            ShortDiagName = prefix.Str();
        }
        ComponentLog = MakeHolder<TLog>(MakeHolder<NLoggingImpl::TComponentLogBackend>(ShortDiagName));
    }


    TLog* TExtBuilderComponentLogger::GetProgressLogger() {
        return ComponentLog.Get();
    }

    TExtBuilderDebugHelper::TExtBuilderDebugHelper()
        : Impl(MakeHolder<TImpl>())
    {
    }

    TExtBuilderDebugHelper::~TExtBuilderDebugHelper() {
    }

    void TExtBuilderDebugHelper::OnAppStart(const TString& statePrefix, const TAssertPolicy& assertPol) {
        Impl->Init(statePrefix, assertPol);

        if (Impl->HasIncompleteMergeFlags()) {
            if (!Impl->AssertPolicy.StopOnIncompleteMerge(true)) {
                Impl->ClearIncompleteMergeFlags();
            } else {
                TVector<TFsPath> builderTaskPbTxts = Impl->GetIncompleteMergeTasks();
                if (!builderTaskPbTxts.empty()) {
                    Impl->ClearIncompleteMergeFlags();
                    SetFailedIndex(builderTaskPbTxts, /*waitDebugger=*/false);
                }
            }
        }
    }

    ITaskTrace::TPtr TExtBuilderDebugHelper::OnTaskStart(const NRtDoc::TBuilderTask& task) noexcept
    try {
        const TString statName = GetStatName(task);
        const TString shortDiagName = GetShortStatName(task);
        if (statName) {
            TFsPath fsTask = GetPbTxt(task);
            NFusion::DumpBuilderTask(task, fsTask);
            Impl->SetIncompleteMergeFlag(statName, fsTask);
        }
        return MakeIntrusive<TExtBuilderComponentLogger>(shortDiagName);
    } catch (...) {
        ERROR_LOG << CurrentExceptionMessage() << Endl;
        return ITaskTrace::TPtr();
    }

    void TExtBuilderDebugHelper::OnTaskEnd(const NRtDoc::TBuilderTask& task, const NRtDoc::TBuilderTaskResult& result) noexcept
    try {
        const TString statName = GetStatName(task);

        if (result.GetErrorCode() == 0 || result.GetErrorCode() == 10 || !Impl->AssertPolicy.StopOnIncompleteMerge(false)) {
            Impl->DropIncompleteMergeFlag(statName);
        } else {
            TFsPath pbTxt = Impl->GetIncompleteMergeTask(statName);
            if (!pbTxt.IsDefined() && task.HasOutput() && task.GetOutput().GetTrgDir())
                pbTxt = task.GetOutput().GetTrgDir();
            Impl->ClearIncompleteMergeFlags();

            const bool waitDebugger = IsShmStop(pbTxt);
            SetFailedIndex({pbTxt}, waitDebugger);
        }
    } catch (...) {
        ERROR_LOG << CurrentExceptionMessage() << Endl;
    }
}
