#include <util/generic/hash_set.h>

#include <mapreduce/yt/interface/client.h>
#include <wmconsole/version3/wmcutil/regex.h>
#include <wmconsole/version3/wmcutil/string.h>

#include "jupiter.h"
#include "sources.h"

namespace NWebmaster {

const char *JUPITER_PRODUCTION_SOURCE_TAG       = "//home/jupiter/@jupiter_meta/production_current_state";
const char *JUPITER_ACCEPTANCE_SOURCE_TAG       = "//home/jupiter/@jupiter_meta/priemka_current_state";
const char *JUPITER_PREV_PRODUCTION_SOURCE_TAG  = "//home/jupiter/@jupiter_meta/production_prev_state";
const char *JUPITER_DESSERT_PREV_STATE          = "//home/jupiter/@jupiter_meta/dessert_prev_state";

const char *USERFEAT_LONG_USER_BROWSE_PRODUCTION_TAG = "//userfeat/@userfeat_meta/long_user_browse_meta/state_for_production";
const char *USERFEAT_USER_COUNTERS_LEMUR_TAG         = "//userfeat/@userfeat_meta/user_counters_lemur_meta/state_for_production";

const char *JUPITER_SOURCE_ACCEPTANCE_HOST_TPL  = "//home/jupiter/acceptance/$ts/hosttable";
const char *JUPITER_SOURCE_ACCEPTANCE_TPL       = "//home/jupiter/acceptance/$ts/urls_for_webmaster_simple";
const char *JUPITER_SOURCE_CONTENT_ATTRS_TPL    = "//home/jupiter/export/$ts/webmaster/content_attrs";
const char *JUPITER_SOURCE_LINKS_TPL            = "//home/jupiter/export/$ts/webmaster/lemur";
const char *JUPITER_SOURCE_MIRRORS_TRIE_TPL     = "//home/jupiter/export/$ts/mirrors/mirrors.trie";
const char *JUPITER_SOURCE_MIRRORS_TPL          = "//home/jupiter/export/$ts/mirrors/mirrors";
const char *JUPITER_SOURCE_SAMOVAR_PREPARAT_TPL = "//home/jupiter/export/$ts/webmaster/samovar_preparat";
const char *JUPITER_SOURCE_SPREAD_PREFIX        = "//home/jupiter/spread_export";

const char *USERFEAT_SOURCE_LONG_USER_BROWSE_LEMUR_DATA_TPL = "//userfeat/export/long_user_browse/$ts/url/lemur_data";
const char *USERFEAT_SOURCE_USER_COUNTERS_LEMUR_DATA_TPL    = "//userfeat/export/user_counters_lemur/$ts/lemur_data/lemur_data";

const char *SPREAD_TABLE_URLDAT = "urldat";
const char *SPREAD_TABLE_HOSTDAT = "hostdat";

namespace {
const char *F_SOURCE_ID = "SourceId";
const char *F_SOURCE_NAME = "SourceName";
}

static TString GetJupiterTable(NYT::IClientBasePtr client, const TString &pathTemplate, const TString &stateTag) {
    const TString timestamp = client->Get(stateTag).AsString();
    return NUtils::ReplaceAll(pathTemplate, "$ts", timestamp);
}

static TString GetUserfeatTable(NYT::IClientBasePtr client, const TString &pathTemplate, const TString &stateTag) {
    const TString timestamp = ToString(client->Get(stateTag).AsInt64());
    return NUtils::ReplaceAll(pathTemplate, "$ts", timestamp);
}

TString GetJupiterAcceptanceHostTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_ACCEPTANCE_HOST_TPL, JUPITER_ACCEPTANCE_SOURCE_TAG);
}

TString GetJupiterAcceptanceTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_ACCEPTANCE_TPL, JUPITER_ACCEPTANCE_SOURCE_TAG);
}

TString GetJupiterAcceptanceInProdHostTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_ACCEPTANCE_HOST_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterAcceptanceInProdTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_ACCEPTANCE_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterAcceptanceInPrevProdTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_ACCEPTANCE_TPL, JUPITER_PREV_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterContentAttrsTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_CONTENT_ATTRS_TPL, JUPITER_ACCEPTANCE_SOURCE_TAG);
}

TString GetJupiterContentAttrsInProdTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_CONTENT_ATTRS_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterMirrorsTrieFile(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_MIRRORS_TRIE_TPL, JUPITER_ACCEPTANCE_SOURCE_TAG);
}

TString GetJupiterMirrorsTrieInProdFile(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_MIRRORS_TRIE_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterMirrorsInProdTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_MIRRORS_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetJupiterSamovarPreparatInProdTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_SAMOVAR_PREPARAT_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}

TString GetUserfeatLongUserBrowseLemurDataInProdTable(NYT::IClientBasePtr client) {
    return GetUserfeatTable(client, USERFEAT_SOURCE_LONG_USER_BROWSE_LEMUR_DATA_TPL, USERFEAT_LONG_USER_BROWSE_PRODUCTION_TAG);
}

TString GetUserfeatUserCountersLemurDataInProdTable(NYT::IClientBasePtr client) {
    return GetUserfeatTable(client, USERFEAT_SOURCE_USER_COUNTERS_LEMUR_DATA_TPL, USERFEAT_USER_COUNTERS_LEMUR_TAG);
}

bool GetJupiterProductionState(NYT::IClientBasePtr client, TString &state, TString &error) try {
    state = client->Get(JUPITER_PRODUCTION_SOURCE_TAG).AsString();
    return true;
} catch(yexception &e) {
    error = e.what();
    return false;
}

bool GetJupiterAcceptanceState(NYT::IClientBasePtr client, TString &state, TString &error) try {
    state = client->Get(JUPITER_ACCEPTANCE_SOURCE_TAG).AsString();
    return true;
} catch(yexception &e) {
    error = e.what();
    return false;
}

bool GetJupiterDessertAcceptanceState(NYT::IClientBasePtr client, TString &state, TString &error) try {
    state = client->Get(JUPITER_DESSERT_PREV_STATE).AsString();
    return true;
} catch(yexception &e) {
    error = e.what();
    return false;
}

TString GetJupiterSpreadExportPrefix() {
    return JUPITER_SOURCE_SPREAD_PREFIX;
}

time_t GetTsTZFromJupiterState(const TString &state) {
    struct tm timedata;
    Zero(timedata);
    strptime(state.data(), "%Y%m%d-%H%M%S", &timedata);
    time_t ts = mktime(&timedata);
    if (ts == -1) {
        ythrow yexception() << "unable to parse jupiter timestamp " << state;
    }
    return ts;
}

TString GetJupiterStateFromTsTZ(time_t timestamp) {
    const int STATENAME_BUF_LEN = 4 + 2 + 2 + 1 + 2 + 2 + 2 + 1; // YYYYMMDD-HHMMSS*

    TInstant instant = TInstant::Seconds(timestamp);

    tm localTm;
    memset(&localTm, 0, sizeof(localTm));

    instant.LocalTime(&localTm);

    char buf[STATENAME_BUF_LEN];

    int n = snprintf(buf, STATENAME_BUF_LEN, "%04d%02d%02d-%02d%02d%02d",
    localTm.tm_year + 1900,
    localTm.tm_mon + 1,
    localTm.tm_mday,
    localTm.tm_hour,
    localTm.tm_min,
    localTm.tm_sec);

    if (n != (STATENAME_BUF_LEN - 1)) {
        ythrow yexception() << "Can't save timestamp" << timestamp << "into buffer.";
    }

    return TString(buf);
}

TString GetJupiterStateFromPath(const TString &path) {
    TRegularExpression regex("/(\\d+-\\d+)");
    TVector<TString> hits;
    if (regex.GetMatches(path, hits) != 1) {
        ythrow yexception() << "unable to find timestamp in jupiter table name " << path;
    }
    return hits[0];
}

time_t GetJupiterTsTZFromPath(const TString &path) {
    return GetTsTZFromJupiterState(GetJupiterStateFromPath(path));
}

bool IsSpreadUrlBannedBySourceId(const NYT::TNode &row) {
    if (!row.HasKey(F_SOURCE_ID) || row[F_SOURCE_ID].GetType() != NYT::TNode::Uint64) {
        return false;
    }

    if (!row.HasKey(F_SOURCE_NAME) || row[F_SOURCE_NAME].GetType() != NYT::TNode::String) {
        return false;
    }

    if (!TSourceConfigHolder::IsAllowedZoraSource(row[F_SOURCE_NAME].AsString(), row[F_SOURCE_ID].AsUint64())) {
        return true;
    }

    return false;
}

static void LoadSpreadTables(NYT::IClientBasePtr client, time_t jupiterTimestampTZ, TDeque<NYTUtils::TTableInfo> &spreadTables, const TString &tableType) {
    TDeque<NYTUtils::TTableInfo> tmp;
    const TString spreadPrefix = GetJupiterSpreadExportPrefix();

    if (NYTUtils::GetTableList(client, spreadPrefix, tmp) == 0) {
        return;
    }

    std::sort(tmp.begin(), tmp.end(),
        [] (const NWebmaster::NYTUtils::TTableInfo &lhs, const NWebmaster::NYTUtils::TTableInfo &rhs) -> bool {
            return lhs.Name < rhs.Name;
        }
    );

    spreadTables.clear();
    for (const NYTUtils::TTableInfo &table : tmp) {
        if (NYTUtils::GetTableName(table.Name) != tableType) {
            continue;
        }

        time_t spreadTimestamp = GetJupiterTsTZFromPath(table.Name);
        if (spreadTimestamp < jupiterTimestampTZ) {
            continue;
        }

        const TString directoryName = NYTUtils::GetDirectoryName(table.Name);

        if (!NYTUtils::HasAttr(client, directoryName, "completed")) {
            continue;
        }

        if (!NYTUtils::GetAttr(client, directoryName, "completed").AsBool()) {
            continue;
        }

        spreadTables.push_back(table);
    }
}

void LoadSpreadTables(NYT::IClientBasePtr client, time_t jupiterTimestampTZ, TDeque<NYTUtils::TTableInfo> &spreadTables) {
    LoadSpreadTables(client, jupiterTimestampTZ, spreadTables, SPREAD_TABLE_URLDAT);
}

void LoadHostSpreadTables(NYT::IClientBasePtr client, time_t jupiterTimestampTZ, TDeque<NYTUtils::TTableInfo> &spreadTables) {
    LoadSpreadTables(client, jupiterTimestampTZ, spreadTables, SPREAD_TABLE_HOSTDAT);
}

TString GetJupiterCurrentLinksTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_LINKS_TPL, JUPITER_PRODUCTION_SOURCE_TAG);
}
TString GetJupiterPreviousLinksTable(NYT::IClientBasePtr client) {
    return GetJupiterTable(client, JUPITER_SOURCE_LINKS_TPL, JUPITER_PREV_PRODUCTION_SOURCE_TAG);
}

bool IsImagePathExtension(const TString &path) {
    TString extension = GetPathExtension(path);
    static const THashSet<TString> imageTypes({"svg", "pnm", "tiff", "webp", "bmp", "gif", "png", "pjpg", "jpg", "jpeg"});
    return imageTypes.contains(extension);
}

TString GetPathExtension(const TString &path) {
    const TStringBuf buf = TStringBuf(path).Before('?');
    TString extension = ToString((buf.Empty() ? TStringBuf(path) : buf).RAfter('.'));
    extension.to_lower();
    return extension;
}

} //namespace NWebmaster
