#include "maps/wikimap/mapspro/services/mrc/libs/db/include/toloka/platform.h"
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>

#include <maps/libs/config/include/config.h>
#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/enum_io/include/enum_io.h>

#include <string_view>

using namespace std::string_view_literals;

namespace maps {
namespace mrc {
namespace common {

constexpr enum_io::Representations<ServiceRole>
SERVICE_ROLE_REPRESENTATIONS{
    {ServiceRole::DataGenerator, "data_generator"sv},
    {ServiceRole::DataConsumer, "data_consumer"sv},
};

DEFINE_ENUM_IO(ServiceRole, SERVICE_ROLE_REPRESENTATIONS);

TolokaTaskConfig::TolokaTaskConfig(const xml3::Node& node)
    : templatePoolId_{node.attr("template-pool-id")}
    , poolSize_{node.attr<size_t>("pool-size")}
    , suiteSize_{node.attr<size_t>("suite-size")}
    , goldenTasksCount_{node.attr("golden-tasks-count", 0ull)} {
}

TolokaConfig::TolokaConfig(const xml3::Node& node)
    : host_{node.attr("host")}
    , authHeader_{node.attr("auth-header")} {
    auto tasks = node.firstElementChild("tasks");
    for (auto task = tasks.firstElementChild(); !task.isNull();
         task = task.nextElementSibling()) {
        taskTypeToConfigMap_.emplace(
            maps::enum_io::fromString<db::toloka::TaskType>(task.attr("type")),
            TolokaTaskConfig{task});
    }
}

std::optional<TolokaTaskConfig>
TolokaConfig::taskConfig(db::toloka::TaskType taskType) const {
    auto it = taskTypeToConfigMap_.find(taskType);
    if (it == taskTypeToConfigMap_.end()) {
        return {};
    }
    return std::make_optional(it->second);
}

SignalsUploaderConfig::SignalsUploaderConfig(const xml3::Node& node)
    : queuePath_(node.firstElementChild("queue-path").value()) {
}

YtConfig::YtConfig(const xml3::Node& node)
    : token_(node.attr("token"))
    , cluster_(node.attr("cluster"))
    , path_(node.attr("path"))
    , panoramas_(node.node("panoramas")) {}

NYT::IClientPtr YtConfig::makeClient() const
{
    return NYT::CreateClient(
       TString(cluster()),
       NYT::TCreateClientOptions().Token(TString(token()))
   );
}

const YtConfig::Panoramas& YtConfig::panoramas() const { return panoramas_; }

YtConfig::Panoramas::Panoramas(const xml3::Node& node)
    : exportDir_(node.attr("exportDir"))
{
}

const std::string& YtConfig::Panoramas::exportDir() const
{
    return exportDir_;
}

ClickHouseConfig::ClickHouseConfig(const xml3::Node& node)
    : database_(node.attr("database"))
    , user_(node.attr("user"))
    , password_(node.attr("password"))
    , readHost_(node.node("readHost").attr("url"))
{
    for (auto host = node.firstElementChild("writeHost"); !host.isNull();
         host = host.nextElementSibling("writeHost")) {
        writeHosts_.push_back(host.attr("url"));
    }
}

TakeoutConfig::TakeoutConfig(const xml3::Node& node)
    : ownTvmServiceId_(node.attr<uint32_t>("own-tvm-service-id"))
    , dstTvmServiceId_(node.attr<uint32_t>("dst-tvm-service-id"))
    , tvmSecret_(node.attr("tvm-secret"))
    , url_(node.attr("url"))
{}

EyeTvmConfig::EyeTvmConfig(const xml3::Node& node)
    : selfTvmServiceId_(node.attr<uint32_t>("self-tvm-service-id"))
    , panoDescTvmServiceId_(node.attr<uint32_t>("pano-desc-tvm-service-id"))
    , tvmSecret_(node.tryGetAttr("tvm-secret"))
{}

NexarConfig::NexarConfig(const xml3::Node& node)
    : host_(node.attr("host"))
    , refreshToken_(node.attr("refresh-token"))
{}

WikiAclConfig::WikiAclConfig(const xml3::Node& node)
    : host_(node.attr("host"))
    , tvmId_(node.attr<int>("tvm-id"))
{}

ExternalServicesConfig::ExternalServicesConfig(const xml3::Node& node)
    : blackboxUrl_(node.node("blackbox").attr("url"))
    , nmapsUrl_(node.node("nmaps").attr("url"))
    , socialBackofficeUrl_(node.node("social-backoffice").attr("url"))
    , mrcBrowserUrl_(node.node("browser").attr("url"))
    , mrcBrowserProUrl_(node.node("browser-pro").attr("url"))
    , s3MdsUrl_(node.node("s3mds").attr("url"))
    , s3MdsPublicReadHost_(node.node("s3mds").attr("public-read-host", std::string{}))
    , yavisionUrl_(node.node("yavision").attr("url"))
    , geoIdCoveragePath_(node.node("geo-id-coverage").attr("path"))
    , ytConfig_(node.node("yt"))
    , clickHouseConfig_(node.node("clickhouse"))
    , takeoutConfig_(node.node("takeout"))
    , eyeTvmConfig_(node.node("eye-tvm"))
    , nirvana_{node.node("nirvana")}
    , mapsCoreUgcBackofficeUrl_(node.node("maps-core-ugc-backoffice").attr("url"))
    , mapsCoreNmapsMrcUgcBackUrl_(node.node("maps-core-nmaps-mrc-ugc-back").attr("url"))
    , ecstaticEnvironmentOverride_(loadEcstaticEnvironmentEverride(node))
    , stvdescrUrl_(node.node("maps-core-stvdescr").attr("url"))
    , firmwareUpdaterHost_(node.node("firmware-updater").attr("host"))
    , wikiEditorHost_(node.node("wiki-editor").attr("host"))
    , nexar_(node.node("nexar"))
    , wikiAcl_(node.node("wiki-acl"))
    , geosearchUrl_(node.node("geosearch").attr("url"))
{}

TaxiImportConfig::TaxiImportConfig(const xml3::Node& node)
    : ytCluster_(node.node("yt").attr("cluster"))
    , ytEventsPath_(node.node("yt").attr("events-path"))
    , ytTracksPath_(node.node("yt").attr("tracks-path"))
    , ytToken_(node.node("yt").attr("token"))
{}

std::optional<maps::common::Environment>
ExternalServicesConfig::loadEcstaticEnvironmentEverride(const xml3::Node& node) const
{
    auto ecstaticNode = node.node("ecstatic", /* quiet = */ true);
    if (!ecstaticNode.isNull()) {
        auto optEnvironmentOverride = ecstaticNode.tryGetAttr("environment-override");
        if (optEnvironmentOverride) {
            maps::common::Environment env;
            fromString(*optEnvironmentOverride, env);
            return env;
        }
    }

    return std::nullopt;
}

NirvanaConfig::NirvanaConfig(const xml3::Node& node)
    : oauthToken_{node.attr("oauth-token")} {
}

namespace {

const auto UNITTEST_CONFIG_TEMPLATE_RESOURCE_KEY = "t-config.unittest.xml";
const auto PATH_PREFIX_MDS = "/config/external-services/mds";
const auto PATH_PREFIX_MDS_PUBLIC = "/config/external-services/mds-public";


std::string
getTemplateResource(ServiceRole role)
{
    if (role == ServiceRole::DataGenerator) {
        return "t-config.xml";
    } else if (role == ServiceRole::DataConsumer) {
        return "t-consumer-config.xml";
    }
    REQUIRE(false, "Unsupported value of service role " << role);
}

std::string getHostOr(const xml3::Node& node, const std::string& attrName)
{
    if (auto host = node.tryGetAttr("host")) {
        return host.value();
    }
    return node.attr(attrName);
}

void resetMdsConfig(mds::Configuration& cfg, const xml3::Node& node)
{
    using namespace std::chrono_literals;
    cfg.setReadHost(getHostOr(node, "read-host"))
       .setWriteHost(getHostOr(node, "write-host"))
       .setNamespaceName(node.attr("namespace-name"))
       .setPathPrefix(node.attr("path-prefix"))
       .setMaxRequestAttempts(4)
       .setRetryInitialTimeout(1s)
       .setTimeout(5s);
    if (auto readPort = node.tryGetAttr<unsigned>("read-port")) {
        cfg.setReadPort(*readPort);
    }
    if (auto writePort = node.tryGetAttr<unsigned>("write-port")) {
        cfg.setWritePort(*writePort);
    }
}

mds::Configuration makeMdsConfig(const xml3::Node& node)
{
    auto result = mds::Configuration{"", // reset host later
                                     "", // reset namespace later
                                     node.attr("auth-header")};
    resetMdsConfig(result, node);
    return result;
}

mds::Configuration makeMdsConfig(
    const xml3::Node& node,
    std::shared_ptr<NTvmAuth::TTvmClient> tvmClient)
{
    auto url = getHostOr(node, "write-host");
    auto mdsEnv = mds::STABLE.url == url
                      ? mds::STABLE
                      : mds::TESTING.url == url
                            ? mds::TESTING
                            : throw RuntimeError() << "Unknown MDS: " << url;
    auto result = mds::Configuration{mdsEnv,  // reset host later
                                     "",      // reset namespace later
                                     std::move(tvmClient)};
    resetMdsConfig(result, node);
    return result;
}

} // namespace

Config Config::forTests() {
    return {std::make_shared<xml3::Doc>(
        xml3::Doc::fromString(
            vault_boy::RandomContext{}.renderTemplate(
                config::readConfigFile(UNITTEST_CONFIG_TEMPLATE_RESOURCE_KEY))))};
}

Config::Config(const vault_boy::Context& ctx, ServiceRole role)
    : Config(std::make_shared<xml3::Doc>(
    xml3::Doc::fromString(
        ctx.renderTemplate(
            config::readConfigFile(getTemplateResource(role)))))) {
}

Config::Config(const vault_boy::Context& ctx, const std::string& path)
    : Config(std::make_shared<xml3::Doc>(
    xml3::Doc::fromString(
        ctx.renderTemplate(
            maps::common::readFileToString(path))))) {
}

Config::Config(const std::string& path)
    : Config(std::make_shared<xml3::Doc>(
    xml3::Doc::fromFile(path))) {
}

Config::Config(SharedXmlDoc xml)
    : xml_{std::move(xml)}
    , mdsConfiguration_{makeMdsConfig(xml_->node(PATH_PREFIX_MDS))}
    , publicMdsConfiguration_{makeMdsConfig(xml_->node(PATH_PREFIX_MDS_PUBLIC))}
    , signalsUploader_{xml_->node("/config/common/signals-uploader")}
    , externals_{xml_->node("/config/external-services")}
    , taxiImportConfig_{xml_->node("/config/taxi-import")}
{
    crowdPlatformToConfig_.emplace(
        db::toloka::Platform::Toloka,
        TolokaConfig(xml_->node("/config/external-services/toloka"))
    );
    crowdPlatformToConfig_.emplace(
        db::toloka::Platform::Yang,
        TolokaConfig(xml_->node("/config/external-services/yang"))
    );
}

Config Config::fromString(const std::string& config)
{
    return Config(
        std::make_shared<xml3::Doc>(xml3::Doc::fromString(config))
    );
}

std::string Config::toString() const
{
    std::string result;
    xml_->save(result);
    return result;
}

wiki::common::PoolHolder
Config::makePoolHolder(const std::string& dbId, const std::string& poolId) const {
    return wiki::common::PoolHolder{
        xml_->node("/config/common/databases/database[@id=\"" + dbId + "\"]"),
        dbId,
        poolId
    };
}

SharedPool
Config::makeSharedPool(const std::string& dbId, const std::string& poolId) const {
    return std::make_shared<wiki::common::PoolHolder>(
        xml_->node("/config/common/databases/database[@id=\"" + dbId + "\"]"),
        dbId,
        poolId
    );
}

std::string Config::rideReportsEmailAddress() const {
    return xml_->node("/config/common/ride-reports-email").value();
}

void Config::enableTvmClient()
{
    auto tvmClient =
        std::make_shared<NTvmAuth::TTvmClient>(auth::TvmtoolSettings().makeTvmClient());
    mdsConfiguration_ =
        makeMdsConfig(xml_->node(PATH_PREFIX_MDS), tvmClient);
    publicMdsConfiguration_ =
        makeMdsConfig(xml_->node(PATH_PREFIX_MDS_PUBLIC), tvmClient);
}

} // common
} // mrc
} // maps
