#include "request_matcher.h"

#include <balancer/kernel/cookie/cookie.h>
#include <balancer/kernel/http/parser/common_headers.h>
#include <balancer/kernel/http/parser/http.h>

#include <library/cpp/cgiparam/cgiparam.h>
#include <library/cpp/uri/uri.h>

#include <util/string/split.h>


namespace NSrvKernel {
    TProtoMatcher::EProto TProtoMatcher::GetProto(const TConnDescr& descr) noexcept {
        if (descr.Properties) {
            switch (descr.Properties->Parent.Ssl.NextProto) {
            case EClientProto::CP_HTTP:
                return EProto::Http1x;
            case EClientProto::CP_HTTP2:
                return EProto::Http2;
            default:
                return EProto::Unknown;
            }
        } else {
            return EProto::Unknown;
        }
    }

    bool TFsmMatcher::Match(const TConnDescr& descr) const noexcept {
        if (Y_UNLIKELY(!descr.Request)) {
            return false;
        }

        TRequest* const request = descr.Request;
        const TRequestLine& requestLine = request->RequestLine();
        NUri::TUri parsed;

        switch (MatchType_) {
        case EMatchType::RequestLine:
            return requestLine.Match(*this);
        case EMatchType::Path:
            return ::NSrvKernel::Match(*this, request->RequestLine().Path.AsStringBuf());
        case EMatchType::NormalizedPath:
            if (parsed.Parse(request->RequestLine().Path.AsStringBuf()) == NUri::TState::ParsedOK) {
                return ::NSrvKernel::Match(*this, parsed.GetField(NUri::TField::FieldPath));
            }
            return false;
        case EMatchType::Cgi:
            return ::NSrvKernel::Match(*this, request->RequestLine().CGI.AsStringBuf());
        case EMatchType::Url:
            return requestLine.MatchUrl(*this);
        case EMatchType::Host:
            for (const auto& headerVal : request->Headers().GetValuesRef("host")) {
                if (::NSrvKernel::Match(*this, headerVal.AsStringBuf())) {
                    return true;
                }
            }
            return false;
        case EMatchType::Upgrade:
            if (!request->Props().UpgradeRequested) {
                return false;
            }
            [[fallthrough]];
        case EMatchType::Header:
            for (const auto& hdr : request->Headers()) {
                if (::NSrvKernel::Match(*KVMatcherNameFsm_, hdr.first.AsStringBuf())) {
                    for (const auto& hdrValue : hdr.second) {
                        if (::NSrvKernel::Match(*this, hdrValue.AsStringBuf())) {
                            return true;
                        }
                    }
                }
            }
            return false;
        case EMatchType::CgiParam: {
            TStringBuf cgi = request->RequestLine().CGI.AsStringBuf();
            if (!cgi.empty() && cgi.front() == '?') {
                cgi = cgi.substr(1);
            }
            const TCgiParameters cgiParams(cgi);
            for (const auto& param : cgiParams) {
                if (::NSrvKernel::Match(*KVMatcherNameFsm_, param.first)) {
                    if (::NSrvKernel::Match(*this, param.second)) {
                        return true;
                    }
                }
            }
            return false;
        }
        case EMatchType::Cookie: {
            for (auto& headerValue : request->Headers().GetValuesRef("cookie")) {
                // Shortcuts on successful match
                // Behaviour has been changed in BALANCER-1725. Right now delimiter is ";" without space.
                if (ForEachCookieC([&](TStringBuf cookie) noexcept {
                    return TMatcher(*this).Match(cookie).Final() ? ECookieIter::Stop : ECookieIter::Continue;
                }, headerValue.AsStringBuf())) {
                    return true;
                }
            }
            return false;
        }
        // should not happen
        default:
            return false;
        }
    }

    template<>
    THolder<IMatcher<TConnDescr>> ConstructSpecMatcher(const TString& key, NConfig::IConfig* config, ICtl* ctl) {
        if (key == "match_source_ip") {
            return MakeHolder<TRequestIpMatcher>(config);
        }

        if (key == "match_proto") {
            return MakeHolder<TProtoMatcher>(config);
        }

        if (key == "match_method") {
            return MakeHolder<TMethodMatcher>(config);
        }

        if (key == "match_exp_id") {
            return MakeHolder<TExpBoxesMatcher>(config);
        }

        if (key == "match_uaas_device") {
            return MakeHolder<TExpBoxesMatcher>(config);
        }

        if (key == "match_fsm") {
            return MakeHolder<TFsmMatcher>(config);
        }

        if (key == "match_if_file_exists") {
            return MakeHolder<TLocalFileMatcher>(config, ctl);
        }

        return nullptr;
    }
}
