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

#include "watchdog_opts.h"

#include <util/stream/file.h>
#include <util/system/fs.h>

static const TString FileHandleName = "watchdog_test_handle.tmp";
static const TString FileHandleNameSecond = "watchdog_test_handle_2.tmp";

class TWatchdogOptsTest : public TTestBase {
    UNIT_TEST_SUITE(TWatchdogOptsTest);
    UNIT_TEST(TestOptLoad);
    UNIT_TEST(TestOptOverridesNoFile);
    UNIT_TEST(TestSharedHandles);
    UNIT_TEST(TestMultipleFiles);
    UNIT_TEST_SUITE_END();

    using TData = TWatchdogOptionsCollection::TData;

    static void WriteOpts(const TData& opts, const TString& filename = FileHandleName) {
        TFileOutput fo(filename);
        for (auto kv : opts)
            fo << kv.first << "=" << kv.second << Endl;
        fo.Finish();
    }

    static void WriteOpts(const TString& key, const TString& value, const TString& filename = FileHandleName) {
        TData opts;
        opts[key] = value;
        WriteOpts(opts, filename);
    }

    static TString DumpData(const TWatchdogOptionsCollection& w) {
        TStringStream so;
        w.DumpData(so, ";");
        return so.Str();
    }

    void SetUp() override {
        TearDown();
    }

    void TearDown() override {
        if (NFs::Exists(FileHandleName))
            NFs::Remove(FileHandleName);
    }

    void TestOptLoad() {
        WriteOpts(TData());
        TWatchdogOptionsHandle handle(FileHandleName, TDuration::Days(1000));
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");

        WriteOpts("\tmy.key  ", " some=fancy \x03stuff         \r");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "my.key=some=fancy \x03stuff;", "must load opts, and trim whitespace");

        WriteOpts("my.\xd0\xa0\xd1\x83\xd1\x81", "\xd0\xa0\xd1\x83\xd1\x81""Eng");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "my.\xd0\xa0\xd1\x83\xd1\x81=\xd0\xa0\xd1\x83\xd1\x81""Eng" + TString(";"), "must handle unicode");

        WriteOpts("my.\xd0\xa0", "\xd1\x33\xd1");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "my.\xd0\xa0=\xd1\x33\xd1" + TString(";"), "must handle broken unicode");

        WriteOpts("my.key", "");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "my.key=;", "must NOT distinguish empty from non-existing, when from file");

        WriteOpts(TData());
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must handle deletions");

        {
            TFileOutput fo(FileHandleName);
            fo << "a=b" << Endl;
            fo << Endl;
            fo << "a=c"; // no Endl
            fo.Finish();
        }
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=c;", "must use the last value if duplicated and ignore bad EOLs");

        TData data1;
        data1["a"]="b";
        data1["c"]="d";
        WriteOpts(data1);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=d;", "must handle multiple lines");

        data1.erase("a");
        WriteOpts(data1);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "c=d;", "must handle removals");

        data1.clear();
        data1["a"]="b";
        data1["c"]="d";
        WriteOpts(data1);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=d;", "must handle readditions");

        handle.AsUpdater().Override("c", "x");
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=d;", "must not notify subscribers instantly if not told to");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=x;", "must apply overrides");

        handle.AsUpdater().Override("c", "");
        handle.AsUpdater().NotifyNow();
        UNIT_ASSERT_STRINGS_UNEQUAL_C(DumpData(handle), "a=b;c=x;", "must react to NotifyNow()");
        UNIT_ASSERT_STRINGS_UNEQUAL_C(DumpData(handle), "a=b;c=d;", "must distinguish empty override from cancelled override");
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=;", "must handle empty override");

        handle.AsUpdater().Override("c", TMaybe<TString>());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=;", "must not notify subscribers  override instantly if not told to");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_UNEQUAL_C(DumpData(handle), "a=b;c=;", "DoCheck must apply overrides");
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=d;", "must handle cancellation of override");

        handle.AsUpdater().Override("e", "f");
        handle.AsUpdater().Override("q", TMaybe<TString>());
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=b;c=d;e=f;", "must handle overrides for parameters not present in the file");

        NFs::Remove(FileHandleName);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "e=f;", "must handle overrides even if the file is not present");
    }

    void TestOptOverridesNoFile() {
        TWatchdogOptionsHandle handle(FileHandleName, TDuration::Days(1000));
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");

        handle.AsUpdater().Override("e", "f");
        handle.AsUpdater().Override("q", TMaybe<TString>());
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "e=f;", "must handle overrides");

        handle.AsUpdater().Override("e", TMaybe<TString>());
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must handle overrides");

        handle.AsUpdater().Override("e", "");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "e=;", "must handle overrides");
    }

    void TestSharedHandles() {
        TWatchdogOptionsHandlePtr ptr1 = TWatchdogOptionsHandle::CreateShared(FileHandleName, TDuration::Days(1000));
        TWatchdogOptionsHandlePtr ptr2 = TWatchdogOptionsHandle::CreateShared(FileHandleName, TDuration::Days(1000));
        TWatchdogOptionsHandlePtr ptr3 = TWatchdogOptionsHandle::CreateShared(FileHandleName + "_", TDuration::Days(1000));
        UNIT_ASSERT(ptr1.Get() != ptr3.Get());
        UNIT_ASSERT_C(ptr1.Get() == ptr2.Get(), "must be the same object");
    }

    void TestMultipleFiles() {
        WriteOpts(TData(), FileHandleName);
        WriteOpts(TData(), FileHandleNameSecond);

        TWatchdogOptionsHandle handle({FileHandleName, FileHandleNameSecond}, TDuration::Days(1000));
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "", "must be empty");

        WriteOpts("a", "b", FileHandleName);
        WriteOpts("a", "c", FileHandleNameSecond);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=c;", "second file must override the duplicate key");

        WriteOpts(TData{{"a", "b"}, {"c", "d"}}, FileHandleName);
        WriteOpts(TData{{"a", "e"}, {"f", "g"}}, FileHandleNameSecond);
        handle.DoCheck(TInstant());
        UNIT_ASSERT_STRINGS_EQUAL_C(DumpData(handle), "a=e;c=d;f=g;", "must contain all unique keys from both files");
    }
};

UNIT_TEST_SUITE_REGISTRATION(TWatchdogOptsTest);

