#include <util/string/vector.h>
#include <util/stream/file.h>

#include <library/cpp/json/json_writer.h>

#include <wmconsole/version3/wmcutil/log.h>

#include "monitor.h"
#include "service.h"
#include <util/string/split.h>

namespace NWebmaster {

TCanonizerService::TCanonizerService(const TConfig &config)
    : Config(config)
{
    KernelOwnerCanonizer.LoadTrueOwners();
    KernelOwnerCanonizer.LoadSerpCategOwners();
    MascotOwnerCanonizer.LoadTrueOwners();

    AddAction("/getCaseHost", this, &TCanonizerService::MethodGeminiCaseHostRequest);
    AddAction("/getMirror", this, &TCanonizerService::MethodGeminiCaseHostRequest);
    AddAction("/getUrlOwner", this, &TCanonizerService::MethodUrlOwnerRequest);
    AddAction("/getHostOwner", this, &TCanonizerService::MethodHostOwnerRequest);
    AddAction("/getMascotUrlOwner", this, &TCanonizerService::MethodMascotUrlOwnerRequest);
    AddAction("/getMascotHostOwner", this, &TCanonizerService::MethodMascotHostOwnerRequest);
    AddAction("/ping", this, &TCanonizerService::MethodPing);
}

void *TCanonizerService::CreateThreadSpecificResource() {
    size_t quota = Config.GEMINI_QUOTA / Config.HTTP_SERVER_THREADS / Config.BACKENDS;
    LOG_INFO("gemini thread quota %lu", quota);
    TCanonizerContext *ctx = new TCanonizerContext(
        new TGeminiCanonizer(Config.GEMINI_USER, quota, Config.GEMINI_TIMEOUT, NGeminiProtos::CASE_HOST)
    );
    return static_cast<void*>(ctx);
}

void TCanonizerService::DestroyThreadSpecificResource(void *tsr) {
    TCanonizerContext *obj = static_cast<TCanonizerContext*>(tsr);
    delete obj;
}

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

void TCanonizerService::PrepareGeminiPlainAnswer(TGeminiCanonizer *canonizer, const TDeque<TString> &urls, TString &output) {
    output.clear();

    TDeque<TResolvedUrl> resolved;
    canonizer->GetCanonical(urls, resolved);

    for (const TResolvedUrl &record : resolved) {
        if (!record.HasError) {
            TMonitor::Instance().SuccessRequests();
        } else if (record.ErrorCode == NGeminiProtos::BAD_URL || record.ErrorCode == NGeminiProtos::URL_NOT_FOUND) {
            TMonitor::Instance().RejectedRequests();
        } else {
            TMonitor::Instance().FailedRequests();
        }

        output.append(record.Original);
        output.append("=");

        if (record.HasError) {
            output.append("ERROR:" + record.ErrorName);
        } else {
            output.append(record.Canonized);
        }

        output.append("\n");
    }
}

void TCanonizerService::PrepareGeminiJsonAnswer(TGeminiCanonizer *canonizer, const TDeque<TString> &urls, TString &output) {
    output.clear();

    TStringOutput jsonStream(output);

    NJson::TJsonWriter writer(&jsonStream, false, false);

    writer.OpenMap();
    writer.Write("results");
    writer.OpenArray();

    TDeque<TResolvedUrl> resolved;
    canonizer->GetCanonical(urls, resolved);

    for (const TResolvedUrl &record : resolved) {
        writer.OpenMap();
        if (record.HasError) {
            writer.Write("error", record.ErrorName);
        }

        if (!record.HasError) {
            TMonitor::Instance().SuccessRequests();
        } else if (record.ErrorCode == NGeminiProtos::BAD_URL || record.ErrorCode == NGeminiProtos::URL_NOT_FOUND) {
            TMonitor::Instance().RejectedRequests();
        } else {
            TMonitor::Instance().FailedRequests();
        }

        TString canonicalFieldName = "canonized";
        TString originalFieldName = "url";
        switch(canonizer->Type) {
        case NGeminiProtos::MIRROR:
        case NGeminiProtos::CASE_HOST:
            canonicalFieldName = "mirror";
            originalFieldName = "host";
            break;
        default:
            break;
        }

        writer.Write(originalFieldName, record.Original);
        writer.Write(canonicalFieldName, record.Canonized);
        writer.CloseMap();
    }

    writer.CloseArray();
    writer.CloseMap();

    writer.Flush();
}

bool TCanonizerService::MethodGeminiRequest(TGeminiCanonizer *canonizer, THttpServer::TRequest &request) {
    LOG_INFO("requested %s?%s - [%s]", request.Method.data(), request.Query.data(), request.GetRemoteAddr().data());

    TCgiParameters::const_iterator needJsonIt = request.Params.Find("json");
    bool needJson = (needJsonIt != request.Params.end() && needJsonIt->second == "true");
    TString content = request.Input().ReadAll();

    TDeque<TString> urls;
    StringSplitter(content).Split(';').AddTo(&urls);

    if (urls.empty()) {
        request.Die(200);
        return true;
    }

    TString data;
    if (needJson) {
        PrepareGeminiJsonAnswer(canonizer, urls, data);
    } else {
        PrepareGeminiPlainAnswer(canonizer, urls, data);
    }

    try {
        request.Output() << "HTTP/1.1 200 Ok\r\n\r\n" << data;
        LOG_INFO("sent %s reply with %zu hosts [%s] in %s%s", NGeminiProtos::ECanonizationType_Name(canonizer->Type).data(), urls.size(), data.data(), request.GetTimerString().data(), needJson ? " (json=true)" : "");
    } catch(const yexception &e) {
        LOG_ERROR("unable to complete answer: %s", e.what());
        request.Die(500, e.what());
    }

    return true;
}

bool TCanonizerService::MethodGeminiCaseHostRequest(THttpServer::TRequest &request) {
    TCanonizerContext *context = static_cast<TCanonizerContext*>(request.ThreadSpecificResource);
    TGeminiCanonizer *canonizer = context->GeminiCaseHostCanonizer.Get();

    return MethodGeminiRequest(canonizer, request);
}

template <class TCanonizer>
void PrepareGeminiOwnerJsonAnswer(const TCanonizer &canonizer, const TVector<TString> &requests, bool needUrl, TString &output) {
    output.clear();

    TStringOutput jsonStream(output);

    NJson::TJsonWriter writer(&jsonStream, false, false);

    writer.OpenMap();
    writer.Write("results");
    writer.OpenArray();

    for (const TString &request : requests) {
        TString mirror;

        writer.OpenMap();
        writer.Write("request", request);

        try {
            TString owner;
            if (needUrl) {
                owner = canonizer.GetUrlOwner(request);
            } else {
                owner = canonizer.GetHostOwner(request);
            }

            writer.Write("owner", owner);
        } catch (yexception &e) {
            writer.Write("error", e.what());
            LOG_WARN("url owner canonizer: %s", e.what());
        }

        writer.CloseMap();
    }

    writer.CloseArray();
    writer.CloseMap();

    writer.Flush();
}

template <class TCanonizer>
bool MethodOwnerRequest(const TCanonizer &canonizer, THttpServer::TRequest &request, bool needUrl) {
    LOG_INFO("requested %s - [%s]", request.Method.data(), request.GetRemoteAddr().data());

    TVector<TString> hosts = SplitString(request.Input().ReadAll(), ";");
    if (hosts.empty()) {
        request.Die(200);
        return true;
    }

    TString data;
    PrepareGeminiOwnerJsonAnswer(canonizer, hosts, needUrl, data);

    try {
        request.Output() << "HTTP/1.1 200 Ok\r\n\r\n" << data;
        LOG_INFO("sent OWNER reply with %zu hosts in %s", hosts.size(), request.GetTimerString().data());
    } catch(const yexception &e) {
        LOG_ERROR("unable to complete answer: %s", e.what());
        request.Die(500, e.what());
    }

    return true;
}

bool TCanonizerService::MethodUrlOwnerRequest(THttpServer::TRequest &request) {
    return MethodOwnerRequest(KernelOwnerCanonizer, request, true);
}

bool TCanonizerService::MethodHostOwnerRequest(THttpServer::TRequest &request) {
    return MethodOwnerRequest(KernelOwnerCanonizer, request, false);
}

bool TCanonizerService::MethodMascotUrlOwnerRequest(THttpServer::TRequest &request) {
    return MethodOwnerRequest(MascotOwnerCanonizer, request, true);
}

bool TCanonizerService::MethodMascotHostOwnerRequest(THttpServer::TRequest &request) {
    return MethodOwnerRequest(MascotOwnerCanonizer, request, false);
}

} //namespace NWebmaster
