#include "template_corrector.h"

#include <saas/searchproxy/common/cgi.h>
#include <saas/searchproxy/configs/searchserverconfig.h>

#include <library/cpp/charset/wide.h>
#include <library/cpp/object_factory/object_factory.h>
#include <library/cpp/logger/global/global.h>
#include <library/cpp/charset/ci_string.h>

#include <util/charset/utf8.h>

typedef NObjectFactory::TObjectFactory<ITemplateCorrector, TCiString> TTemplateCorrectorFactory;

class TMetaServiceTemplateCorrector: public ITemplateCorrector {
    TString FillTemplate(TReqEnv& /*reqEnv*/, const TString& text) override {
        return text;
    }
};

ITemplateCorrectorPtr CreateTemplateCorrector(const TSearchServerConfig& config) {
    if (config.MetaService) {
        return new TMetaServiceTemplateCorrector;
    }

    ITemplateCorrectorPtr result = TTemplateCorrectorFactory::Construct(config.TemplateCorrectionPolicy);
    if (!result) {
        TSet<TCiString> keys;
        Singleton<TTemplateCorrectorFactory>()->GetKeys(keys);
        TCiString msg = "unknown TemplateCorreectionPolicy: " + config.TemplateCorrectionPolicy + ", must be in (";
        for (TSet<TCiString>::const_iterator i = keys.begin(); i != keys.end(); ++i)
            msg.append(*i).append(", ");
        msg.append(")");
        FAIL_LOG("%s", msg.data());
    }
    return result;
}

class TTemplateCorrectorNo : public ITemplateCorrector {
public:
    TString FillTemplate(TReqEnv& reqEnv, const TString& text) override {
        return Fill(GetTemplate(reqEnv), DecodeString(text));
    }
    static TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorNo> Registrator;

protected:
    inline static TUtf16String DecodeString(const TString& str) {
        return CharToWide(str, NotUTF8 == UTF8Detect(str) ? CODES_YANDEX : CODES_UTF8);
    }

    inline static const TUtf16String GetTemplate(TReqEnv& reqEnv) {
        return DecodeString(reqEnv.Get(NSearchProxyCgi::tmplate, 0));
    }

    TString Fill(const TUtf16String& templ, const TUtf16String& text) {
        if (!templ)
            return WideToUTF8(text);
        TUtf16String result(templ);
        static const TUtf16String placeholder(u"%request%");
        for (size_t pos = result.find(placeholder); pos != TUtf16String::npos; pos = result.find(placeholder, pos + text.length())) {
                result.replace(pos, placeholder.length(), text);
        }
        return WideToUTF8(result);
    }
};

TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorNo> TTemplateCorrectorNo::Registrator("no");

class TTemplateCorrectorCheck : public TTemplateCorrectorNo {
protected:
    class TDangerCharesters : public TUtf16String {
    public:
        TDangerCharesters() {
            // !
            append(0x0021);
            // "
            append(0x0022).append(0x00AB).append(0x00BB).append(0x201C).append(0x201D).append(0x201E).append(0x201F).append(0x2033).append(0x2034).append(0x2036).append(0x2037).append(0x2039).append(0x203A).append(0x2057).append(0x276E).append(0x276F).append(0x300C).append(0x300D).append(0x300E).append(0x300F).append(0x301D).append(0x301E).append(0x301F).append(0xFE41).append(0xFE42).append(0xFE43).append(0xFE44).append(0xFF02).append(0xFF62).append(0xFF63);
            // &
            append(0x0026);
            // |
            append(0x007C);
            // (
            append(0x0028).append(0x0F3A).append(0x0F3C).append(0x169B).append(0x2045).append(0x207D).append(0x208D).append(0x2329).append(0x23B4).append(0x2768).append(0x0028).append(0x0028).append(0x0028).append(0x0028).append(0x0028).append(0x276A).append(0x276C).append(0x2770).append(0x2772).append(0x2774).append(0x27E6).append(0x27E8).append(0x27EA).append(0x2983).append(0x2985).append(0x2987).append(0x2989).append(0x298B).append(0x298D).append(0x298F).append(0x2991).append(0x2993).append(0x2995).append(0x2997).append(0x29D8).append(0x29DA).append(0x29FC).append(0x3008).append(0x300A).append(0x3010).append(0x3014).append(0x3016).append(0x3018).append(0x301A).append(0xFD3E).append(0xFE35).append(0xFE37).append(0xFE39).append(0xFE3B).append(0xFE3D).append(0xFE3F).append(0xFE47).append(0xFE59).append(0xFE5B).append(0xFE5D).append(0xFF08).append(0xFF3B).append(0xFF5B).append(0xFF5F);
            // )
            append(0x0029).append(0x0F3B).append(0x0F3D).append(0x169C).append(0x2046).append(0x207E).append(0x208E).append(0x232A).append(0x23B5).append(0x2769).append(0x0029).append(0x0029).append(0x0029).append(0x0029).append(0x0029).append(0x276B).append(0x276D).append(0x2771).append(0x2773).append(0x2775).append(0x27E7).append(0x27E9).append(0x27EB).append(0x2984).append(0x2986).append(0x2988).append(0x298A).append(0x298C).append(0x298E).append(0x2990).append(0x2992).append(0x2994).append(0x2996).append(0x2998).append(0x29D9).append(0x29DB).append(0x29FD).append(0x3009).append(0x300B).append(0x3011).append(0x3015).append(0x3017).append(0x3019).append(0x301B).append(0xFD3F).append(0xFE36).append(0xFE38).append(0xFE3A).append(0xFE3C).append(0xFE3E).append(0xFE40).append(0xFE48).append(0xFE5A).append(0xFE5C).append(0xFE5E).append(0xFF09).append(0xFF3C).append(0xFF5C).append(0xFF60);
            // <
            append(0x003C);
            // <
            append(0x003E);
        }
    };
    static const TDangerCharesters DangerCharesters;

    inline static bool AllRight(const TUtf16String& text, const TUtf16String& templ) {
        return !text || !templ || text.find_first_of(DangerCharesters) == text.npos || templ.find_first_of(DangerCharesters) == templ.npos;
    }
};

const TTemplateCorrectorCheck::TDangerCharesters TTemplateCorrectorCheck::DangerCharesters;

class TTemplateCorrectorCheckOnly : public TTemplateCorrectorCheck {
public:
    TString FillTemplate(TReqEnv& reqEnv, const TString& text) override {
        const TUtf16String templ(GetTemplate(reqEnv));
        const TUtf16String wtext(DecodeString(text));
        if (!AllRight(wtext, templ)) {
            constexpr TStringBuf output("HTTP/1.1 400 Bad Request\r\n\r\n"sv);
            reqEnv.PrintS(output.data(), output.size());
            throw yexception() << "syntax symbols in template and in text";
        }
        return Fill(templ, wtext);
    }
    static TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckOnly> Registrator;
};

TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckOnly> TTemplateCorrectorCheckOnly::Registrator("check_only");

class TTemplateCorrectorCheckAndFix : public TTemplateCorrectorCheck {
public:
    TString FillTemplate(TReqEnv& reqEnv, const TString& text) override {
        const TUtf16String templ(GetTemplate(reqEnv));
        TUtf16String wtext(DecodeString(text));
        if (AllRight(wtext, templ))
            return Fill(templ, wtext);
        for (size_t pos = wtext.find_first_of(DangerCharesters); pos != wtext.npos; pos = wtext.find_first_of(DangerCharesters))
            wtext.erase(pos, 1);
        return Fill(templ, wtext);
    }
    static TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckAndFix> Registrator;
};

TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckAndFix> TTemplateCorrectorCheckAndFix::Registrator("check_and_fix");

class TTemplateCorrectorCheckAndFixSpaces : public TTemplateCorrectorCheck {
public:
    TString FillTemplate(TReqEnv& reqEnv, const TString& text) override {
        const TUtf16String templ(GetTemplate(reqEnv));
        TUtf16String wtext(DecodeString(text));
        if (AllRight(wtext, templ))
            return Fill(templ, wtext);
        for (size_t pos = wtext.find_first_of(DangerCharesters); pos != wtext.npos; pos = wtext.find_first_of(DangerCharesters))
            wtext[pos] = ' ';
        return Fill(templ, wtext);
    }
    static TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckAndFixSpaces> Registrator;
};

TTemplateCorrectorFactory::TRegistrator<TTemplateCorrectorCheckAndFixSpaces> TTemplateCorrectorCheckAndFixSpaces::Registrator("check_and_fix_spaces");
