#include "misspellcorrector.h"

#include <saas/searchproxy/configs/searchserverconfig.h>
#include <saas/searchproxy/logging/misspell_log.h>
#include <saas/util/network/address.h>

#include <dict/dictutil/xml.h>
#include <dict/web/xml_reader.h>

#include <library/cpp/dns/cache.h>
#include <library/cpp/http/client/client.h>
#include <library/cpp/logger/global/global.h>

#include <library/cpp/string_utils/quote/quote.h>

class TErratumMisspellCorrector: public IMisspellCorrector {
private:
    const TMisspellCorrectorConfig Config;
    TString RequestPrefix;

private:
    TString GetRawResponse(const TString& request, const TString& opts) const {
        NHttp::TFetchOptions options;
        options.RetryCount = 0;
        options.ConnectTimeout = Config.Connection.ConnectionTimeout;
        options.Timeout = Config.Connection.InteractionTimeout;
        const auto url = TString::Join("http://", Config.Connection.Host, ':', ToString(Config.Connection.Port), RequestPrefix, request, '&', opts);
        const NHttp::TFetchQuery query(url, options);
        const auto result = NHttp::Fetch(query);
        if (result->Code >= 200 && result->Code < 400) {
            return result->Data;
        }
        ERROR_LOG << "Cannot send request to " << url << ". Code = " << result->Code << ", response: " << TStringBuf{result->Data}.substr(0, 1000) << Endl;
        return {};
    }
public:
    TErratumMisspellCorrector(const TMisspellCorrectorConfig& config)
        : Config(config)
    {
        RequestPrefix = TString::Join(
            TStringBuf("/misspell/check?"),
            (config.EnablePorno ? TStringBuf("&options=65") : TStringBuf{}),
            TStringBuf("&srv="), config.Service,
            TStringBuf("&text=")
        );
    }

    TCorrectionResult CorrectRequest(const TString& reqid, const TString& text) const override
    {
        const auto escapedText = CGIEscapeRet(text);

        try {
            const TString& usedResponse = GetRawResponse(escapedText, Config.CgiParametersStr);
            TString reportedResponse = (Config.RawResposeOptions == TMisspellCorrectorConfig::DefaultRawResposeOptions) ? usedResponse : GetRawResponse(escapedText, Config.RawResposeOptions);

            TCorrectionResult result(200, TString(), TString(), TString(), 10000, "No", reportedResponse);
            TStringInput strIn(usedResponse);
            TXmlReader reader(&strIn);
            bool startText = false;
            bool startSrcText = false;
            bool isFix = false;
            while (reader.Next()) {
                if (reader.NodeName() == "MisspellResult") {
                    result.Code = FromString(reader.GetAttribute("code"));
                    result.Reliability = FromString(reader.GetAttribute("r"));
                    result.Rule = WideToUTF8(reader.GetAttribute("rule"));
                    if (result.Code != 201)
                        break;
                } else if (reader.NodeName() == "text") {
                    startText = !startText;
                } else if (startText && reader.NodeName() == "#text") {
                    result.Suggest += XmlEncode(CODES_YANDEX, WideToUTF8(reader.NodeValue()));
                    result.Fixed += WideToUTF8(reader.NodeValue());
                } else if (startText && reader.NodeName() == "fix") {
                    if (!isFix)
                        result.Suggest += "<fix>";
                    else
                        result.Suggest += "</fix>";
                    isFix = !isFix;
                } else if (startSrcText && reader.NodeName() == "fix") {
                    if (!isFix)
                        result.SrcText += "<fix>";
                    else
                        result.SrcText += "</fix>";
                    isFix = !isFix;
                } else if (reader.NodeName() == "srcText") {
                    startSrcText = !startSrcText;
                } else if (startSrcText && reader.NodeName() == "#text") {
                    result.SrcText += XmlEncode(CODES_YANDEX, WideToUTF8(reader.NodeValue()));
                }
            }
            return result;
        } catch (...) {
            const TString& message = CurrentExceptionMessage();
            NSearchProxy::NLogging::TMisspellLog::LogError(reqid, message);
            return TCorrectionResult(502, "", text, text, 0, "Error", message);
        }
    }
};

IMisspellCorrectorPtr CreateMisspellCorrector(const TSearchServerConfig& config)
{
    if (config.MisspellCorrectorConfig) {
        return new TErratumMisspellCorrector(*config.MisspellCorrectorConfig);
    }

    return nullptr;
}

