#include "tvm.h"

#include <saas/library/daemon_base/config/daemon_config.h>

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

#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>

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

namespace NSaas {
    const TString SECRET = "some_secret_string";
    const NTvmAuth::TTvmId CLIENT_ID = 12345;
    const TString DST_ALIAS = "dest_alias";
    const NTvmAuth::TTvmId DST_CLIENT_ID = 98765;

    const TString CONFIG_FILENAME = "tvm_config.yaml";
    const TString SECTION_NAME = "TVM";

    void WriteConfigToFile(const YAML::Node& yamlConfig, const TString& filename) {
        TUnbufferedFileOutput fo(filename);
        fo << TString(YAML::Dump(yamlConfig)) << Endl;
        fo.Finish();
    }

    TTvmConfig ParseConfig(const TString& configText, const TString& sectionName = SECTION_NAME) {
        TAnyYandexConfig config;
        if (!config.ParseMemory(configText)) {
            TString errors;
            config.PrintErrors(errors);
            UNIT_ASSERT_C(false, errors);
        }
        auto children = config.GetRootSection()->GetAllChildren();
        auto it = children.find(sectionName);
        UNIT_ASSERT(it != children.end());
        TTvmConfig tvmConfig;
        tvmConfig.Init(*it->second);
        return tvmConfig;
    }

    TString CreateConfigString(
        const TString& dstAlias,
        NTvmAuth::TTvmId dstClientId,
        std::optional<TString> secret = {},
        std::optional<TString> envSecret = {},
        std::optional<NTvmAuth::TTvmId> clientId = {},
        std::optional<TString> envClientId = {},
        std::optional<TString> configFilename = {},
        std::optional<TString> configKeySecret = {},
        std::optional<TString> configKeyClientId = {},
        const TString& sectionName = SECTION_NAME
    ) {
        TString str = "<" + sectionName + ">\n";
        if (secret) {
            str += "Secret : " + *secret + "\n";
        }
        if (clientId) {
            str += "ClientId : " + ToString(*clientId) + "\n";
        }
        if (envSecret) {
            str += "EnvKeySecret : " + *envSecret + "\n";
        }
        if (envClientId) {
            str += "EnvKeyClientId : " + *envClientId + "\n";
        }
        if (configKeySecret) {
            str += "ConfigKeySecret : " + *configKeySecret + "\n";
        }
        if (configKeyClientId) {
            str += "ConfigKeyClientId : " + *configKeyClientId + "\n";
        }
        if (configFilename) {
            str += "ConfigFilename : " + *configFilename + "\n";
        }
        str += "DestinationAlias : " + dstAlias + "\n";
        str += "DestinationClientId : " + ToString(dstClientId) + "\n";
        str += "</" + sectionName + ">\n";
        return str;
    }

    void WriteConfig(
        const TString& configFilename,
        std::optional<TString> secret,
        std::optional<NTvmAuth::TTvmId> clientId,
        const TString& secretKey = TTvmConfig::DEFAULT_CONFIG_KEY_SECRET,
        const TString& clientIdKey = TTvmConfig::DEFAULT_CONFIG_KEY_CLIENT_ID
    ) {
        YAML::Node yamlConfig(YAML::NodeType::Map);
        if (secret) {
            yamlConfig[secretKey] = *secret;
        }
        if (clientId) {
            yamlConfig[clientIdKey] = *clientId;
        }
        WriteConfigToFile(yamlConfig, configFilename);
    }

    void CheckConfigOK(
        const TTvmConfig& config,
        const TString& secret,
        NTvmAuth::TTvmId clientId,
        const TString& dstAlias,
        NTvmAuth::TTvmId dstClientId
    ) {
        UNIT_ASSERT(config.IsFilled());
        auto settings = config.GetSettings();
        UNIT_ASSERT_EQUAL(settings.Secret, secret);
        UNIT_ASSERT_EQUAL(settings.ClientId, clientId);
        UNIT_ASSERT_EQUAL(settings.DestinationClients.size(), 1);
        auto& dst = *settings.DestinationClients.begin();
        UNIT_ASSERT_EQUAL(dst.first, dstAlias);
        UNIT_ASSERT_EQUAL(dst.second, dstClientId);
    }

    Y_UNIT_TEST_SUITE(TTvmConfigTest) {
        Y_UNIT_TEST(FilledFromConfig) {
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                SECRET,
                {},
                CLIENT_ID,
                {}
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);
        }

        Y_UNIT_TEST(DefaultEnvVars) {
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, SECRET);
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, ToString(CLIENT_ID));
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            // unset env variables
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, "");
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, "");
        }

        Y_UNIT_TEST(SpecificEnvVars) {
            const TString envSecret = "TVM_SPECIFIC_SECRET";
            const TString envClientId = "TVM_SPECIFIC_CLIENT_ID";
            SetEnv(envSecret, SECRET);
            SetEnv(envClientId, ToString(CLIENT_ID));
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                {},
                envSecret,
                {},
                envClientId
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            // unset env variables
            SetEnv(envSecret, "");
            SetEnv(envClientId, "");
        }

        Y_UNIT_TEST(DefaultsFieldsFromFile) {
            WriteConfig(CONFIG_FILENAME, SECRET, CLIENT_ID);

            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                {}, {}, {}, {},
                CONFIG_FILENAME
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            TFsPath(CONFIG_FILENAME).DeleteIfExists();
        }

        Y_UNIT_TEST(SpecificFieldsFromFile) {
            const TString keySecret = "TVM_SPECIFIC_SECRET";
            const TString keyClientId = "TVM_SPECIFIC_CLIENT_ID";
            WriteConfig(CONFIG_FILENAME, SECRET, CLIENT_ID, keySecret, keyClientId);

            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                {}, {}, {}, {},
                CONFIG_FILENAME,
                keySecret,
                keyClientId
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            TFsPath(CONFIG_FILENAME).DeleteIfExists();
        }

        Y_UNIT_TEST(FirstPriorityConfigData) {
            const TString incorrectSecret = "incorrect_secret";
            const NTvmAuth::TTvmId incorrectClientdId = 54321;
            UNIT_ASSERT_C(SECRET != incorrectSecret, "incorrect test data");
            UNIT_ASSERT_C(CLIENT_ID != incorrectClientdId, "incorrect test data");
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, incorrectSecret);
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, ToString(incorrectClientdId));
            WriteConfig(CONFIG_FILENAME, incorrectSecret, incorrectClientdId);

            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                SECRET,
                {},
                CLIENT_ID,
                {},
                CONFIG_FILENAME
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, "");
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, "");
            TFsPath(CONFIG_FILENAME).DeleteIfExists();
        }

        Y_UNIT_TEST(SecondPriorityEnvData) {
            const TString incorrectSecret = "incorrect_secret";
            const NTvmAuth::TTvmId incorrectClientdId = 54321;
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, SECRET);
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, ToString(CLIENT_ID));
            WriteConfig(CONFIG_FILENAME, incorrectSecret, incorrectClientdId);

            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                {},
                {},
                {},
                {},
                CONFIG_FILENAME
            ));
            CheckConfigOK(config, SECRET, CLIENT_ID, DST_ALIAS, DST_CLIENT_ID);

            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_SECRET, "");
            SetEnv(TTvmConfig::DEFAULT_ENV_KEY_CLIENT_ID, "");
            TFsPath(CONFIG_FILENAME).DeleteIfExists();
        }

        Y_UNIT_TEST(Serialization) {
            const TString envSecret = "specific_env_secret";
            const TString envClientId = "specific_env_client_id";
            const TString keySecret = "specific_config_key_secret";
            const TString keyClientId = "specific_config_key_client_id";
            const TString sectionName = "SpecificTVMConfig";

            TString configStr = CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                SECRET,
                envSecret,
                CLIENT_ID,
                envClientId,
                CONFIG_FILENAME,
                keySecret,
                keyClientId,
                sectionName
            );
            auto config = ParseConfig(configStr, sectionName);
            UNIT_ASSERT(config.IsFilled());
            TString serialized = config.ToString(sectionName.c_str());
            UNIT_ASSERT_EQUAL(serialized, configStr);
        }

        Y_UNIT_TEST(NotFilledSecret) {
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                {}, {},
                CLIENT_ID, {}
            ));
            UNIT_ASSERT(!config.IsFilled());
        }

        Y_UNIT_TEST(NotFilledClientId) {
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                DST_CLIENT_ID,
                SECRET, {},
                {}, {}
            ));
            UNIT_ASSERT(!config.IsFilled());
        }

        Y_UNIT_TEST(NotFilledDstAlias) {
            auto config = ParseConfig(CreateConfigString(
                "",
                DST_CLIENT_ID,
                SECRET, {},
                CLIENT_ID, {}
            ));
            UNIT_ASSERT(!config.IsFilled());
        }

        Y_UNIT_TEST(FailWithZeroDstClientId) {
            auto config = ParseConfig(CreateConfigString(
                DST_ALIAS,
                0,
                SECRET, {},
                CLIENT_ID, {}
            ));
            UNIT_ASSERT(!config.IsFilled());
        }
    }
}
