#include <balancer/kernel/cookie/cookie.h>

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

using namespace NSrvKernel;

Y_UNIT_TEST_SUITE(TCookieIterTest) {

    auto DummyFuncC = [](TStringBuf, ui32) { return ECookieIter::Continue; };

    template <class TFunc>
    void DoTestCookieC(const TStringBuf cookie, const ui32 expectedCnt, TFunc&& func) {
        ui32 cnt = 0;

        ForEachCookieC([&](TStringBuf c) noexcept {
            return func(c, cnt++);
        }, cookie);

        UNIT_ASSERT_VALUES_EQUAL(cnt, expectedCnt);
    }

    Y_UNIT_TEST(TestForEachCookieC) {
        using namespace NSrvKernel;

        DoTestCookieC("", 0, DummyFuncC);

        DoTestCookieC("= ;= ; = ;=; =; =", 0, DummyFuncC);

        DoTestCookieC("a=b", 1, [&](TStringBuf cookie, ui32) noexcept {
            UNIT_ASSERT_VALUES_EQUAL(cookie, "a=b");
            return ECookieIter::Continue;
        });

        DoTestCookieC("; a=b; ", 1, [&](TStringBuf cookie, ui32) noexcept {
            UNIT_ASSERT_VALUES_EQUAL(cookie, "a=b");
            return ECookieIter::Continue;
        });

        DoTestCookieC("a=b; c=d", 2, [&](TStringBuf cookie, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(cookie, "a=b");
            } else {
                UNIT_ASSERT_VALUES_EQUAL(cookie, "c=d");
            }
            return ECookieIter::Continue;
        });

        DoTestCookieC("a=b; c=d", 1, [&](TStringBuf cookie, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(cookie, "a=b");
                return ECookieIter::Stop;
            } else {
                UNIT_FAIL("should have stopped");
                return ECookieIter::Continue;
            }
        });

        DoTestCookieC("a=b;c=d", 2, [&](TStringBuf cookie, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(cookie, "a=b");
                return ECookieIter::Continue;
            } else {
                UNIT_ASSERT_VALUES_EQUAL(cookie, "c=d");
                return ECookieIter::Continue;
            }
        });
    }

    template <class TFunc>
    void DoTestCookieKV(const TStringBuf cookie, const ui32 expectedCnt, TFunc&& func) {
        using namespace NSrvKernel;
        ui32 cnt = 0;

        ForEachCookieKV([&](TNameValue nv) noexcept {
            return func(nv, cnt++);
        }, cookie);

        UNIT_ASSERT_VALUES_EQUAL(cnt, expectedCnt);
    }

    auto DummyFuncKV = [](TNameValue, ui32) { return ECookieIter::Continue; };

    Y_UNIT_TEST(TestForEachCookieKV) {
        using namespace NSrvKernel;

        DoTestCookieKV("", 0, DummyFuncKV);

        DoTestCookieKV("= ;= ; = ;=; =; =", 0, DummyFuncKV);

        DoTestCookieKV("a=b", 1, [&](TNameValue nv, ui32) noexcept {
            UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
            UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
            return ECookieIter::Continue;
        });

        DoTestCookieKV("; a=b; ", 1, [&](TNameValue nv, ui32) noexcept {
            UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
            UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
            return ECookieIter::Continue;
        });

        DoTestCookieKV("a=b; c=d", 2, [&](TNameValue nv, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
            } else {
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "c");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "d");
            }
            return ECookieIter::Continue;
        });

        DoTestCookieKV("a=b;c=d", 2, [&](TNameValue nv, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
            } else {
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "c");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "d");
            }
            return ECookieIter::Continue;
        });

        DoTestCookieKV("a=b; c=d", 1, [&](TNameValue nv, ui32 cnt) noexcept {
            if (cnt == 0) {
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
                return ECookieIter::Stop;
            } else {
                UNIT_FAIL("should have stopped");
                return ECookieIter::Continue;
            }
        });

        // Really weird yet totally possible cookies
        DoTestCookieKV("a = b ; c=; =d; e", 4, [&](TNameValue nv, ui32 cnt) noexcept {
            switch (cnt) {
            case 0:
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "a");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "b");
                break;
            case 1:
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "c");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "");
                break;
            case 2:
                UNIT_ASSERT_VALUES_EQUAL(nv.Name, "");
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "d");
                break;
            case 3:
                UNIT_ASSERT(!nv.Name);
                UNIT_ASSERT_VALUES_EQUAL(nv.Value, "e");
                break;
            }
            return ECookieIter::Continue;
        });
    }

    Y_UNIT_TEST(TestAppendCookie) {
        using namespace NSrvKernel;
        {
            TString res;
            AppendCookieKV(res, {" a", "b "});
            AppendCookieKV(res, {"c", "d"});
            UNIT_ASSERT_VALUES_EQUAL(res, "a=b; c=d");
        }
        {
            TString res;
            AppendCookieC(res, "a=b");
            AppendCookieC(res, "c=d");
            UNIT_ASSERT_VALUES_EQUAL(res, "a=b; c=d");
        }
        // Some corner cases https://stackoverflow.com/q/27726166
        {
            TString res;
            AppendCookieKV(res, {Nothing(), "a"});
            AppendCookieKV(res, {"b", ""});
            AppendCookieKV(res, {"", "c"});
            AppendCookieKV(res, {Nothing(), ""});
            UNIT_ASSERT_VALUES_EQUAL(res, "a; b=; =c");
        }
        {
            TString res;
            AppendCookieC(res, "a");
            AppendCookieC(res, "=");
            AppendCookieC(res, "b=");
            AppendCookieC(res, "=c");
            UNIT_ASSERT_VALUES_EQUAL(res, "a; b=; =c");
        }
    }

    Y_UNIT_TEST(TestFindCookie) {
        TStringBuf cookie("a=b1; a=b2; c=d; e=f");
        UNIT_ASSERT_VALUES_EQUAL(FindCookieK(cookie, "a"), TStringBuf("b1"));
        UNIT_ASSERT_VALUES_EQUAL(FindCookieK(cookie, "c"), TStringBuf("d"));
        UNIT_ASSERT_VALUES_EQUAL(FindCookieK(cookie, "e"), TStringBuf("f"));
        UNIT_ASSERT(!FindCookieK(cookie, "x"));

        {
            using TFound = TVector<std::pair<TNameValue, size_t>>;
            TFound found;
            UNIT_ASSERT(!FindCookieKFsm(cookie, TFsm("a") | TFsm("e"), [&](const TNameValue& nv, size_t idx) {
                found.emplace_back(nv, idx);
                return ECookieIter::Continue;
            }));

            UNIT_ASSERT_VALUES_EQUAL(found, (TFound{{{"a", "b1"}, 0}, {{"a", "b2"}, 0}, {{"e", "f"}, 1}}));
        }

        {
            using TFound = TVector<std::pair<TNameValue, size_t>>;
            TFound found;
            UNIT_ASSERT(FindCookieKFsm(cookie, TFsm("a") | TFsm("e"), [&](const TNameValue& nv, size_t idx) {
                found.emplace_back(nv, idx);
                return ECookieIter::Stop;
            }));

            UNIT_ASSERT_VALUES_EQUAL(found, (TFound{{{"a", "b1"}, 0}}));
        }
    }

    Y_UNIT_TEST(TestStrToLower) {
        using namespace NCookie;
        UNIT_ASSERT_VALUES_EQUAL(StrToLower(
            TStringBuf("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"sv)
            ),
            TStringBuf("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"sv)
        );
    }
};
