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

#include <library/cpp/iterator/enumerate.h>
#include <library/cpp/testing/unittest/registar.h>
#include <util/generic/xrange.h>
#include <util/stream/format.h>

using namespace NSrvKernel;
using namespace NSrvKernel::NCookie;

Y_UNIT_TEST_SUITE(TExpiresTest) {
    Y_UNIT_TEST(TestRenderRanges) {
        UNIT_ASSERT_VALUES_EQUAL(
            ToStringBuf(TExpires::FromInstant(TInstant::ParseIso8601("2015-10-21 07:28:00Z"))->Render()),
            "Wed, 21 Oct 2015 07:28:00 GMT"
        );
        for (const auto d : {
            "1970-01-01 00:00:00Z",
            "2019-12-31 23:59:60Z",
            "2020-01-01 00:00:00Z",
            "2059-12-31 23:59:60Z",
            "2060-01-01 00:00:00Z",
            "9999-12-31 23:59:59Z",
        }) {
            const auto t = TInstant::ParseIso8601(d);
            const auto s = t.ToRfc822String();
            UNIT_ASSERT_VALUES_EQUAL_C(ToStringBuf(TExpires::FromInstant(t)->Render()), s, d);
            UNIT_ASSERT_VALUES_EQUAL_C(TExpires::Parse(TBlob::NoCopy(s.data(), s.size()))->ToInstant(), t, d);
        }
    }

    Y_UNIT_TEST(TestValidRanges) {
        for (const auto d : xrange(1, 32)) {
            const auto date = TStringBuilder() << "Mon, " << LeftPad(d, 2, '0') << " " << "Mar 1974 05:06:07 GMT";
            const auto expDate = TStringBuilder() << LeftPad(d, 2, '0') << "-03-1974 05:06:07";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto w : {
            "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
        }) {
            const auto date = TStringBuilder() << w << ", 01 Feb 1973 04:05:06 GMT";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), "01-02-1973 04:05:06");
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto [i, m] : Enumerate(TVector<TStringBuf>{
            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
        })) {
            const auto date = TStringBuilder() << "Mon, 01 " << m << " 1974 05:06:07 GMT";
            const auto expDate = TStringBuilder() << "01-" << LeftPad(i + 1, 2, '0') << "-1974 05:06:07";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto y : xrange(1970, 10000)) {
            const auto date = TStringBuilder() << "Mon, 01 May " << y << " 03:04:05 GMT";
            const auto expDate = TStringBuilder() << "01-05-" << y << " 03:04:05";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto h : xrange(0, 24)) {
            const auto date = TStringBuilder() << "Mon, 01 Feb 1973 " << LeftPad(h, 2, '0') << ":31:42 GMT";
            const auto expDate = TStringBuilder() << "01-02-1973 " << LeftPad(h, 2, '0') << ":31:42";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto m : xrange(0, 60)) {
            const auto date = TStringBuilder() << "Mon, 01 Feb 1973 04:" << LeftPad(m, 2, '0') << ":01 GMT";
            const auto expDate = TStringBuilder() << "01-02-1973 04:" << LeftPad(m, 2, '0') << ":01";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto s : xrange(0, 60)) {
            const auto date = TStringBuilder() << "Mon, 01 Feb 1973 04:05:" << LeftPad(s, 2, '0') << " GMT";
            const auto expDate = TStringBuilder() << "01-02-1973 04:05:" << LeftPad(s, 2, '0');
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), expDate);
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto t : {"GMT", "UTC"}) {
            const auto date = TStringBuilder() << "Mon, 01 Feb 1973 04:05:06 " << t;
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), "01-02-1973 04:05:06");
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
        for (const auto s : {" ", "-"}) {
            const auto date = TStringBuilder() << "Mon, 01" << s << "Feb" << s << "1973 04:05:06 GMT";
            auto res = TExpires::Parse(TBlob::NoCopy(date.data(), date.size()));
            UNIT_ASSERT_C(res, date);
            UNIT_ASSERT_VALUES_EQUAL(res->ToInstant()->FormatGmTime("%d-%m-%Y %T"), "01-02-1973 04:05:06");
            UNIT_ASSERT_VALUES_EQUAL(ToStringBuf(res->Render()), res->ToInstant()->ToRfc822String());
        }
    }

    Y_UNIT_TEST(TestBadParses) {
        for (const auto d : std::initializer_list<TStringBuf>{
            "", // empty
            "tue, 05 May 2020 00:00:00 GMT", // lower case wday
            "05 May 2020 00:00:00 GMT", // no wday
            "Xxx, 05 May 2020 00:00:00 GMT", // bad wday
            "Tuesday, 05 May 2020 00:00:00 GMT", // bad wday

            "Tue, 32 Apr 2020 00:00:00 GMT", // bad day
            "Tue, 00 Apr 2020 00:00:00 GMT", // bad day
            "Tue, 5 May 2020 00:00:00 GMT", // one digit day
            "Tue, Apr 2020 00:00:00 GMT", // no day

            "Tue, 05 may 2020 00:00:00 GMT", // lower case month
            "Tue, 05 Xxx 2020 00:00:00 GMT", // bad month
            "Sun, 05 April 2020 00:00:00 GMT", // bad month
            "Tue, 05 05 2020 00:00:00 GMT", // bad month
            "Tue, 05 2020 00:00:00 GMT", // no month

            "Tue, 05 May 20 00:00:00 GMT", // two digit year
            "Tue, 05 May 00:00:00 GMT", // no year

            "Tue, 05 May 2020 0:00:00 GMT", // 1 digit hour
            "Tue, 05 May 2020 24:00:00 GMT", // bad hour

            "Tue, 05 May 2020 00:0:00 GMT", // 1 digit min
            "Tue, 05 May 2020 00:60:00 GMT", // bad min

            "Tue, 05 May 2020 00:00:0 GMT", // 1 digit sec
            "Tue, 05 May 2020 00:00:60 GMT", // bad sec
            "Tue, 05 May 2020 00:00 GMT", // no sec

            "Tue, 05 May 2020 00:00:00 gmt", // lower case tz
            "Tue, 05 May 2020 00:00:00 MSK", // bad tz

            "Tue,  05 May 2020 00:00:00 GMT", // bad ws
            "Tue, 05  May 2020 00:00:00 GMT", // bad ws
            "Tue, 05 May  2020 00:00:00 GMT", // bad ws
            "Tue, 05 May 2020  00:00:00 GMT", // bad ws
            "Tue, 05 May 2020 00:00:00  GMT", // bad ws
        }) {
            UNIT_ASSERT_C(!TExpires::Parse(TBlob::NoCopy(d.data(), d.size())), d);
        }
    }

    Y_UNIT_TEST(TestMinMaxRels) {
        UNIT_ASSERT(TExpires::Min().IsValid());
        UNIT_ASSERT(TExpires::Max().IsValid());

        UNIT_ASSERT(TExpires::Min() < TExpires::Max());
        UNIT_ASSERT(!(TExpires::Min() < TExpires::Min()));

        UNIT_ASSERT(!(TExpires::Min() > TExpires::Max()));
        UNIT_ASSERT(!(TExpires::Min() > TExpires::Min()));

        UNIT_ASSERT(TExpires::Min() <= TExpires::Max());
        UNIT_ASSERT(TExpires::Min() <= TExpires::Min());

        UNIT_ASSERT(!(TExpires::Min() >= TExpires::Max()));
        UNIT_ASSERT(TExpires::Min() >= TExpires::Min());

        UNIT_ASSERT(TExpires::Min() != TExpires::Max());
        UNIT_ASSERT(!(TExpires::Min() != TExpires::Min()));

        UNIT_ASSERT(!(TExpires::Min() == TExpires::Max()));
        UNIT_ASSERT(TExpires::Min() == TExpires::Min());

        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) != TExpires::Max());
        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) != TExpires::Min()));
        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) != TMaybe<TExpires>(TExpires::Max()));
        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) != TMaybe<TExpires>(TExpires::Min())));
        UNIT_ASSERT(TExpires::Min() != TMaybeExpires(TExpires::Max()));
        UNIT_ASSERT(!(TExpires::Min() != TMaybeExpires(TExpires::Min())));
        UNIT_ASSERT(TMaybe<TExpires>(TExpires::Min()) != TMaybeExpires(TExpires::Max()));
        UNIT_ASSERT(!(TMaybe<TExpires>(TExpires::Min()) != TMaybeExpires(TExpires::Min())));
        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) != TMaybeExpires(TExpires::Max()));
        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) != TMaybeExpires(TExpires::Min())));

        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) == TExpires::Max()));
        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) == TExpires::Min());
        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) == TMaybe<TExpires>(TExpires::Max())));
        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) == TMaybe<TExpires>(TExpires::Min()));
        UNIT_ASSERT(!(TExpires::Min() == TMaybeExpires(TExpires::Max())));
        UNIT_ASSERT(TExpires::Min() == TMaybeExpires(TExpires::Min()));
        UNIT_ASSERT(!(TMaybe<TExpires>(TExpires::Min()) == TMaybeExpires(TExpires::Max())));
        UNIT_ASSERT(TMaybe<TExpires>(TExpires::Min()) == TMaybeExpires(TExpires::Min()));
        UNIT_ASSERT(!(TMaybeExpires(TExpires::Min()) == TMaybeExpires(TExpires::Max())));
        UNIT_ASSERT(TMaybeExpires(TExpires::Min()) == TMaybeExpires(TExpires::Min()));

        UNIT_ASSERT_VALUES_EQUAL(TExpires::Max().ToInstant(), MaxExpiresTstamp);
        UNIT_ASSERT_VALUES_EQUAL(NCookie::ToStringBuf(TExpires::Past().Render()), PastDate);
    }

    Y_UNIT_TEST(TestCache) {
        const auto t = TInstant::Seconds(123);
        UNIT_ASSERT_VALUES_EQUAL(
            TExpiresGlobalCache<3>::Get(t).ToInstant(), t + TDuration::Seconds(3));
        for (auto i : xrange(3)) {
            UNIT_ASSERT_VALUES_EQUAL(
                TExpiresGlobalCache<3>::Get(t - TDuration::Seconds(i)).ToInstant(), t + TDuration::Seconds(3 - i));
            UNIT_ASSERT_VALUES_EQUAL(
                TExpiresGlobalCache<3>::Get(t + TDuration::Seconds(i)).ToInstant(), t + TDuration::Seconds(3 + i));
        }
    }
}
