#include "config.h"
#include <balancer/kernel/helpers/common_parsers.h>
#include <util/string/builder.h>

namespace NBalancerSD {
    TString MakeUniqueBackendName(const TString& endpointSetKey, const TBackend& backend) {
        TStringBuilder backendName;
        backendName << endpointSetKey << "_" << (backend.Ip ?: backend.Host) << ":" << backend.Port;
        return backendName;
    }

    TConfigNode Save(NConfig::IConfig& config) {
        TConfigNode dest;

        class TFunc: public NConfig::IConfig::IFunc {
        public:
            explicit TFunc(TConfigNode& dest)
                : Dest_(dest)
            {
            }
        private:
            void DoConsume(const TString& key, NConfig::IConfig::IValue* value) override {
                if (value->IsContainer()) {
                    TConfigNode dest;
                    dest.IsContainer = true;
                    TFunc f(dest);
                    value->AsSubConfig()->ForEach(&f);
                    Dest_[key] = std::move(dest);
                } else {
                    try {
                        Dest_[key].AsString = value->AsString();
                    } catch (...) {
                    }
                    try {
                        Dest_[key].AsBool = value->AsBool();
                    } catch (...) {
                    }
                }
            };

            TConfigNode& Dest_;
        } f(dest);

        config.ForEach(&f);

        return dest;
    }

    class TFromSaved: public NConfig::IConfig, public NConfig::IConfig::IValue {
    public:
        TFromSaved(const TConfigNode& node)
            : Node_(node)
        {
        }

        TFromSaved(TConfigNode&& node)
            : Node_(std::move(node))
        {
        }

        void DoForEach(IFunc* func) override {
            for (const auto& it : Node_.NestedNodes) {
                TFromSaved child(it.second);
                func->Consume(it.first, &child);
            }
        }

        bool AsBool() override {
            if (Node_.AsBool.Defined()) {
                return *Node_.AsBool;
            } else {
                throw yexception() << "not bool";
            }
        }

        TString AsString() override {
            if (Node_.AsString.Defined()) {
                return *Node_.AsString;
            } else {
                throw yexception() << "not string";
            }
        }

        bool IsContainer() const override {
            return Node_.IsContainer;
        }

        IConfig* AsSubConfig() override {
            return this;
        }

        void DumpJson(IOutputStream&) const override {
            Y_ASSERT(false);
        }

        void DumpLua(IOutputStream&) const override {
            Y_ASSERT(false);
        }
    private:
        const TConfigNode Node_;
    };

    THolder<NConfig::IConfig> Restore(const TConfigNode& saved) {
        return MakeHolder<TFromSaved>(saved);
    }

    THolder<NConfig::IConfig> MakeConfig(const TConfigNode& algoOpts,
                                        const TConfigNode& proxyOpts,
                                        const TConfigNode& proxyWrapper,
                                        ui16 overridenPort,
                                        i32 portOffset,
                                        const TVector<TEndpointSetBackends>& allBackends)
{
        TConfigNode result = algoOpts;

        for (const auto& epsBackends : allBackends) {
            for (const auto& backend : epsBackends.Backends) {
                TConfigNode proxyConfig = proxyOpts;
                proxyConfig["host"].AsString = backend.Host;

                TString backendName = MakeUniqueBackendName(epsBackends.EndpointSetKey, backend);

                if (backend.Ip) {
                    proxyConfig["need_resolve"].AsString = "false";
                    proxyConfig["need_resolve"].AsBool = false;

                    proxyConfig["cached_ip"].AsString = backend.Ip;
                }

                int port = overridenPort;
                if (!port) {
                    port = backend.Port;
                }
                port += portOffset;

                proxyConfig["port"].AsString = ToString(port);
                proxyConfig["port"].AsBool = (port > 0);

                auto& backendConfig = result[backendName];

                if (proxyWrapper.IsContainer) {
                    auto wrapper = proxyWrapper.NestedNodes.at("value");
                    wrapper["proxy"] = std::move(proxyConfig);
                    backendConfig[proxyWrapper.NestedNodes.at("key").AsString.GetRef()] = std::move(wrapper);
                } else {
                    backendConfig["proxy"] = std::move(proxyConfig);
                }

                if (backend.Weight.Defined()) {
                    backendConfig["weight"].AsString = ToString(*backend.Weight);
                    backendConfig["weight"].AsBool = (*backend.Weight > 0);
                }
            }
        }

        return MakeHolder<TFromSaved>(std::move(result));
    }


    TVector<TEndpointSetBackends> ParseBackends(NConfig::IConfig& config) {
        TVector<TBackend> result;

        NSrvKernel::ParseMap(&config, [&](const auto&, auto* value) {
            if (!value->IsContainer()) {
                return;
            }

            TBackend backend;
            NSrvKernel::ParseMapStrict(value->AsSubConfig(), [&](const auto& key, auto* value) {
                if (key == "host") {
                    backend.Host = value->AsString();
                    return true;
                } else if (key == "port") {
                    backend.Port = FromString(value->AsString());
                    return true;
                } else if (key == "cached_ip") {
                    backend.Ip = value->AsString();
                    return true;
                } else if (key == "weight") {
                    backend.Weight = FromString<double>(value->AsString());
                    return true;
                }

                return false;
            });

            backend.Ready = true;

            result.emplace_back(std::move(backend));
        });

        if (result.empty()) {
            return {};
        }

        return TVector{TEndpointSetBackends{"__from_config", std::move(result)}};
    }
}
