#pragma once

#include "config_check.h"
#include "connection.h"
#include "main_config.h"
#include "main_event_handler.h"

#include <balancer/kernel/ban/banned_addresses.h>
#include <balancer/kernel/coro/own_executor.h>
#include <balancer/kernel/cpu/cpu_limiter.h>
#include <balancer/kernel/custom_io/stream.h>
#include <balancer/kernel/dns/async_resolver.h>
#include <balancer/kernel/dns/resolver.h>
#include <balancer/kernel/fs/shared_file_exists_checker.h>
#include <balancer/kernel/fs/shared_files.h>
#include <balancer/kernel/helpers/common_parsers.h>
#include <balancer/kernel/helpers/disposable.h>
#include <balancer/kernel/helpers/ranges.h>
#include <balancer/kernel/helpers/universal_mutex.h>
#include <balancer/kernel/http/parser/headers.h>
#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/log/log.h>
#include <balancer/kernel/log/logbackend.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/memory/shared.h>
#include <balancer/kernel/module/events.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/storage/storage.h>
#include <balancer/kernel/net/address.h>
#include <balancer/kernel/net/config_addrs.h>
#include <balancer/kernel/net/format.h>
#include <balancer/kernel/net/listener.h>
#include <balancer/kernel/net/sockops.h>
#include <balancer/kernel/pinger/pinger_backend_manager.h>
#include <balancer/kernel/rpslimiter/quota_manager.h>
#include <balancer/kernel/process/children_common/process_stat.h>
#include <balancer/kernel/coro/channel.h>
#include <balancer/kernel/process_common/channel_listener.h>
#include <balancer/kernel/process_common/main_options.h>
#include <balancer/kernel/process_common/two_nonblocking_pipe.h>
#include <balancer/kernel/thread/threadedqueue.h>

#include <infra/yp_service_discovery/libs/sdlib/config/config.h>

#include <library/cpp/build_info/sandbox.h>
#include <library/cpp/coroutine/engine/impl.h>
#include <library/cpp/coroutine/engine/mutex.h>
#include <library/cpp/coroutine/listener/listen.h>
#include <library/cpp/getopt/opt.h>
#include <library/cpp/logger/file.h>
#include <library/cpp/logger/stream.h>
#include <library/cpp/svnversion/svnversion.h>

#include <logbroker/unified_agent/client/cpp/logger/backend.h>

#include <util/datetime/cputimer.h>
#include <util/generic/hash.h>
#include <util/generic/map.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/memory/mmapalloc.h>
#include <util/memory/pool.h>
#include <util/stream/file.h>
#include <util/system/compiler.h>
#include <util/system/env.h>
#include <util/system/pipe.h>
#include <util/system/thread.h>

#include <utility>

namespace NSrvKernel {
    class TChildManager;
    class TInitializerMessage;
    class TBackendProtocolFactory;
    class TSDManager;
    struct TModuleRequirements;
}

namespace NSrvKernel::NProcessCore {
    constexpr char NAME[] = "main";

    THolder<TOwnExecutor> CreateExecutor(
        const TMainOptions& options,
        const TMainConfig& config,
        NCoro::IEnterPollerCallback* enterPollerCallback,
        NCoro::IScheduleCallback* scheduleCallback = nullptr
    ) noexcept;

    using TAcceptFull = TContListener::ICallBack::TAcceptFull;

    class TResolveHelper;
    class TWorkerProcess;

    void RunMasterMain(
        TGlobals& globals,
        const TString& config,
        IModuleFactory* loader,
        TMainOptions& options,
        TTwoNonblockingPipes* initializerPipes
    );

    class TMainTask
        : public IConfig::IFunc
        , public ICtl
        , public IDisposable
    {
    public:
        TMainTask(
            const TString& config,
            TGlobals& globals,
            IModuleFactory* loader,
            TMainOptions& options,
            TTwoNonblockingPipes* initializerPipes
        );

        ~TMainTask() override;

    public:
        void AddModules(const TString& config, const TGlobals& globals, TNodeFactory<IModule>& factory,
                        TAtomic* totalConnectionInProgress = nullptr);
        THolder<TTree> CreateTree(const TString& config, const TString& name, TNodeFactory<IModule>& factory);
        void AddExternalTree(ITree& tree);
        void RemoveExternalTree(ITree& tree);
        TChannelListener& GetChannelListener();
        void Execute(NThreading::TPromise<void>* startPromise = nullptr);
        void CloseListeners(const TGracefulShutdownOpts& opts);
        TError ExecuteFunctionByWorkersAndWait(std::function<void(IWorkerCtl*)> func);
        void StopMaster(const TGracefulShutdownOpts& opts);
        void DoDispose() noexcept override;

        // ICtl
        TLog* CreateLog(const TString& name) override;
        TSharedAllocator& SharedAllocator() noexcept override;
        TSharedStatsManager& SharedStatsManager() noexcept override;
        THolder<IModuleData>& GetModuleData(TStringBuf name) noexcept override;
        NYP::NServiceDiscovery::TEndpointSetManager* GetSDManager() noexcept override;
        bool IsPingerEnabled() const noexcept override;
        NDynamicBalancing::TUpdaterManager& DynamicBalancingUpdaterManager() noexcept override;
        NConnectionManager::TConnectionManager* ConnectionManager()  noexcept override;
        size_t GetCountOfChildren() const noexcept override;
        TSharedFiles& GetSharedFiles() noexcept override;
        NRpsLimiter::TQuotaManager* GetQuotaManager() noexcept override;
        TBackendProtocolFactory& GetProtocolFactory() const noexcept override;
        void RegisterStorage(NStorage::TStorage*) override;
        TModuleRequirements& GetModuleRequirements() const noexcept override;
        const TMainConfig& GetMainConfig() const noexcept override {
            return Config_;
        }
        const TString& GetConfigHash() const noexcept override {
            return ConfigHash_;
        }
        TBannedAddresses& BannedAddresses() noexcept override {
            return *BannedAddresses_;
        }


        TProcessStatOwner& GetProcessStatOwner() const;

        void MakePingerStatsCounters() noexcept;

        void InitLogWritingThreads(TContExecutor& executor, size_t workerId) noexcept;

        void ShutdownWithError(TStringBuf message) noexcept;

        bool IsBasicConfigCheck() const noexcept {
            return MainOptions_.JustCheckConfig && !IsExtendedConfigCheck();
        }

        bool IsExtendedConfigCheck() const noexcept {
            return MainOptions_.JustCheckConfig && ConfigCheck_ && !MainOptions_.SkipExtendedChecks;
        }

        bool CheckBackends() const noexcept {
            return IsExtendedConfigCheck() && !MainOptions_.SkipBackendChecks;
        }

        TOwnListener::EBindMode GetBindMode() const noexcept {
            if (IsExtendedConfigCheck()) {
                return MainOptions_.SkipIpChecks ? TOwnListener::EBindMode::Skip : TOwnListener::EBindMode::ZeroPort;
            } else {
                return TOwnListener::EBindMode::Normal;
            }
        }

        void SetupSd(const NYP::NServiceDiscovery::TSDConfig& config);

        // true means that no errors occurred
        [[nodiscard]] bool ReopenAllLogs(IOutputStream& out) noexcept;

        void ReopenAllLogsFromThread() noexcept;

    public:
        struct TTreeWrapper {
        public:
            TTreeWrapper()
                : Tree_(MakeHolder<TTree>())
                , TreePtr_(Tree_.Get())
            {
            }

            explicit TTreeWrapper(ITree& tree)
                : TreePtr_(&tree)
            {}

            TTreeWrapper(TConfigAddresses addresses, THolder<ITree> tree)
                : ListenAddrs(std::move(addresses))
                , Tree_(std::move(tree))
                , TreePtr_(Tree_.Get())
            {}

            ITree& Get() {
                return *TreePtr_;
            }

            const ITree& Get() const {
                return *TreePtr_;
            }

        public:
            TConfigAddresses ListenAddrs;

        private:
            THolder<ITree> Tree_;
            ITree* TreePtr_ = nullptr;
        };

        class TTrees : public TList<TTreeWrapper> {
        public:
            template <typename... Args>
            void AddTree(Args&&... args) {
                auto& last = emplace_back(std::forward<Args>(args)...);
                IteratorByTree_[&last.Get()] = --end();
            }
            void RemoveTree(ITree& tree) {
                auto mapIter = IteratorByTree_.find(&tree);
                Y_ASSERT(mapIter != IteratorByTree_.end());
                auto iter = mapIter->second;
                Y_ASSERT(iter != end());
                erase(iter);
                IteratorByTree_.erase(mapIter);
            }
            void Init(IWorkerCtl* process);
            void Dispose(IWorkerCtl* process);
            void ShowListen(TEventData& event) const;

        private:
            THashMap<ITree*, TList<TTreeWrapper>::iterator> IteratorByTree_;
        };

    public:
        TUniversalMutex TreesMutex;

        TT2CChannel<TGracefulShutdownOpts> StopChannel_ = TT2CChannel<TGracefulShutdownOpts>(1);

        TT2CChannel<char> ReopenLogChannel_ = TT2CChannel<char>(1);

        TVector<THolder<TU2WChannel<std::function<void(IWorkerCtl*)>>>> ChildrenChannels_;

        THolder<TChannelListener> ChannelListener;

        TTwoNonblockingPipes* InitializerPipes_= nullptr; // master
        IModuleFactory* const Loader_ = nullptr; // instance delete after parse config
        TIntrusivePtr<NConnectionManager::TConnectionManager> ConnectionManager_;

        const TMainConfig& Config() const {
            return Config_;
        }

        const TMainOptions& MainOptions() const {
            return MainOptions_;
        }

        const pid_t MainPid_ = getpid();

        // Allocators
        TSharedAllocator SharedAllocator_; // maintask
        // Depend on SharedAllocator_
        TSharedStatsManager SharedStatsManager_; // maintask
        THolder<TMainStats> MainStats_; // master without allocator
        // Depend on WorkerExecutor_ and ThreadPool_
        TContextSafeLogGenerator<TSystemLog> Log_; // instance
        TContextSafeLogGenerator<TSystemLog> PingerLog_; // instance
        TContextSafeLogGenerator<TSystemLog> ConnectionManagerLog_; // instance
        TContextSafeLogGenerator<TSystemLog> StorageGCLog_; // instance
        TContextSafeLogGenerator<TSystemLog> DynamicBalancingLog_; // instance
        THashMap<TString, TAtomicSharedPtr<TContextSafeLogGenerator<TLog>>> Logs_; // instance
        THolder<TLog> UnifiedAgentLog_;

        TTrees Trees;

        THolder<TCpuLimiterConfig> CpuLimiterConfig_;
        THolder<TCpuLimiterStat> CpuLimiterStat_;
        // Depend on CpuLimiter_ and SharedStatsManager_
        THolder<TProcessStatOwner> ProcessStatOwner_;
        TSharedFiles SharedFiles_;
        TVector<NStorage::TStorage*> Storages_;
        TAtomic LiveWorkersCounter_ = 0;
    private:
        TMainConfig Config_;
        TString ConfigHash_;

    public:
        THolder<NDynamicBalancing::TUpdaterManager> UpdaterManager_; // maintask
        THolder<TSDManager> SDManager_; // maintask
        THolder<TBackendProtocolFactory> ProtocolFactory_;

        THolder<TPingerStatsCounters> PingerStatsCounters_;

        THolder<NRpsLimiter::TQuotaManager> QuotaManager_;

        const TMainOptions& MainOptions_;

        TMaybe<TConfigCheck> ConfigCheck_; // maintask
        THashSet<TIpAddr> IgnoreBindErrors_; // maintask
        std::atomic<bool> Failed_ = false;
    private:
        void DoConsume(const TString& key, NConfig::IConfig::IValue* value) override;

        void HandleResolveOnStart(NConfig::IConfig* configParser);
        void ParseInstanceParams(NConfig::IConfig* configParser);
        void ApplyModuleRequirements();
        void HandleChildren();
        void CheckConfigParams();

    private:
        THashMap<TString, THolder<IModuleData>> ModuleData_; // maintask
        THolder<TResolveHelper> ResolveHelper_;
        THolder<TBannedAddresses> BannedAddresses_;
        THolder<TModuleRequirements> ModuleRequirements_;
    };
}
