#include "convert.h"
#include "processing_tree.h"
#include "resolver.h"

#include <balancer/yt/log-tool/proto/schema.pb.h>

#include <util/datetime/base.h>
#include <util/generic/string.h>
#include <util/generic/queue.h>
#include <util/string/cast.h>
#include <util/string/escape.h>
#include <util/string/split.h>
#include <util/string/join.h>
#include <util/system/types.h>

using namespace NBalancerYt;

using TProdLog = NBalancerYt::NProto::TProdLog;
using THamsterLog = NBalancerYt::NProto::THamsterLog;

using TLog = NBalancerYt::NProto::TLog;
using TProcessingTree = NBalancerYt::NProto::TProcessingTree;
using TResolvedLog = NBalancerYt::NProto::TResolvedLog;

struct TCheckResult {
    TString Error;
    TString ErrorType;
    TString ResponseCode;
};

TCheckResult ParseProxy(const TProcessingTree& node) {
    TCheckResult result;
    const auto& payload = node.GetPayload();
    if (payload.size() > 2 && payload[2].GetValue() == "system_error") {
        result.ErrorType = "backend_error";
        result.Error = "system_error:" + payload[3].GetValue();
    } else if (payload.size() > 4 && payload[3].GetValue() == "system_error") {
        result.Error = "system_error:" + payload[4].GetValue();
    } else if (payload.size() > 5 && payload[4].GetValue() == "system_error") {
        result.Error = Join(" ", payload[3].GetValue(), payload[4].GetValue(), payload[5].GetValue());
        result.ErrorType = payload[3].GetValue();
    } else if (payload.size() > 6 && payload[5].GetValue() == "system_error") {
        result.Error = Join(" ", payload[3].GetValue(), payload[4].GetValue(), payload[5].GetValue(), payload[6].GetValue());
        if (result.Error == "flush failed system_error EIO") {
            result.ErrorType = "client";
        }
    } else if (payload.size() > 4) {
        result.ResponseCode = payload[4].GetValue();
        if (payload[3].GetValue() != "succ") {
            result.Error = payload[3].GetValue();
        }
    }
    return result;
}

template<>
void NBalancerYt::ConvertRowTo<TLog, TResolvedLog>(const TLog& input, TResolvedLog* output) {
    output->SetStartTime(input.GetStartTime());
    output->SetDuration(input.GetDuration());
    output->SetReqId(input.GetReqId());

    TPriorityQueue<std::tuple<int, size_t, TString>> errors;
    TPriorityQueue<std::tuple<int, size_t, TString>> errorTypes;
    TPriorityQueue<std::tuple<int, size_t, TString>> destinations;
    TString responseCode;

    errors.emplace(0, 0, "");
    errorTypes.emplace(0, 0, "");
    destinations.emplace(0, 0, "");

    ProcessingTreeForeach(input.GetProcessingTree(), [&] (const TProcessingTree& node) {
        if (node.GetModuleName() == "regexp_path") {
            destinations.emplace(100, destinations.size(), "regexp_path:" + node.GetPayload(0).GetValue());
        } else if (node.GetModuleName() == "proxy") {
            TCheckResult proxyInfo = ParseProxy(node);
            if (proxyInfo.Error) {
                errors.emplace(100, errors.size(), proxyInfo.Error);
            }
            if (proxyInfo.ErrorType) {
                errorTypes.emplace(100, errorTypes.size(), proxyInfo.ErrorType);
            }
            if (proxyInfo.ResponseCode) {
                responseCode = proxyInfo.ResponseCode;
            }
        } else if (node.GetModuleName() == "clck_lib") {
            if (node.GetPayload(0).GetValue() != "click_lib_answer") {
                errors.emplace(99, errors.size(), node.GetPayload(0).GetValue());
                errorTypes.emplace(99, errorTypes.size(), "clck_lib");
            } else {
                responseCode = node.GetPayload(1).GetValue();
            }
        }
    });

    output->SetDestination(std::get<2>(destinations.top()));

    output->SetError(std::get<2>(errors.top()));
    output->SetErrorType(std::get<2>(errorTypes.top()));
    output->SetResponseCode(responseCode);
}

template<>
void NBalancerYt::ConvertRowTo<THamsterLog, TResolvedLog>(const THamsterLog& input, TResolvedLog* output) {
    TLog tmp;
    NBalancerYt::ConvertRowTo(input, &tmp);
    NBalancerYt::ConvertRowTo(tmp, output);
}

template<>
void NBalancerYt::ConvertRowTo<TProdLog, TResolvedLog>(const TProdLog& input, TResolvedLog* output) {
    TLog tmp;
    NBalancerYt::ConvertRowTo(input, &tmp);
    NBalancerYt::ConvertRowTo(tmp, output);
}
