#include "entities.h"

#include <balancer/modules/balancer/policies/policy_factory.h>

namespace NModBalancer {

    TEntities::TEntities(TString errorPrefix, NBalancerSD::TBackendsInitializationParams::TConfigMaker configMaker)
        : ErrorPrefix_(std::move(errorPrefix))
        , ConfigMaker_(std::move(configMaker))
    {}

    void TEntities::ApplyConfiguration(const NSrvKernel::TModuleParams& mp, const NSrvKernel::TBackendCheckParameters& checkParams) {
        if (!PolicyFactory_) {
            PolicyFactory_.Reset(NSrvKernel::PolicyFactories()->Load("unique_policy", mp).Release());
        }

        NSrvKernel::TPolicyFeatures features;
        PolicyFactory_->FillFeatures(features);

        if (SD_) {
            SD_->FinishInitialization(features, checkParams);
            Y_ENSURE(SD_->GetBackendsHolder());
            SD_->EnableUpdate();
        } else {
            if (!Backends_) {
                ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "no backends configured";
            }

            if (!Backends_->Size()) {
                ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "empty backends";
            }

            Backends_->ProcessPolicyFeatures(features);
            Backends_->SetCheckParameters(checkParams);
        }
    }

    void TEntities::Configure(const TString& key, const NSrvKernel::TModuleParams& mp) {
        if (key == "sd") {
            if (Backends_) {
                ythrow NConfig::TConfigParseError() << ErrorPrefix_ << " try to setup both service discovery and backends";
            }

            SD_ = MakeHolder<NBalancerSD::TBackendsProvider>(mp, ConfigMaker_);
            BackendsType_ = SD_->BackendsType();
            return;
        }

        {
            THolder<NSrvKernel::IBackends> backends = NSrvKernel::CommonBackends()->Load(key, mp, BackendsUID_);

            if (backends) {
                if (SD_) {
                    ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "try to setup both service discovery and backends";
                }

                if (Backends_) {
                    ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "trying to replace the existing algorithm with a new one (" << key << ")";
                }
                Backends_.Reset(backends.Release());
                BackendsType_ = key;

                return;
            }
        }

        {
            THolder<NSrvKernel::IPolicyFactory> policyFactory = NSrvKernel::PolicyFactories()->Load(key, mp);
            if (policyFactory) {
                if (PolicyFactory_) {
                    ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "trying to replace the existing policy with a new one (" << key << ")";
                }
                PolicyFactory_.Reset(policyFactory.Release());

                return;
            }
        }

        ythrow NConfig::TConfigParseError() << ErrorPrefix_ << "unsupported key(" << key.Quote() << ")";
    }

    void TEntities::Init(NSrvKernel::IWorkerCtl* process, THolder<NBalancerSD::TWorkerBackendsRef>& backendsRef) {
        if (Backends_) {
            Backends_->Init(process);
        }

        if (SD_) {
            backendsRef = MakeHolder<NBalancerSD::TWorkerBackendsRef>(process, *SD_);
        }

        PolicyFactory_->Init(process);
    }

    void TEntities::Dispose(NSrvKernel::IWorkerCtl* process, THolder<NBalancerSD::TWorkerBackendsRef>& backendsRef) {
        if (Backends_) {
            Backends_->Dispose(process);
        }

        if (backendsRef) {
            backendsRef->GetWorkerBackendsHolder()->DisposeBackends();
            backendsRef.Destroy();
        }
    }

    NSrvKernel::IBackends* TEntities::Backends() const noexcept {
        return Backends_.Get();
    }

    const TString& TEntities::BackendsType() const noexcept {
        return BackendsType_;
    }

    NSrvKernel::IPolicyFactory* TEntities::PolicyFactory() const noexcept {
        return PolicyFactory_.Get();
    }

    NBalancerSD::TBackendsProvider* TEntities::BackendsProvider() const noexcept {
        return SD_.Get();
    }

}
