#include <util/draft/datetime.h>
#include <util/generic/buffer.h>
#include <util/generic/hash_set.h>
#include <util/stream/buffer.h>
#include <util/charset/wide.h>

#include <mapreduce/yt/interface/client.h>

#include <wmconsole/version3/wmcutil/hostid.h>
#include <wmconsole/version3/wmcutil/siphash.h>
#include <wmconsole/version3/wmcutil/yt/yt_utils.h>
#include <wmconsole/version3/protos/queries2.pb.h>

#include "util.h"

namespace NWebmaster {

namespace {
const char *FIELD_PATH = "Path";
const char *FIELD_IS_USER_NODE = "IsUserPath";

const char *DEVICE_MOBILE_ID = "1";
const char *DEVICE_DESKTOP_ID = "2";
const char *DEVICE_PAD_ID = "3";

const uint64_t SIP_KEY1 = 0x728dc3a1c4b2b96cL;
const uint64_t SIP_KEY2 = 0x21e604856ad44214L;

static const THashSet<TString> STOP_HOSTS = {
    "https:www.youtube.com:443",
    "http:yandex.ru:80",
    "http:yandex.by:80",
    "http:yandex.kz:80",
    "http:yandex.ua:80",
    "http:www.yandex.ru:80",
    "http:www.yandex.by:80",
    "http:www.yandex.kz:80",
    "http:www.yandex.ua:80",
    "https:yandex.ru:443",
    "https:yandex.by:443",
    "https:yandex.kz:443",
    "https:yandex.ua:443",
    "https:www.yandex.ru:443",
    "https:www.yandex.by:443",
    "https:www.yandex.kz:443",
    "https:www.yandex.ua:443"
};
}

const THashSet<TString> &GetStopHostsList() {
    return STOP_HOSTS;
}

TString GetHostId(const TString &hostName) {
    return TWebmasterHostId::FromHostname(hostName).Str();
}

int64_t JavaStringHashCode(const TString &s) {
    TUtf16String ws = UTF8ToWide(s);
    uint32_t hash = 0;
    for (size_t i = 0; i < ws.size(); ++i) {
        hash = 31 * hash + ws[i];
    }
    if (hash <= static_cast<uint32_t>(Max<int32_t>())) {
        return static_cast<int64_t>(hash);
    } else {
        return static_cast<int64_t>(hash) - static_cast<int64_t>(Max<uint32_t>()) - 1;
    }
}

int64_t UI64ToTwoComplimentI64(uint64_t value) {
    static uint64_t MAX_SIGNED = Max<uint64_t>() / 2;
    if (value <= MAX_SIGNED) {
        return static_cast<int64_t>(value);
    } else {
        return static_cast<int64_t>(value - MAX_SIGNED - 1) + Min<int64_t>();
    }
}

int64_t JavaQueryHash(const TString &queryText) {
    TSipHash hasher(SIP_KEY1, SIP_KEY2);
    hasher.Update(queryText.data(), queryText.Size());
    return UI64ToTwoComplimentI64(hasher.Get64());
}

int64_t GetNodeId(const NYT::TNode &raw) {
    TString path = raw[FIELD_PATH].AsString();
    if (raw[FIELD_IS_USER_NODE].AsBool()) {
        path.prepend("user::");
    } else if (path == "/") {
        return 47;
    } else {
        path.prepend("/");
    }
    return JavaStringHashCode(path);
}

TString encode(const TString &in) {
    TStringBuilder sb;
    for (const auto ch : in) {
        switch (ch) {
            case '\\':
                sb.append("\\\\");
                break;
            case '\b':
                sb.append("\\b");
                break;
            case '\f':
                sb.append("\\f");
                break;
            case '\r':
                sb.append("\\r");
                break;
            case '\n':
                sb.append("\\n");
                break;
            case '\t':
                sb.append("\\t");
                break;
            case '\0':
                sb.append("\\0");
                break;
            case '\'':
                sb.append("\\'");
                break;
            default:
                sb.append(ch);
        }
    }
    return sb;
}

time_t GetTimestamp(const NYT::TNode &row, const TString &field) {
    const NYT::TNode &f = row[field];
    if (f.IsUint64()) {
        return f.AsUint64();
    } else {
        return f.AsInt64();
    }
}

time_t GetJavaTimestamp(const NYT::TNode &row, const TString &field) {
    const NYT::TNode &f = row[field];
    if (f.IsUint64()) {
        return f.AsUint64() * 1000;
    } else {
        return f.AsInt64() * 1000;
    }
}

TString PrintTimestampField(const NYT::TNode &row, const TString &field) {
    const NYT::TNode &f = row[field];
    time_t t = NYTUtils::IsNodeNull(f) ? 0 : GetTimestamp(row, field);
    return ToString(t);
//    return Sprintf("%010ld", t);
}

TString PrintNullableString(const NYT::TNode &row, const TString &field) {
    const NYT::TNode f = row[field];
    if (NYTUtils::IsNodeNull(f)) {
        return "";
    } else {
        return encode(f.AsString());
    }
}

uint64_t GetUnsigned(const NYT::TNode &row, const TString &field, uint64_t defaultValue) {
    const NYT::TNode &f = row[field];
    if (NYTUtils::IsNodeNull(f)) {
        return defaultValue;
    }
    if (f.IsInt64()) {
        return (uint64_t) f.AsInt64();
    }
    if (f.IsUint64()) {
        return f.AsUint64();
    }
    ythrow yexception() << "Unexpected node type " << f.GetType();
}

TString PrintNumOrDefault(const NYT::TNode &row, const TString &field, int64_t defaultValue) {
    const NYT::TNode &f = row[field];
    if (NYTUtils::IsNodeNull(f)) {
        return ToString(defaultValue);
    }
    if (f.IsInt64()) {
        return ToString(f.AsInt64());
    }
    if (f.IsUint64()) {
        return ToString(f.AsUint64());
    }
    if (f.IsDouble()) {
        return ToString(f.AsDouble());
    }
    ythrow yexception() << "Unexpected node type " << f.GetType();
}

TString PrintBool(const NYT::TNode &row, const TString &field) {
    const NYT::TNode &f = row[field];
    if (NYTUtils::IsNodeNull(f) || !f.AsBool()) {
        return "0";
    } else {
        return "1";
    }
}

TString PrintDeviceType(bool isMobile, bool isPad) {
    if (isPad) {
        return DEVICE_PAD_ID;
    }
    return isMobile ? DEVICE_MOBILE_ID : DEVICE_DESKTOP_ID;
}

void TQueryStat::Add(const proto::queries2::QueryRegionInfo &regionInfo) {
    SerpsTotal += regionInfo.shown_serps();
    for (const proto::queries2::QueryPositionInfo &positionInfo : regionInfo.position_info()) {
        uint32_t position = positionInfo.position() + 1;

        ShowsTotal += positionInfo.shows_count();
        ClicksTotal += positionInfo.clicks_count();
        if (position == 1) {
            Shows_1 += positionInfo.shows_count();
            Clicks_1 += positionInfo.clicks_count();
        } else if (position >= 2 && position <= 3) {
            Shows_2_3 += positionInfo.shows_count();
            Clicks_2_3 += positionInfo.clicks_count();
            AgrPosShows_2_3 += position * positionInfo.shows_count();
            AgrPosClicks_2_3 += position * positionInfo.clicks_count();
        } else if (position >= 4 && position <= 10) {
            Shows_4_10 += positionInfo.shows_count();
            Clicks_4_10 += positionInfo.clicks_count();
            AgrPosShows_4_10 += position * positionInfo.shows_count();
            AgrPosClicks_4_10 += position * positionInfo.clicks_count();
        } else if (position >= 11 && position <= 50) {
            Shows_11_50 += positionInfo.shows_count();
            Clicks_11_50 += positionInfo.clicks_count();
            AgrPosShows_11_50 += position * positionInfo.shows_count();
            AgrPosClicks_11_50 += position * positionInfo.clicks_count();
        }
    }
    Shows_1_50 = Shows_1 + Shows_2_3 + Shows_4_10 + Shows_11_50;
    Clicks_1_50 = Clicks_1 + Clicks_2_3 + Clicks_4_10 + Clicks_11_50;
    AgrPosShows_1_50 = Shows_1 + AgrPosShows_2_3 + AgrPosShows_4_10 + AgrPosShows_11_50;
    AgrPosClicks_1_50 = Clicks_1 + AgrPosClicks_2_3 + AgrPosClicks_4_10 + AgrPosClicks_11_50;
}

void TQueryStat::Add(const proto::queries2::QueryWeightedAggrInfo &weightedInfo) {
    ShowsTotal = weightedInfo.shows_count();
    ClicksTotal = weightedInfo.clicks_count();
    for (const proto::queries2::QueryPositionGroupAggrInfo& groupInfo : weightedInfo.per_group_info()) {
        switch (groupInfo.group()) {
            case proto::queries2::GROUP_1:
                Shows_1 = groupInfo.shows_count();
                Clicks_1 = groupInfo.clicks_count();
                break;
            case proto::queries2::GROUP_2_3:
                Shows_2_3 = groupInfo.shows_count();
                Clicks_2_3 = groupInfo.clicks_count();
                AgrPosShows_2_3 = groupInfo.shows_position_product();
                AgrPosClicks_2_3 = groupInfo.clicks_position_product();
                break;
            case proto::queries2::GROUP_4_10:
                Shows_4_10 = groupInfo.shows_count();
                Clicks_4_10 = groupInfo.clicks_count();
                AgrPosShows_4_10 = groupInfo.shows_position_product();
                AgrPosClicks_4_10 = groupInfo.clicks_position_product();
                break;
            case proto::queries2::GROUP_11_50:
                Shows_11_50 = groupInfo.shows_count();
                Clicks_11_50 = groupInfo.clicks_count();
                AgrPosShows_11_50 = groupInfo.shows_position_product();
                AgrPosClicks_11_50 = groupInfo.clicks_position_product();
                break;
            default:;
        }
    }
    Shows_1_50 = Shows_1 + Shows_2_3 + Shows_4_10 + Shows_11_50;
    Clicks_1_50 = Clicks_1 + Clicks_2_3 + Clicks_4_10 + Clicks_11_50;
    AgrPosShows_1_50 = Shows_1 + AgrPosShows_2_3 + AgrPosShows_4_10 + AgrPosShows_11_50;
    AgrPosClicks_1_50 = Clicks_1 + AgrPosClicks_2_3 + AgrPosClicks_4_10 + AgrPosClicks_11_50;
}
} //namespace NWebmaster
