#pragma once

#include <util/generic/hash_set.h>
#include <util/generic/string.h>
#include <util/string/builder.h>

#include <library/cpp/uri/http_url.h>
#include <library/cpp/string_utils/scan/scan.h>
#include <library/cpp/string_utils/url/url.h>
#include <library/cpp/uri/uri.h>

#include <contrib/libs/libidn/lib/idna.h>

namespace NWebmaster {
namespace NUtils {

inline bool ParseUrl(THttpURL &url, const TStringBuf &str) {
    bool hasScheme = GetHttpPrefixSize(str) != 0;
    THttpURL::TParsedState parsedState = url.Parse(hasScheme ? str : TStringBuf(TString::Join("http://", str)), THttpURL::FeaturesRobot);
    return THttpURL::ParsedOK == parsedState;
}

inline bool SplitUrl(const TString &url, TString &host, TString &path, TString &query) {
    THttpURL parsedUrl;

    if (!ParseUrl(parsedUrl, url)) {
        return false;
    }

    host = parsedUrl.PrintS(THttpURL::FlagHostPort | THttpURL::FlagScheme);
    path.clear();

    const TStringBuf &pfld = parsedUrl.GetField(THttpURL::FieldPath);
    query = parsedUrl.GetField(THttpURL::FieldQuery);

    if (pfld.empty() || '/' != pfld[0]) {
        path = "/";
    }

    path += pfld;
    return true;
}

inline bool SplitUrl(const TString &url, TString &host, TString &path) {
    TString query;
    if (SplitUrl(url, host, path, query)) {
        if (!query.empty()) {
            path = path + "?" + query;
        }
        return true;
    }
    return false;
}

inline bool IDNHostToAscii(const TString &host, TString &asciiHost) {
    THttpURL parsedUrl;
    size_t schemePrefix = GetHttpPrefixSize(host);
    THttpURL::TParsedState parsedState = parsedUrl.Parse(schemePrefix > 0 ? host : TStringBuf(TString::Join("http://", host)),
        NUri::TParseFlags(NUri::TFeature::FeaturesAll | NUri::TFeature::FeatureAllowHostIDN, NUri::TFeature::FeatureDecodeExtendedASCII)
    );

    if (THttpURL::ParsedOK != parsedState) {
        return false;
    }

    TString tmp = TString{parsedUrl.GetField(NUri::TField::FieldHostAscii)};
    if (tmp.empty()) {
        tmp = TString{parsedUrl.GetField(NUri::TField::FieldHost)};
    }

    tmp = host.substr(0, schemePrefix) + tmp;

    asciiHost.swap(tmp);
    return true;
}

inline bool IDNToUtf8(const TString &host, TString &utf8Host) {
    THttpURL parsedUrl;
    if (!ParseUrl(parsedUrl, host)) {
        return false;
    }

    const TString hostOnly = TString{parsedUrl.GetField(NUri::TField::FieldHost)};

    char *udata = nullptr;
    if (IDNA_SUCCESS == idna_to_unicode_8z8z(hostOnly.data(), &udata, 0)) {
        TMallocPtr<char> buf(udata);
        parsedUrl.Set(NUri::TField::FieldHost, buf.Get());
        utf8Host = parsedUrl.PrintS(THttpURL::FlagHostPort | THttpURL::FlagScheme);
        return true;
    }

    return false;
}

struct TCgiParamScanFilter {
    TCgiParamScanFilter(const THashSet<TString> &filterParams, TString &filteredQuery, bool keep = false)
        : FilterParams(filterParams)
        , FilteredQuery(filteredQuery)
        , Keep(keep)
    {
    }

    void operator()(const TStringBuf &key, const TStringBuf &value) {
        const bool condition = FilterParams.contains(key);
        if (Keep == condition) {
            if (!FilteredQuery.empty()) {
                FilteredQuery.append("&");
            }
            FilteredQuery.append(key);
            FilteredQuery.append("=");
            FilteredQuery.append(value);
        }
    }

public:
    const THashSet<TString> &FilterParams;
    TString &FilteredQuery;
    bool Keep;
};

inline TString FilterCgiParams(const THashSet<TString> &filterParams, const TString &url, bool keep = false) {
    NUri::TUri parsedUrl;
    if (parsedUrl.Parse(url, NUri::TFeature::FeaturesRecommended) != NUri::TState::ParsedOK) {
        return url;
    }

    const TStringBuf query = parsedUrl.GetField(NUri::TField::FieldQuery);
    if (query.empty()) {
        return url;
    }

    TString filteredQuery;
    ScanKeyValue<true, '&', '='>(query, TCgiParamScanFilter(filterParams, filteredQuery, keep));

    if (filteredQuery.empty()) {
        parsedUrl.FldClr(NUri::TField::FieldQuery);
    } else {
        parsedUrl.FldMemSet(NUri::TField::FieldQuery, filteredQuery);
    }

    return parsedUrl.PrintS(NUri::TField::FlagAllFields);
}

inline bool IsSubdomain(const TString &subdomain, const TString &domain) {
    size_t pos = subdomain.rfind(domain);

    if (pos == TString::npos) {
        return false;
    }

    if ((pos + domain.size()) != subdomain.size()) {
        return false;
    }

    if (pos == 0) {
        return true;
    }

    char symb = subdomain[pos - 1];

    if (symb == '/' || symb == '.') {
        return true;
    }

    return false;
}

inline TStringBuf FixDomainPrefix(TStringBuf host) {
    if (host.empty()) {
        return host;
    }

    size_t domainLevel = 1;
    for (size_t i = 0; i < host.size() - 1; i++) { //ignore last dot, if exists
        if (host[i] == '.') {
            domainLevel++;
        }
    }

    if (domainLevel < 3) {
        return host;
    }

    for (size_t step = 0; step < (domainLevel - 2); step++) {
        if (host.find("m.") == 0) {
            host = host.SubStr(2);
        } else if (host.find("www.") == 0) {
            host = host.SubStr(4);
        }
    }

    return host;
}

inline TStringBuf RemoveScheme(TStringBuf host) {
    size_t del = host.find("://");
    if (del != TString::npos) {
        host = host.SubStr(del + 3);
    }

    //newHost = OwnerCanonizer.GetHostOwner(newHost);
    return host;
}

inline TStringBuf RemoveWWW(TStringBuf host) {
    if (host.empty()) {
        return host;
    }

    size_t domainLevel = 1;
    for (size_t i = 0; i < host.size() - 1; i++) { //ignore last dot, if exists
        if (host[i] == '.') {
            domainLevel++;
        }
    }

    if (domainLevel < 3) {
        return host;
    }

    for (size_t step = 0; step < (domainLevel - 2); step++) {
        if (host.find("www.") == 0) {
            host = host.SubStr(4);
        }
    }

    return host;
}

inline bool GetHostWithoutScheme(const TString &url, TString &host) {
    THttpURL parsedUrl;
    if (!ParseUrl(parsedUrl, url)) {
        return false;
    }

    TString tmp = parsedUrl.PrintS(THttpURL::FlagHostPort);
    tmp.to_lower();
    tmp.swap(host);
    return true;
}

inline TString GetHostWithoutScheme(const TString &url) {
    TString res = url;
    NUtils::GetHostWithoutScheme(res, res);
    return res;
}

inline bool GetSchemedHost(const TString &url, TString &host) {
    THttpURL parsedUrl;
    if (!ParseUrl(parsedUrl, url)) {
        return false;
    }

    host = parsedUrl.PrintS(THttpURL::FlagHostPort | THttpURL::FlagScheme);
    return true;
}

inline TString GetSchemedHost(const TString &url) {
    TString res = url;
    NUtils::GetSchemedHost(res, res);
    return res;
}

inline bool GetOnlyHost(const TString &url, TString &host) {
    THttpURL parsedUrl;
    if (!ParseUrl(parsedUrl, url)) {
        return false;
    }

    host = parsedUrl.PrintS(THttpURL::FlagHost);
    return true;
}

inline TStringBuf GetHost2vecDomain(const TStringBuf &host) {
    return FixDomainPrefix(RemoveScheme(host));
}

inline TString GetDomainWithoutPrefix(const TStringBuf &url) {
    THttpURL parsedUrl;
    if (!ParseUrl(parsedUrl, url)) {
        return "";
    }
    TString host(FixDomainPrefix(parsedUrl.PrintS(THttpURL::FlagHost)));
    return host;
}

inline size_t GetDomainLevel(const TStringBuf& domain) {
    size_t counter = 1;
    for (const char* c = domain.data(); (size_t)(c - domain.data()) < domain.size(); ++c) {
        if (*c == '.')
            ++counter;
    }
    return counter;
}

} //namespace NUtils
} //namespace NWebmaster
