#include "nix/util/configuration.hh"
#include "nix/util/args.hh"

#include <sstream>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>

namespace nix {

/* ----------------------------------------------------------------------------
 * Config
 * --------------------------------------------------------------------------*/

TEST(Config, setUndefinedSetting)
{
    Config config;
    ASSERT_EQ(config.set("undefined-key", "value"), false);
}

TEST(Config, setDefinedSetting)
{
    Config config;
    std::string value;
    Setting<std::string> foo{&config, value, "name-of-the-setting", "description"};
    ASSERT_EQ(config.set("name-of-the-setting", "value"), true);
}

TEST(Config, getDefinedSetting)
{
    Config config;
    std::string value;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> foo{&config, value, "name-of-the-setting", "description"};

    config.getSettings(settings, /* overriddenOnly = */ false);
    const auto iter = settings.find("name-of-the-setting");
    ASSERT_NE(iter, settings.end());
    ASSERT_EQ(iter->second.value, "");
    ASSERT_EQ(iter->second.description, "description\n");
}

TEST(Config, getDefinedOverriddenSettingNotSet)
{
    Config config;
    std::string value;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> foo{&config, value, "name-of-the-setting", "description"};

    config.getSettings(settings, /* overriddenOnly = */ true);
    const auto e = settings.find("name-of-the-setting");
    ASSERT_EQ(e, settings.end());
}

TEST(Config, getDefinedSettingSet1)
{
    Config config;
    std::string value;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> setting{&config, value, "name-of-the-setting", "description"};

    setting.assign("value");

    config.getSettings(settings, /* overriddenOnly = */ false);
    const auto iter = settings.find("name-of-the-setting");
    ASSERT_NE(iter, settings.end());
    ASSERT_EQ(iter->second.value, "value");
    ASSERT_EQ(iter->second.description, "description\n");
}

TEST(Config, getDefinedSettingSet2)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};

    ASSERT_TRUE(config.set("name-of-the-setting", "value"));

    config.getSettings(settings, /* overriddenOnly = */ false);
    const auto e = settings.find("name-of-the-setting");
    ASSERT_NE(e, settings.end());
    ASSERT_EQ(e->second.value, "value");
    ASSERT_EQ(e->second.description, "description\n");
}

TEST(Config, addSetting)
{
    class TestSetting : public AbstractSetting
    {
    public:
        TestSetting()
            : AbstractSetting("test", "test", {})
        {
        }

        void set(const std::string & value, bool append) override {}

        std::string to_string() const override
        {
            return {};
        }

        bool isAppendable() override
        {
            return false;
        }
    };

    Config config;
    TestSetting setting;

    ASSERT_FALSE(config.set("test", "value"));
    config.addSetting(&setting);
    ASSERT_TRUE(config.set("test", "value"));
    ASSERT_FALSE(config.set("extra-test", "value"));
}

TEST(Config, withInitialValue)
{
    const StringMap initials = {
        {"key", "value"},
    };
    Config config(initials);

    {
        std::map<std::string, Config::SettingInfo> settings;
        config.getSettings(settings, /* overriddenOnly = */ false);
        ASSERT_EQ(settings.find("key"), settings.end());
    }

    Setting<std::string> setting{&config, "default-value", "key", "description"};

    {
        std::map<std::string, Config::SettingInfo> settings;
        config.getSettings(settings, /* overriddenOnly = */ false);
        ASSERT_EQ(settings["key"].value, "value");
    }
}

TEST(Config, resetOverridden)
{
    Config config;
    config.resetOverridden();
}

TEST(Config, resetOverriddenWithSetting)
{
    Config config;
    Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};

    {
        std::map<std::string, Config::SettingInfo> settings;

        setting.set("foo");
        ASSERT_EQ(setting.get(), "foo");
        config.getSettings(settings, /* overriddenOnly = */ true);
        ASSERT_TRUE(settings.empty());
    }

    {
        std::map<std::string, Config::SettingInfo> settings;

        setting.override("bar");
        ASSERT_TRUE(setting.overridden);
        ASSERT_EQ(setting.get(), "bar");
        config.getSettings(settings, /* overriddenOnly = */ true);
        ASSERT_FALSE(settings.empty());
    }

    {
        std::map<std::string, Config::SettingInfo> settings;

        config.resetOverridden();
        ASSERT_FALSE(setting.overridden);
        config.getSettings(settings, /* overriddenOnly = */ true);
        ASSERT_TRUE(settings.empty());
    }
}

TEST(Config, toJSONOnEmptyConfig)
{
    ASSERT_EQ(Config().toJSON().dump(), "{}");
}

TEST(Config, toJSONOnNonEmptyConfig)
{
    using nlohmann::literals::operator""_json;
    Config config;
    Setting<std::string> setting{
        &config,
        "",
        "name-of-the-setting",
        "description",
    };
    setting.assign("value");

    ASSERT_EQ(
        config.toJSON(),
        R"#({
            "name-of-the-setting": {
              "aliases": [],
              "defaultValue": "",
              "description": "description\n",
              "documentDefault": true,
              "value": "value",
              "experimentalFeature": null
            }
          })#"_json);
}

TEST(Config, toJSONOnNonEmptyConfigWithExperimentalSetting)
{
    using nlohmann::literals::operator""_json;
    Config config;
    Setting<std::string> setting{
        &config,
        "",
        "name-of-the-setting",
        "description",
        {},
        true,
        Xp::Flakes,
    };
    setting.assign("value");

    ASSERT_EQ(
        config.toJSON(),
        R"#({
            "name-of-the-setting": {
              "aliases": [],
              "defaultValue": "",
              "description": "description\n",
              "documentDefault": true,
              "value": "value",
              "experimentalFeature": "flakes"
            }
          })#"_json);
}

TEST(Config, setSettingAlias)
{
    Config config;
    Setting<std::string> setting{&config, "", "some-int", "best number", {"another-int"}};
    ASSERT_TRUE(config.set("some-int", "1"));
    ASSERT_EQ(setting.get(), "1");
    ASSERT_TRUE(config.set("another-int", "2"));
    ASSERT_EQ(setting.get(), "2");
    ASSERT_TRUE(config.set("some-int", "3"));
    ASSERT_EQ(setting.get(), "3");
}

/* FIXME: The reapplyUnknownSettings method doesn't seem to do anything
 * useful (these days).  Whenever we add a new setting to Config the
 * unknown settings are always considered.  In which case is this function
 * actually useful? Is there some way to register a Setting without calling
 * addSetting? */
TEST(Config, DISABLED_reapplyUnknownSettings)
{
    Config config;
    ASSERT_FALSE(config.set("name-of-the-setting", "unknownvalue"));
    Setting<std::string> setting{&config, "default", "name-of-the-setting", "description"};
    ASSERT_EQ(setting.get(), "default");
    config.reapplyUnknownSettings();
    ASSERT_EQ(setting.get(), "unknownvalue");
}

TEST(Config, applyConfigEmpty)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    config.applyConfig("");
    config.getSettings(settings);
    ASSERT_TRUE(settings.empty());
}

TEST(Config, applyConfigEmptyWithComment)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    config.applyConfig("# just a comment");
    config.getSettings(settings);
    ASSERT_TRUE(settings.empty());
}

TEST(Config, applyConfigAssignment)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};
    config.applyConfig(
        "name-of-the-setting = value-from-file #useful comment\n"
        "# name-of-the-setting = foo\n");
    config.getSettings(settings);
    ASSERT_FALSE(settings.empty());
    ASSERT_EQ(settings["name-of-the-setting"].value, "value-from-file");
}

TEST(Config, applyConfigWithReassignedSetting)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};
    config.applyConfig(
        "name-of-the-setting = first-value\n"
        "name-of-the-setting = second-value\n");
    config.getSettings(settings);
    ASSERT_FALSE(settings.empty());
    ASSERT_EQ(settings["name-of-the-setting"].value, "second-value");
}

TEST(Config, applyConfigFailsOnMissingIncludes)
{
    Config config;
    std::map<std::string, Config::SettingInfo> settings;
    Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};

    ASSERT_THROW(
        config.applyConfig(
            "name-of-the-setting = value-from-file\n"
            "# name-of-the-setting = foo\n"
            "include /nix/store/does/not/exist.nix"),
        Error);
}

TEST(Config, applyConfigInvalidThrows)
{
    Config config;
    ASSERT_THROW(config.applyConfig("value == key"), UsageError);
    ASSERT_THROW(config.applyConfig("value "), UsageError);
}
} // namespace nix
