#include "user_access_module.h"
#include <library/cpp/json/json_reader.h>
#include <library/cpp/digest/md5/md5.h>

void TUserAccessModule::TAllowedResources::AddPermissions(TPermissions& result) const {
    for (TServicesSet::const_iterator i = Services.begin(); i != Services.end(); ++i)
        if (*i == "my_service")
            result.Resources.Services.insert(result.MyServices.begin(), result.MyServices.end());
        else
            result.Resources.Services.insert(*i);
}

void TUserAccessModule::TAllowedResources::Serialize(NJson::TJsonValue& result, const TString& name) const {
    if (Services.empty())
        return;
    NJson::TJsonValue& val = result.InsertValue(name, NJson::JSON_MAP).InsertValue("services", NJson::JSON_ARRAY);
    for (TServicesSet::const_iterator i = Services.begin(); i != Services.end(); ++i)
        val.AppendValue(*i);
}

bool TUserAccessModule::TAllowedResources::Deserialize(const NJson::TJsonValue& section) {
    if (!section.Has("services"))
        return false;
    const NJson::TJsonValue::TArray& serv = section["services"].GetArray();
    if (serv.empty())
        return false;
    for (NJson::TJsonValue::TArray::const_iterator i = serv.begin(); i != serv.end(); ++i)
        Services.insert(i->GetString());
    return true;
}

TUserAccessModule::TGroupData::TGroupData(TUserAccessModule::TAccessConfig& owner)
    : Owner(owner)
{}

void TUserAccessModule::TGroupData::Serialize(NJson::TJsonValue& result) const {
    if (!Groups.empty()) {
        NJson::TJsonValue& val = result.InsertValue("groups", NJson::JSON_ARRAY);
        for (TGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
            val.AppendValue(*i);
    }
    if (!MyServices.empty()) {
        NJson::TJsonValue& val = result.InsertValue("user_services", NJson::JSON_ARRAY);
        for (TServicesSet::const_iterator i = MyServices.begin(); i != MyServices.end(); ++i)
            val.AppendValue(*i);
    }
    if (!Commands.empty()) {
        NJson::TJsonValue& val = result.InsertValue("commands", NJson::JSON_MAP);
        for (TCommandToResources::const_iterator i = Commands.begin(); i != Commands.end(); ++i)
            i->second.Serialize(val, i->first);
    }
}

bool TUserAccessModule::TGroupData::Deserialize(const NJson::TJsonValue& section) {
    bool empty = true;
    if (section.Has("groups")) {
        const NJson::TJsonValue::TArray& arr = section["groups"].GetArray();
        if (!arr.empty())
            for (NJson::TJsonValue::TArray::const_iterator i = arr.begin(); i != arr.end(); ++i) {
                Groups.insert(i->GetString());
                empty = false;
            }
    }
    if (section.Has("user_services")) {
        const NJson::TJsonValue::TArray& arr = section["user_services"].GetArray();
        if (!arr.empty())
            for (NJson::TJsonValue::TArray::const_iterator i = arr.begin(); i != arr.end(); ++i) {
                MyServices.insert(i->GetString());
                empty = false;
            }
    }
    if (section.Has("commands")) {
        const NJson::TJsonValue::TMapType& map = section["commands"].GetMap();
        if (!map.empty())
            for (NJson::TJsonValue::TMapType::const_iterator i = map.begin(); i != map.end(); ++i) {
                TAllowedResources res;
                if (res.Deserialize(i->second)) {
                    Commands.insert(std::make_pair(i->first, res));
                    empty = false;
                }
            }
    }
    return !empty;
}

void TUserAccessModule::TGroupData::FillMyServices(TPermissions& result) const {
    for (TGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i) {
        const TGroupData* gr = Owner.GetGroup(*i);
        if (gr)
            gr->FillMyServices(result);
    }
    result.MyServices.insert(MyServices.begin(), MyServices.end());
}

void TUserAccessModule::TGroupData::FillResources(TPermissions& result, const TString& command) const {
    for (TGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i) {
        const TGroupData* gr = Owner.GetGroup(*i);
        if (gr)
            gr->FillResources(result, command);
    }
    TCommandToResources::const_iterator i = Commands.find("all_commands");
    if (i != Commands.end())
        i->second.AddPermissions(result);
    i = Commands.find(command);
    if (i != Commands.end())
        i->second.AddPermissions(result);
}

TUserAccessModule::TUserData::TUserData(TAccessConfig& owner)
: TGroupData(owner)
{}

TUserAccessModule::TAccessConfig::TAccessConfig(const TString& privateKey)
    : PrivateKey(privateKey)
{}

TString TUserAccessModule::TAccessConfig::Serialize() const {
    NJson::TJsonValue result;
    if (!Groups.empty()) {
        NJson::TJsonValue& val = result.InsertValue("groups", NJson::JSON_MAP);
        for (TGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
            i->second.Serialize(val.InsertValue(i->first, NJson::JSON_MAP));
    }
    if (!Users.empty()) {
        NJson::TJsonValue& val = result.InsertValue("users", NJson::JSON_MAP);
        for (TUsers::const_iterator i = Users.begin(); i != Users.end(); ++i)
            i->second.Serialize(val.InsertValue(i->second.GetUserName(), NJson::JSON_MAP));
    }
    TStringStream ss;
    NJson::WriteJson(&ss, &result, true, true, false);
    return ss.Str();
}

void TUserAccessModule::TAccessConfig::Serialize(NRTYDeploy::IVersionedStorage& storage) const {
    storage.SetValue("/common/access_config", Serialize());
}

bool TUserAccessModule::TAccessConfig::Deserialize(NRTYDeploy::IVersionedStorage& storage) {
    if (!PrivateKey)
        return false;
    TString ac;
    if (!storage.GetValue("/common/access_config", ac))
        return false;
    TStringInput si(ac);
    NJson::TJsonValue accessConfig;
    if (!NJson::ReadJsonTree(&si, true, &accessConfig))
        return false;
    if (accessConfig.Has("users")) {
        const NJson::TJsonValue::TMapType& map = accessConfig["users"].GetMap();
        if (!map.empty())
            for (NJson::TJsonValue::TMapType::const_iterator i = map.begin(); i != map.end(); ++i) {
                TUserData res(*this);
                res.SetUserName(i->first);
                if (res.Deserialize(i->second))
                    Users.insert(std::make_pair(GetCookie(i->first), res));
            }
    }
    if (Users.empty())
        return false;
    if (accessConfig.Has("groups")) {
        const NJson::TJsonValue::TMapType& map = accessConfig["groups"].GetMap();
        if (!map.empty())
            for (NJson::TJsonValue::TMapType::const_iterator i = map.begin(); i != map.end(); ++i) {
                TGroupData res(*this);
                if (res.Deserialize(i->second))
                    Groups.insert(std::make_pair(i->first, res));
            }
    }
    return true;
}

const TUserAccessModule::TUserData& TUserAccessModule::TAccessConfig::GetUser(const TString& cookie) const {
    TUsers::const_iterator i = Users.find(cookie);
    if (i == Users.end())
        throw yexception() << "unknown user";
    return i->second;
}

const TUserAccessModule::TGroupData* TUserAccessModule::TAccessConfig::GetGroup(const TString& group) const {
    TGroups::const_iterator i = Groups.find(group);
    return i == Groups.end() ? nullptr : &i->second;
}

TString TUserAccessModule::TAccessConfig::GetCookie(const TString& user) const {
    if (!PrivateKey)
        throw yexception() << "there is no PrivateKey in config";
    return MD5::Calc(PrivateKey + user);
}

void TUserAccessModule::TAccessConfig::SetUserData(const NJson::TJsonValue& conf, const TString& user) {
    TUserData usr(*this);
    if (!usr.Deserialize(conf))
        throw yexception() << "errors in config";
    usr.SetUserName(user);
    TString cookie = GetCookie(user);
    Users.erase(cookie);
    Users.insert(std::make_pair(cookie, usr));
}

void TUserAccessModule::TAccessConfig::SetGroupData(const NJson::TJsonValue& conf, const TString& group) {
    TGroupData gr(*this);
    if (!gr.Deserialize(conf))
        throw yexception() << "errors in config";
    Groups.erase(group);
    Groups.insert(std::make_pair(group, gr));
}

TUserAccessModule::TUserAccessModule(const TCgiParameters& cgi, const TString& command, NRTYDeploy::IVersionedStorage& storage, const TDeployManagerConfig& config)
    : Storage(storage)
    , AccessConfig(config.GetPrivateKey())
{
    if (!AccessConfig.Deserialize(Storage))
        Permisions.User = "no_control";
    else {
        TString cookie = cgi.Get("cookie");
        if (!cookie)
            throw yexception() << "three is no cookie in request";
        const TUserData& userData = AccessConfig.GetUser(cookie);
        Permisions.User = userData.GetUserName();
        userData.FillMyServices(Permisions);
        userData.FillResources(Permisions, command);
    }
}

TString TUserAccessModule::GetCookie(const TString& user) const {
    return AccessConfig.GetCookie(user);
}

TString TUserAccessModule::GetUserConfig(const TString& user, bool group) const {
    if (!user)
        return AccessConfig.Serialize();
    const TGroupData* gd = group ? AccessConfig.GetGroup(user) : &AccessConfig.GetUser(AccessConfig.GetCookie(user));
    if (!gd)
        return "{}";
    NJson::TJsonValue val;
    gd->Serialize(val);
    TStringStream ss;
    NJson::WriteJson(&ss, &val, true, true, false);
    return ss.Str();
}

void TUserAccessModule::SetUserConfig(const TString& config, const TString& user, bool group) {
    TStringInput si(config);
    NJson::TJsonValue val;
    if (!NJson::ReadJsonTree(&si, &val))
        throw yexception() << "errors in json";
    if (group)
        AccessConfig.SetGroupData(val, user);
    else
        AccessConfig.SetUserData(val, user);
    AccessConfig.Serialize(Storage);
}
