#include "request_wizard.h"

#include <google/protobuf/text_format.h>
#include <library/cpp/lua/eval.h>
#include <library/cpp/logger/global/global.h>
#include <util/stream/file.h>

TRequestWizard::TRequestWizard(const TRequestWizardConfig& config)
    : Rules(config.Rules)
{}

void TRequestWizard::Process(TServerRequestData& request, const TString& firstLine) const {
    size_t pos = firstLine.find(' ');
    auto rulesSet = Rules.find(firstLine.substr(0, pos));
    if (rulesSet != Rules.end())
        for (const auto& rule : rulesSet->second)
            if (rule.Process(request))
                return;
}

TString TRequestWizard::Help() const {
    TString result;
    for (const auto& rules : Rules)
        for (const auto& rule : rules.second)
            result += rule.ToString(rules.first) + "\n";
    return result;
}

TRequestWizard::TRule::TRule(const NRTYRequestWizard::TRule& cfg) {
    TVector<TString> uri = SplitString(cfg.GetUri(), "/", 0, KEEP_EMPTY_TOKENS);
    bool var = false;
    for (const auto& token : uri) {
        if (token[0] == ':') {
            var = true;
            VariableUriPart.push_back(token.substr(1));
        }
        else if (var)
            ythrow yexception() << "fixed part must be before variable in rule: " << cfg.DebugString();
        else
            FixedUriPart.push_back(token);
    }
    for (size_t i = 0; i < cfg.CgiSize(); ++i) {
        const TString& cgi = cfg.GetCgi(i);
        size_t pos = cgi.find('=');
        Cgi.insert(std::make_pair(cgi.substr(0, pos), pos == TString::npos ? nullptr : cgi.data() + pos));
    }
    Result = cfg.GetResultLua();
}

bool TRequestWizard::TRule::Process(TServerRequestData& request) const {
    TVector<TString> uri = SplitString(TString{request.ScriptName()}, "/", 0, KEEP_EMPTY_TOKENS);
    if (uri.size() != FixedUriPart.size() + VariableUriPart.size())
        return false;
    auto i = uri.begin();
    for (const auto& token : FixedUriPart)
        if (*(i++) != token)
            return false;
    TLuaEval eval;
    for (const auto& token : VariableUriPart)
        eval.SetVariable(token, *(i++));
    for (const auto& param : Cgi)
        if (request.CgiParam.Has(param.first))
            eval.SetVariable(param.first, request.CgiParam.Get(param.first));
        else if (!param.second.Required)
            eval.SetVariable(param.first, param.second.DefValue);
        else
            ythrow yexception() << "required param " << param.first << " not set";
    TString resRequest = eval.Preprocess(Result);
    request.Parse(resRequest.data());
    request.CgiParam.Scan(request.Query());
    return true;
}

bool TRequestWizard::TRule::operator<(const TRequestWizard::TRule& other) const {
    for (size_t i = 0; i < Min(FixedUriPart.size(), other.FixedUriPart.size()); ++i)
        if (FixedUriPart[i] < other.FixedUriPart[i])
            return false;
        else if (FixedUriPart[i] > other.FixedUriPart[i])
            return true;
    if (FixedUriPart.size() > other.FixedUriPart.size())
        return true;
    if (FixedUriPart.size() < other.FixedUriPart.size())
        return false;
    if (VariableUriPart.size() > other.VariableUriPart.size())
        return true;
    if (VariableUriPart.size() < other.VariableUriPart.size())
        return false;
    return false;
}

TString TRequestWizard::TRule::ToString(const TString& method) const {
    TString result = method + " " + JoinStrings(FixedUriPart, "/");
    for (const auto& token : VariableUriPart)
        result += "/:" + token;
    result += "?";
    for (const auto& param : Cgi)
        if (param.second.Required)
            result += "&" + param.first;
        else
            result += "[&" + param.first + "=" + param.second.DefValue + "]";
    result += " => " + Result;
    return result;
}

TRequestWizard::TRule::TCgi::TCgi(const char* value) {
    TStringBuf val(value);
    Required = !val;
    if (!Required)
        DefValue = val.substr(1);
}

TRequestWizard::TRequestWizardConfig::TRequestWizardConfig()
    : Rules()
    , RequestWizardConfigPath()
{
}

void TRequestWizard::TRequestWizardConfig::Init(const TString& rwc) {
    RequestWizardConfigPath = rwc;

    if (!RequestWizardConfigPath.IsDefined()) {
        return;
    }
    VERIFY_WITH_LOG(RequestWizardConfigPath.Exists(), "Request wizard config not exists");

    InitFromString(TUnbufferedFileInput(RequestWizardConfigPath).ReadAll());
}

void TRequestWizard::TRequestWizardConfig::InitFromString(const TString& text) {
    NRTYRequestWizard::TRequestWizardConfig config;
    VERIFY_WITH_LOG(::google::protobuf::TextFormat::ParseFromString(text, &config), "cannot deserialize requesr wizard config");

    for (const auto& rule: config.GetRules()) {
        TRule newRule(rule);
        TString method(rule.TMethod_Name(rule.GetMethod()));
        VERIFY_WITH_LOG(!Rules[method].contains(newRule), "conflict rules in wizard config")
        Rules[method].insert(newRule);
    }
}

TString TRequestWizard::TRequestWizardConfig::ToString() const {
    TStringStream ss;
    ss << RequestWizardConfigPath << Endl;
    return ss.Str();
}

bool TRequestWizard::TRequestWizardConfig::IsInitialized() const {
    return RequestWizardConfigPath.IsDefined();
}

