#include "config.h"

#include <library/cpp/testing/unittest/registar.h>

#include <util/stream/file.h>
#include <util/string/join.h>

namespace {
    TVector<TString> GetRepresentation(NConfig::IConfig& config) {
        struct TState {
            TVector<TString> Res;
            TVector<TString> Current;
        } state;

        class TFunc: public NConfig::IConfig::IFunc {
        public:
            explicit TFunc(TState* state)
                : State_(state)
            {
            }

            void DoConsume(const TString& key, NConfig::IConfig::IValue* value) override {
                TString path = JoinSeq("-", State_->Current);

                if (value->IsContainer()) {
                    State_->Current.push_back(key);
                    value->AsSubConfig()->ForEach(this);
                    State_->Current.pop_back();
                } else {
                    TString boolRepr;
                    try {
                        boolRepr = ToString(value->AsBool());
                    } catch (...) {
                        boolRepr = "not_bool";
                    }

                    path += "-" + boolRepr;

                    TString stringRepr;
                    try {
                        stringRepr = value->AsString();
                    } catch (...) {
                        stringRepr = "not_str";
                    }

                    path += "-" + stringRepr;
                }

                State_->Res.push_back(path);
            }
        private:
            TState* const State_;
        } f(&state);

        config.ForEach(&f);

        Sort(state.Res.begin(), state.Res.end());

        return state.Res;
    };

    TString prefix;

    Y_DECLARE_UNUSED void Print(NConfig::IConfig& config) {
        class TFunc: public NConfig::IConfig::IFunc {
        public:
            void DoConsume(const TString& key, NConfig::IConfig::IValue* value) override {
                Cout << prefix << key << " = ";
                if (value->IsContainer()) {
                    Cout << "{\n";
                    prefix.append(' ');
                    value->AsSubConfig()->ForEach(this);
                    prefix.pop_back();
                    Cout << prefix << "}\n";
                } else {
                    Cout << value->AsString() << Endl;
                }
            }
        } f;
        config.ForEach(&f);
    }
}

Y_UNIT_TEST_SUITE(TSDConfig) {
    void TestEqual(const TString& filename) {
        TVector<TString> repr1;

        {
            TFileInput fi(filename);
            THolder<NConfig::IConfig> config = NConfig::ConfigParser(fi);
            repr1 = GetRepresentation(*config);
        }

        TVector<TString> repr2;

        {
            TFileInput fi(filename);
            THolder<NConfig::IConfig> config = NConfig::ConfigParser(fi);

            NBalancerSD::TConfigNode saved = NBalancerSD::Save(*config);
            THolder<NConfig::IConfig> restored = NBalancerSD::Restore(saved);

            repr2 = GetRepresentation(*restored);
        }

        UNIT_ASSERT(repr1 == repr2);
    }

    Y_UNIT_TEST(TestEqualSimple) {
        TestEqual("config_simple.lua");
    }

    Y_UNIT_TEST(TestEqualFull) {
        TestEqual("config_full.lua");
    }

    NBalancerSD::TConfigNode FromString(const TString& s) {
        TStringInput si(s);
        THolder<NConfig::IConfig> config = NConfig::ConfigParser(si);
        return NBalancerSD::Save(*config);
    }

    Y_UNIT_TEST(MakeConfig) {
        TString proxyOpts = ""
            "instance = {"
            "   backend_timeout = \"5s\";"
            "   connect_timeout = \"100ms\";"
            "};";

        TString proxyWrapper = ""
                "instance = {"
                "   key = \"dummy\";"
                "   value = {};"
                "};";

        TString algoOpts = ""
            "instance = {"
            "   randomized = true;"
            "   some_complex_option = {"
            "       optA = {};"
            "       optB = b;"
            "   };"
            "};";

        TString expectedOpts = ""
            "instance = {"
            "   randomized = true;"
            "   some_complex_option = {"
            "       optA = {};"
            "       optB = b;"
            "   };"
            "   [\"sas#test_127.0.0.1:8080\"] = {"
            "       weight = 1.0;"
            "       dummy = {"
            "           proxy = {"
            "               backend_timeout = \"5s\";"
            "               cached_ip = \"127.0.0.1\";"
            "               connect_timeout = \"100ms\";"
            "               need_resolve = false;"
            "               host = \"backend1\";"
            "               port = 8080;"
            "           };"
            "       };"
            "   };"
            "   [\"man#test_127.0.0.5:8080\"] = {"
            "       weight = 2.0;"
            "       dummy = {"
            "           proxy = {"
            "               backend_timeout = \"5s\";"
            "               cached_ip = \"127.0.0.5\";"
            "               connect_timeout = \"100ms\";"
            "               need_resolve = false;"
            "               host = \"backend2\";"
            "               port = 8080;"
            "           };"
            "       };"
            "   };"
            "   [\"man#test_127.0.0.23:8080\"] = {"
            "       weight = 1.0;"
            "       dummy = {"
            "           proxy = {"
            "               backend_timeout = \"5s\";"
            "               cached_ip = \"127.0.0.23\";"
            "               connect_timeout = \"100ms\";"
            "               need_resolve = false;"
            "               host = \"backend3\";"
            "               port = 8080;"
            "           };"
            "       };"
            "   };"
            "};";

        TStringInput si(expectedOpts);
        THolder<NConfig::IConfig> expected = NConfig::ConfigParser(si);

        THolder<NConfig::IConfig> config = NBalancerSD::MakeConfig(
            FromString(algoOpts),
            FromString(proxyOpts),
            FromString(proxyWrapper),
            0,
            0,
            {
                { "sas#test", { {"backend1", "127.0.0.1", 8080, true, 1.0} } },
                {
                    "man#test",
                    {
                        {"backend2", "127.0.0.5", 8080, true, 2.0},
                        {"backend3", "127.0.0.23", 8080, true, 1.0},
                    }
                },
            }
        );

        //Print(*config);
        UNIT_ASSERT_VALUES_EQUAL(GetRepresentation(*config), GetRepresentation(*expected));
    }

    Y_UNIT_TEST(TestParseBackends) {
        TString backends = ""
            "instance = {"
            "   { host = \"host1\"; };"
            "   { host = \"host2\"; };"
            "   { host = \"host3\"; };"
            "};";

        TStringInput si(backends);

        THolder<NConfig::IConfig> config = NConfig::ConfigParser(si);

        auto parsed = NBalancerSD::ParseBackends(*config);

        UNIT_ASSERT_VALUES_EQUAL(parsed[0].Backends.size(), 3);
        UNIT_ASSERT_VALUES_EQUAL(parsed[0].Backends[0].Host, "host1");

    }
}
