#pragma once

#include "access_log.h"
#include "node.h"

#include <balancer/kernel/fs/shared_files.h>
#include <balancer/kernel/thread/threadedqueue.h>
#include <balancer/kernel/helpers/errors.h>

#include <library/cpp/config/sax.h>

#include <util/generic/hash_set.h>
#include <util/system/type_name.h>

#include <functional>

class TContExecutor;


namespace NSrvKernel {
    using NConfig::IConfig;
    using NConfig::TConfigParseError;

    class ICtl;
    class TConnDescr;
    class IModule;
    class TModuleList;
    class TEventHandler;
    class TThreadedQueue;

    using IModuleHandle = INodeHandle<IModule>;
    using IModuleFactory = INodeFactory<IModule>;

    struct TBackendCheckResult {
        enum class EStatus {
            Success /*"SUCCESS"*/,
            Skipped /*"SKIPPED"*/,
            Failed /*"FAILED"*/,
        };

    public:
        TVector<TError> Errors;
        EStatus Status = EStatus::Skipped;

    public:
        TBackendCheckResult() = default;

        explicit TBackendCheckResult(EStatus st)
            : Status(st)
        {}

        TBackendCheckResult(TError err, EStatus st)
            : Status(st)
        {
            Errors.emplace_back(std::move(err));
        }
    };

    class TModuleParams {
    public:
        using TResolvedHosts = THashMap<TString, TString>;
        using TDisabledHosts = THashSet<TString>;

    public:
        IConfig* Config = nullptr;
        IModuleFactory* Loader = nullptr;
        ICtl* Control = nullptr;
        TModuleList* Modules = nullptr;

    public:
        TModuleParams() noexcept = default;

        TModuleParams(IConfig* config,
                      IModuleFactory* loader,
                      ICtl* control,
                      TModuleList* modules,
                      const TResolvedHosts* resolvedHosts,
                      TDisabledHosts* disabledHosts = nullptr) noexcept;

        TModuleParams Copy(IConfig* cfg) const noexcept;

        bool IsDisabledHost(TStringBuf host) noexcept {
            return DisabledHosts_ && DisabledHosts_->contains(host);
        }

        const TResolvedHosts* GetResolvedHosts() const noexcept { return ResolvedHosts_; }

    private:
        // NB: these fields are valid only during construction of modules, not during their work
        const TResolvedHosts* ResolvedHosts_ = nullptr; // not owned
        TDisabledHosts* DisabledHosts_ = nullptr;
    };


    // TODO(velavokr): split out IBalancer and IProxy module interfaces.

    class IModule : public TIntrusiveListItem<IModule> {
    public:
        virtual ~IModule() {
        }

        IModuleHandle* GetHandle() const noexcept {
            return DoHandle();
        }

        TError Run(const TConnDescr& descr) const noexcept {
            if (ExtraAccessLog()) {
                const TExtraAccessLogEntry logEntry{ descr, GetHandle()->Name() };
                Y_PROPAGATE_ERROR(DoRun(descr));
            } else {
                Y_PROPAGATE_ERROR(DoRun(descr));
            }
            return {};
        }

        void Init(IWorkerCtl* process) {
            DoInit(process);
        }

        void InitTypeName() {
            TypeName_ = ::TypeName(*this);
        }

        const TString& TypeName() {
            return TypeName_;
        }

        void CheckConstraints() const {
            return DoCheckConstraints();
        }

        void Finalize() {
            return DoFinalize();
        }

        void Dispose(IWorkerCtl* process) noexcept {
            return DoDispose(process);
        }

        bool CanWorkWithoutHTTP() const noexcept {
            return DoCanWorkWithoutHTTP();
        }

        bool ExtraAccessLog() const noexcept {
            return DoExtraAccessLog();
        }

        const TEventHandler* EventHandler() const noexcept {
            return DoEventHandler();
        }

        // TODO(velavor): should go to IBalancer
        // Will be called after the Init phase if the module registered itself for the check.
        virtual TBackendCheckResult CheckBackends(IWorkerCtl&, bool) const noexcept {
            return {};
        }

        // TODO(velavokr): should go to IProxy
        virtual void PrintInfo(NJson::TJsonWriter&) const noexcept {}

    private:
        virtual IModuleHandle* DoHandle() const /* noexcept */ = 0;

        virtual TError DoRun(const TConnDescr& descr) const = 0;

        virtual void DoInit(IWorkerCtl*) {
        }

        virtual void DoFinalize() {
        }

        virtual void DoDispose(IWorkerCtl*) {
        }

        virtual bool DoCanWorkWithoutHTTP() const /* noexcept */ {
            return false;
        }

        virtual void DoCheckConstraints() const {
        }

        virtual bool DoExtraAccessLog() const /* noexcept */ {
            return false;
        }

        virtual const TEventHandler* DoEventHandler() const /* noexcept */ {
            return nullptr;
        }

    private:
        TString TypeName_;
    };


    class TModuleList: public TIntrusiveList<IModule> {};

    class TModules: public TModuleList {
    public:
        void Init(IWorkerCtl* process);
        void Finalize();
        void Dispose(IWorkerCtl* process);
    };

    class ITree {
    public:
        virtual ~ITree() {}

        virtual void Init(IWorkerCtl* process) = 0;
        virtual void Dispose(IWorkerCtl* process) = 0;
    };

    struct TTree
        : public ITree
    {
        TTree() = default;
        TTree(TModules modules, THolder<IModule> entry)
            : Entry(std::move(entry))
            , Modules(std::move(modules))
        {}

        void Init(IWorkerCtl* process) override {
            Modules.Init(process);
        }

        void Dispose(IWorkerCtl* process) override {
            Modules.Dispose(process);
        }

        THolder<IModule> Entry;
        TModules Modules;
        TAtomic* TotalConnectionInProgress = nullptr;
    };

}

// Part of config handling
#define STATS_ATTR ON_KEY("stats_attr", StatsAttr_) { StatsAttr_ = TString(" stats_attr=\"").append(StatsAttr_).append('\"'); return; }
