#include <util/charset/wide.h>
#include <util/digest/fnv.h>
#include <util/string/join.h>
#include <util/string/printf.h>

#include <robot/kwyt/library/sharding/sharder.h>

#include <wmconsole/version3/wmcutil/http_client.h>
#include <wmconsole/version3/wmcutil/args.h>
#include <wmconsole/version3/wmcutil/log.h>
#include <wmconsole/version3/wmcutil/http_server.h>
#include <wmconsole/version3/wmcutil/yt/yt_utils.h>

#include <wmconsole/version3/processors/tools/cms-detect/tokenizer/html_extractor.h>

namespace NWebmaster {

using TTokenId = ui64;

const TString config_TABLE_ROOT             = "//home/webmaster/users/lester/CMS_v2";
const TString config_TABLE_POOL_ROOT        = NYTUtils::JoinPath(config_TABLE_ROOT, "pools");
const TString config_TABLE_POOL_ENABLED_TOKENS  = NYTUtils::JoinPath(config_TABLE_POOL_ROOT, "enabled-tokens");

TString GetMainMirror(const TString &host) {
    const TString content = HttpPost<TString>("http://webmaster-canonizer.n.yandex.ru/getMirror", host);
    TString tmp, mainMirror;
    Split(content, '=', tmp, mainMirror);
    if (mainMirror.Contains("ERROR")) {
        return host;
    }
    return StripString(mainMirror);
}

struct TTokenizer {
    TTokenizer(NYT::IClientBasePtr client)
        : Client(client)
    {
        LoadEnabledTokens(config_TABLE_POOL_ENABLED_TOKENS);
        Cerr << "TTokenizer::TTokenizer() - done" << Endl;
    }

    TString GetTokens(const TString &host) {
        TMap<size_t, NYT::TNode> rows;
        auto reader = Client->CreateTableReader<NYT::TNode>(GetPathObject(host));
        for (; reader->IsValid(); reader->Next()) {
            const NYT::TNode &row = reader->GetRow();

            if (NYTUtils::IsNodeNull(row["HttpCode"])) {
                continue;
            }

            const auto httpCode = row["HttpCode"].AsUint64();
            const auto lastAccess = row["LastAccess"].AsUint64();

            if ((httpCode == 200 || (httpCode >= 2000 && httpCode < 3000))) {
                if (!NYTUtils::IsNodeNull(row["Html"]) && !NYTUtils::IsNodeNull(row["Headers"])) {
                    rows.emplace(lastAccess, row);
                }
            }
        }

        if (rows.empty()) {
            Cerr << "rows.empty()" << Endl;
            return "";
        }

        TDeque<TString> tokensList;
        THtmlContentExtractor extractor(rows.rbegin()->second["Html"].AsString(), tokensList);
        THashSet<TString> tokens(tokensList.begin(), tokensList.end());
        TDeque<TStringBuf> filteredTokens;

        for (const auto& token : tokens) {
            if (EnabledTokensHashes.contains(FnvHash<TTokenId>(token.data(), token.size()))) {
                filteredTokens.emplace_back(token);
            }
        }

        return JoinSeq(" ", tokens);
    }

    NYT::TRichYPath GetPathObject(const TString &host) {
        NYT::TRichYPath path("//home/webmaster/users/lester/CMS2018-WMC6628/validate-html");
        //const static NKwYT::TSharder<> SHARDER(32);
        //NYT::TRichYPath path(NYTUtils::JoinPath("//home/kwyt/pages", Sprintf("%03lu", SHARDER.GetShardNumber(host + "/")), "data"));
        path.AddRange(NYT::TReadRange().Exact(NYT::TReadLimit().Key(NYT::TKey(host, "/"))));
        return path;
    }

    void LoadEnabledTokens(const TString &table) {
        auto reader = Client->CreateTableReader<NYT::TNode>(table);
        for (; reader->IsValid(); reader->Next()) {
            const NYT::TNode &row = reader->GetRow();
            const TString &token = row["Token"].AsString();
            try {
                UTF8ToWide(token);
                EnabledTokensHashes.insert(FnvHash<TTokenId>(token.data(), token.size()));
            } catch (yexception &e) {
                //Cerr << e.what() << Endl;
            }
        }
        Cerr << "Loaded " << EnabledTokensHashes.size() << " tokens" << Endl;
    }

public:
    NYT::IClientBasePtr Client;
    THashSet<TTokenId> EnabledTokensHashes;
};

struct TTokenizerService : THttpServer::TUserService {
    TTokenizerService(NYT::IClientBasePtr client)
        : Client(client)
    {
        AddAction("/gettokens", this, &TTokenizerService::MethodGetTokens);
        AddAction("/ping", this, &TTokenizerService::MethodPing);
    }

    void *CreateThreadSpecificResource() override {
        return static_cast<void*>(new TTokenizer(Client));
    }

    void DestroyThreadSpecificResource(void *tsr) override {
        TTokenizer *obj = static_cast<TTokenizer*>(tsr);
        delete obj;
    }

    bool MethodPing(THttpServer::TRequest &request) {
        request.Die(200, "<source>webmaster-cms-detector-tokenizer</source>");
        return true;
    }

    bool MethodGetTokens(THttpServer::TRequest &request) try {
        Cerr << "MethodGetTokens()" << Endl;
        TTokenizer *tokenizer = static_cast<TTokenizer*>(request.ThreadSpecificResource);

        TString host;
        if (!request.GetFilledParameter("host", host)) {
            Cerr << "!request.GetFilledParameter('host', host)" << Endl;
            return true;
        }

        //host = GetMainMirror(host);
        LOG_INFO("requested %s host %s - [%s] ", request.Method.c_str(), host.c_str(), request.GetRemoteAddr().c_str());

        try {
            request.Output() << "HTTP/1.1 200 Ok\r\n\r\n" << tokenizer->GetTokens(host);
            LOG_INFO("request processed");
        } catch(const yexception &e) {
            LOG_ERROR("unable to complete answer: %s", e.what());
            request.Die(500, e.what());
        }

        return true;
    } catch (yexception &e) {
        LOG_ERROR("something went wrong: %s", e.what());
        return true;
    }

public:
    NYT::IClientBasePtr Client;
};

static volatile bool gRunning = true;

static void OnTerminate(int = 0) {
    gRunning = false;
}

int Main() {
    InitNetworkSubSystem();

    // Manage signals
    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT, OnTerminate);
    signal(SIGTERM, OnTerminate);

    NYT::IClientPtr client = NYT::CreateClient("hahn.yt.yandex.net");

    TTokenizerService tokenizerService(client);

    THttpServer httpServer(8371, 2, &tokenizerService);

    // Wait for signals
    do {
        sleep(1);
    } while (gRunning);

    LOG_INFO("Exiting");

    return 0;
}

} //namespace NWebmaster

int main(int argc, const char** argv) {
    using namespace NWebmaster;
    NYT::Initialize(argc, argv);

    int res = 1;

    TArgs::Init(argc, argv);
    TArgs::ParseOpts();

    LOG_INFO("Started");
    try {
        return NWebmaster::Main();
    } catch (std::exception& e) {
        LOG_CRIT("%s", e.what());
    }
    LOG_INFO("Finished");

    //NYT::IClientPtr client = NYT::CreateClient("hahn.yt.yandex.net");
    //TTokenizer tokenizer(client);
    //Cout << tokenizer.GetTokens("https://lenta.ru") << Endl;

    return res;
}
