#include "configuration.h"

#include <util/stream/file.h>
#include <util/string/builder.h>


namespace {


template <typename T>
const T GetYAMLScalar(const YAML::Node& node, const std::string& name) {
    try {
        return node.as<T>();
    } catch (const std::runtime_error& e) {
        if (node.Type() == YAML::NodeType::Scalar) {
            ythrow yexception() << "Error while trying to convert value " << name 
                                << " ('" << node.as<std::string>() << "') to type " << TypeName(T{})
                                << ":\n" << e.what();
        } else {
            ythrow yexception() << "Error while trying to convert value " << name 
                                << " to type " << TypeName(T{}) << ":\n" << e.what();
        }
    }
}

template <>
const TString GetYAMLScalar(const YAML::Node& node, const std::string& name) {
    return TString(GetYAMLScalar<std::string>(node, name));
}

const YAML::Node SubscriptYAMLMap(const YAML::Node& map, const std::string& key) {
    try {
        return map[key];
    } catch (const std::runtime_error& e) {
        ythrow yexception() << "Error while trying to access key '" << key 
                            << "':\n" << e.what();
    }
}

template <typename T>
const T SubscriptYAMLMapAndGetScalar(const YAML::Node& map, const std::string& key) {
    return GetYAMLScalar<T>(SubscriptYAMLMap(map, key), key);
}

template <typename T>
const T SubscriptYAMLMapAndGetScalar(const YAML::Node& map, const std::string& key, T defaultValue) {
    try {
        return SubscriptYAMLMapAndGetScalar<T>(map, key);
    } catch (...) {
        return defaultValue;
    }
}

const YAML::Node SubscriptYAMLMapAndGetMap(const YAML::Node& map, const std::string& key) {
    const YAML::Node item = SubscriptYAMLMap(map, key);
    Y_ENSURE(item.IsMap(), TStringBuilder{} << "Expected " << key << " to be a map.");
    return item;
}

const YAML::Node SubscriptYAMLMapAndGetList(const YAML::Node& map, const std::string& key) {
    const YAML::Node item = SubscriptYAMLMap(map, key);
    Y_ENSURE(item.IsSequence(), TStringBuilder{} << "Expected " << key << " to be a list.");
    return item;
}


} // namespace


bool TConfiguration::ReadFromYAMLFile(const TString& filePath) {
    try {
        return ReadFromYAMLRootNode(YAML::LoadFile(filePath));
    } catch (std::runtime_error e) {
        Cerr << "Error while trying to load YAML file '" << filePath << "':\n" << e.what();
        return false;
    }
}

bool TConfiguration::ReadFromYAMLString(const std::string& configuration) {
    return ReadFromYAMLRootNode(YAML::Load(configuration));
}

bool TConfiguration::ReadFromYAMLRootNode(const YAML::Node& root) {
    try {
        Y_ENSURE(root.IsMap(), "Expected the root to be a map");

        // Ports
        NELPort   = SubscriptYAMLMapAndGetScalar<ui16>(root, "nel-port");
        StatsPort = SubscriptYAMLMapAndGetScalar<ui16>(root, "stats-port");
        AdminPort = SubscriptYAMLMapAndGetScalar<ui16>(root, "admin-port");

        // Flags
        WriteAccessLog = SubscriptYAMLMapAndGetScalar<bool>(root, "write-access-log", true);
        WriteReportLog = SubscriptYAMLMapAndGetScalar<bool>(root, "write-report-log", true);

        // File paths
        if (WriteAccessLog) {
            AccessLogFilePath = SubscriptYAMLMapAndGetScalar<TString>(root, "access-log-file");
        }
        if (WriteReportLog) {
            ReportLogFilePath = SubscriptYAMLMapAndGetScalar<TString>(root, "nel-report-log-file");
        }
        GeodataFilePath = SubscriptYAMLMapAndGetScalar<TString>(root, "geodata-file");

        // Max report age
        MaxReportAge = TDuration::Seconds(SubscriptYAMLMapAndGetScalar<ui64>(root, "max-report-age", 100));

        // ASN groups
        const YAML::Node asnGroups = SubscriptYAMLMapAndGetMap(root, "asns");
        for (const auto& asnGroupKV : asnGroups) {
            const TString asnGroupName = GetYAMLScalar<TString>(asnGroupKV.first, "asn group name");
            const YAML::Node asns = asnGroupKV.second;
            Y_ENSURE(asns.IsSequence(), "Expected asn group value to be a list.");

            // ASNs
            for (const YAML::Node& asn : asns) {
                ASNGroups_[asnGroupName].insert(GetYAMLScalar<TString>(asn, "asn"));
            }
        }

        // Tenants
        const YAML::Node tenants = SubscriptYAMLMapAndGetMap(root, "tenants");
        for (const auto& tenantKV : tenants) {
            const TString tenantName = GetYAMLScalar<TString>(tenantKV.first, "tenant name");
            const YAML::Node tenant = tenantKV.second;
            Y_ENSURE(tenant.IsMap(), "Expected tenant to be a map");

            // ASNs
            Tenants[tenantName].ASNs = &ASNGroups_[SubscriptYAMLMapAndGetScalar<TString>(tenant, "asns")];

            // Flags
            Tenants[tenantName].FullErrorTypes = SubscriptYAMLMapAndGetScalar<bool>(tenant, "enable-full-error-types", true);

            // Handles
            const TString NELHandle     = SubscriptYAMLMapAndGetScalar<TString>(
                tenant, "nel-handle", 
                TStringBuilder{} << "/" << tenantName << "/nel"
            );
            const TString UnistatHandle = SubscriptYAMLMapAndGetScalar<TString>(
                tenant, "unistat-handle",
                TStringBuilder{} << "/" << tenantName << "/unistat"
            );
            const TString SolomonHandle = SubscriptYAMLMapAndGetScalar<TString>(
                tenant, "solomon-handle",
                TStringBuilder{} << "/" << tenantName << "/solomon"
            );

            // Verify that handles are unique
            Y_ENSURE(!Handles.contains(NELHandle),     TStringBuilder{} << "Duplicate handle name " << NELHandle);
            Y_ENSURE(!Handles.contains(UnistatHandle), TStringBuilder{} << "Duplicate handle name " << UnistatHandle);
            Y_ENSURE(!Handles.contains(SolomonHandle), TStringBuilder{} << "Duplicate handle name " << SolomonHandle);

            // Fill handles map
            Handles.emplace(NELHandle,     THandleInfo(EHandlePurpose::NEL,     tenantName));
            Handles.emplace(UnistatHandle, THandleInfo(EHandlePurpose::UNISTAT, tenantName));
            Handles.emplace(SolomonHandle, THandleInfo(EHandlePurpose::SOLOMON, tenantName));

            // TODO: Make sure there are no duplicate site names

            // Sites
            const YAML::Node sites = SubscriptYAMLMapAndGetMap(tenant, "sites");
            Tenants[tenantName].Sites.reserve(sites.size());
            NPire::TNonrelocScanner sitesScanner;

            for (const auto& siteKV : sites) {
                const TString siteName = GetYAMLScalar<TString>(siteKV.first, "site name");
                const YAML::Node site = siteKV.second;
                Y_ENSURE(site.IsMap(), "Expected site to be a map.");

                Tenants[tenantName].Sites.push_back(siteName);

                // Regexp
                NPire::TFsm currentSiteFsm = NPire::TFsm::MakeFalse();
                const YAML::Node regexps = SubscriptYAMLMapAndGetList(site, "regexps");
                for (const YAML::Node& patternNode : regexps) {
                    currentSiteFsm |= NPire::TLexer(GetYAMLScalar<TString>(patternNode, "regexp")).Parse();
                }
                sitesScanner = NPire::TScanner::Glue(sitesScanner, currentSiteFsm.Compile<NPire::TNonrelocScanner>());
            }
            Tenants[tenantName].SitesRegex = sitesScanner;
        }
        return true;
    }

    catch (yexception e) {
        Cerr << "Error while trying to parse configuration YAML:\n"
             << e.what() << '\n';
        return false;
    }
}

