#pragma once

#include "config.h"

#include <balancer/modules/balancer/backends_factory.h>

#include <balancer/kernel/ctl/ctl.h>
#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/module/module_face.h>
#include <balancer/kernel/coro/channel.h>

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

#include <util/generic/ptr.h>

namespace NBalancerSD {

    struct TBackendsConfiguration {
        TString BackendsType;
        TConfigNode BackendsOptions;
        TConfigNode ProxyOptions;
        TConfigNode ProxyWrapper;
        ui16 Port = 0;
        i32 PortOffset = 0;

        NSrvKernel::TPolicyFeatures PolicyFeatures;
        NSrvKernel::TBackendsUID UID;
        NSrvKernel::TBackendCheckParameters CheckParameters;
    };

    struct TBackendsInitializationParams {
        TBackendsConfiguration BackendsConfiguration_;
        NSrvKernel::TModuleParams ModuleParams_;
        using TConfigMaker = std::function<decltype(NBalancerSD::MakeConfig)>;
        TConfigMaker ConfigMaker_;
        using TPtr = TAtomicSharedPtr<TBackendsInitializationParams>;
    };

    class TBackendsRef;

    class TGlobalBackendsHolder: public TNonCopyable {
    public:
        using TPtr = TAtomicSharedPtr<TGlobalBackendsHolder>;

        TGlobalBackendsHolder(
            TBackendsInitializationParams::TPtr initializationParams,
            const TVector<TEndpointSetBackends>& backends
        );

        TPtr Update(const TVector<TEndpointSetBackends>& backends);

        NSrvKernel::IBackends& Backends() {
            return *Backends_;
        }
    private:
        TGlobalBackendsHolder(
            TBackendsInitializationParams::TPtr initializationParams,
            const TVector<TEndpointSetBackends>& instances,
            const NSrvKernel::IBackends& backends
        );

        THolder<NConfig::IConfig> CreateConfig();

        TBackendsInitializationParams::TPtr InitializationParams_;
        TVector<TEndpointSetBackends> BackendDescriptions_;
        THolder<NSrvKernel::IBackends> Backends_;
    };


    using TGlobalBackendsHolderPtr = TGlobalBackendsHolder::TPtr;

    class TWorkerBackendsHolder: public TNonCopyable {
    public:
        using TPtr = TSimpleSharedPtr<TWorkerBackendsHolder>;
        TWorkerBackendsHolder(NSrvKernel::IWorkerCtl* process, TGlobalBackendsHolderPtr next, TWorkerBackendsHolder::TPtr prev);

        void DisposeBackends();

        NSrvKernel::IBackends& Backends() {
            Y_VERIFY(!Disposed_);
            return GlobalBackendsHolder_->Backends();
        }

        TGlobalBackendsHolderPtr GetBackendsHolder() {
            return GlobalBackendsHolder_;
        }

        void AddRef(TBackendsRef& ref);
        void ScheduleTermination(TContExecutor& executor, TDuration delay, TDuration deadline);
        void Invalidate(TWorkerBackendsHolder::TPtr next);

        ~TWorkerBackendsHolder();

    private:
        class TBackendDescriptorDisposer : public TNonCopyable {
        public:
            TBackendDescriptorDisposer(NSrvKernel::TBackendDescriptor::TRef backend, NSrvKernel::IWorkerCtl* process)
                : Backend_(backend)
                , Process_(process)
            {}
            ~TBackendDescriptorDisposer() {
                Backend_->Dispose(Process_);
            }
        private:
            NSrvKernel::TBackendDescriptor::TRef Backend_;
            NSrvKernel::IWorkerCtl* Process_;
        };
        TGlobalBackendsHolderPtr GlobalBackendsHolder_;

        bool Disposed_ = false;
        THashMap<const NSrvKernel::TBackendDescriptor*, TSimpleSharedPtr<TBackendDescriptorDisposer>> Disposers_;

        NSrvKernel::IWorkerCtl* Process_ = nullptr;

        TIntrusiveList<TBackendsRef> Refs_;
        NSrvKernel::TCoroutine Terminator_;
    };

    class TBackendsProvider;

    class TWorkerBackendsRef
        : public TIntrusiveListItem<TWorkerBackendsRef>
        , TNonCopyable
    {
    public:
        TWorkerBackendsRef(NSrvKernel::IWorkerCtl* process, TBackendsProvider& backendsProvider);
        ~TWorkerBackendsRef();

        void Dispose();

        void Sync();
        void OnUpdate();

        NSrvKernel::TError PopLastUpdateError() {
            return std::move(LastUpdateError_);
        }

        bool LastUpdateFailed() const noexcept {
            return LastUpdateFailed_;
        }

        TWorkerBackendsHolder::TPtr GetWorkerBackendsHolder() {
            return WorkerBackendsHolder_;
        }
    private:
        TWorkerBackendsHolder::TPtr MakeWorkerBackendsHolder(TGlobalBackendsHolderPtr next, TWorkerBackendsHolder::TPtr prev) {
            return MakeSimpleShared<TWorkerBackendsHolder>(Process_, next, prev);
        }
    private:
        TBackendsProvider* Provider_ = nullptr;
        NSrvKernel::IWorkerCtl* Process_ = nullptr;
        TWorkerBackendsHolder::TPtr WorkerBackendsHolder_;

#ifdef _linux_
        NSrvKernel::TEventFdEvent UpdateEvent_;
#else
        NSrvKernel::TSimplePipeEvent UpdateEvent_;
#endif
        NSrvKernel::TCoroutine Updater_;
        NSrvKernel::TError LastUpdateError_;
        bool LastUpdateFailed_ = false;
    };

    class TBackendsRef
        : private TWorkerBackendsHolder::TPtr
        , public TIntrusiveListItem<TBackendsRef>
        , TNonCopyable
    {
    private:
        class TInvalidateHandler;
    public:
        TBackendsRef(TCont* cont, const TWorkerBackendsHolder::TPtr& backendsHolder = nullptr)
            : TWorkerBackendsHolder::TPtr(backendsHolder)
            , Cont_(cont)
        {
            Y_VERIFY(cont);
            if (backendsHolder) {
                backendsHolder->AddRef(*this);
            }
        }

        void CancelHoldingCoroutine() {
            if (Cont_) {
                Cont_->Cancel();
            }
        }

        bool IsStored(const TWorkerBackendsHolder* ptr) const noexcept;
        NSrvKernel::IBackends* Backends() noexcept;
        size_t BackendsCount() const noexcept;
        bool IsHashing() const noexcept;

        THolder<NSrvKernel::IAlgorithm> ConstructAlgorithm(const NSrvKernel::TStepParams& params) noexcept;

        void Invalidate(TWorkerBackendsHolder::TPtr valid);
        void SetInvalidateHandler(TInvalidateHandler* handler) noexcept {
            InvalidateHandler_ = handler;
        }
    private:
        TCont* Cont_ = nullptr;
        bool CanInvalidate_ = true;
        TInvalidateHandler* InvalidateHandler_ = nullptr;
    };

    class TBackendsProvider : public NConfig::IConfig::IFunc {
    private:
        class TYpEndpointsProvider;
        struct TEndpointSet {
            THolder<TYpEndpointsProvider> YpEndpointsProvider;
            TString ClusterId;
            TString ServiceId;

            TString BackendsFile;
        };

    public:
        explicit TBackendsProvider(
            const NSrvKernel::TModuleParams& mp,
            TBackendsInitializationParams::TConfigMaker configMaker = NBalancerSD::MakeConfig
        );
        ~TBackendsProvider();

        void FinishInitialization(
            NSrvKernel::TPolicyFeatures policyFeatures,
            const NSrvKernel::TBackendCheckParameters& checkParams
        );

        TGlobalBackendsHolderPtr GetBackendsHolder();
        const TString& BackendsType() const {
            return BackendsInitialization_->BackendsConfiguration_.BackendsType;
        }

        void AddWorker(TWorkerBackendsRef& ref) {
            with_lock (Lock_) {
                WorkerRefs_.PushBack(&ref);
                ref.OnUpdate();
            }
        }

        void RemoveWorker(TWorkerBackendsRef& ref) {
            with_lock (Lock_) {
                ref.Unlink();
            }
        }

        void EnableUpdate();
        void OnUpdate(bool onlyReadinessChanged);
        void UpdateBackends(bool onlyReadinessChanged);

        TDuration GetTerminationDelay() const {
            return TerminationDelay_;
        }

        TDuration GetTerminationDeadline() const {
            return TerminationDeadline_;
        }

    private:
        void DoConsume(const TString& key, NConfig::IConfig::IValue* value) final;

        TGlobalBackendsHolderPtr CreateBackendsHolder(const TVector<TEndpointSetBackends>& backends);
        void UpdateBackendsHolder(const TVector<TEndpointSetBackends>& backends);

        void ParseEndpointSets(NConfig::IConfig& config);

        static NYP::NServiceDiscovery::TEndpointSetKey MakeEndpointSetKey(const TEndpointSet& eps);

    private:
        TBackendsInitializationParams::TPtr BackendsInitialization_;
        TVector<TEndpointSetBackends> BackendsFromConfig_;

        TGlobalBackendsHolderPtr BackendsHolder_;

        TMutex Lock_;
        TIntrusiveList<TWorkerBackendsRef> WorkerRefs_;

        TVector<TEndpointSet> EndpointSets_;
        TDuration TerminationDelay_ = TDuration::Seconds(60);
        TDuration TerminationDeadline_ = TDuration::Seconds(300);
        
        bool UseUnistat_ = true;
    };
}
