#pragma once

#include <library/cpp/neh/neh.h>
#include <library/cpp/neh/location.h>

#include <util/generic/ptr.h>
#include <util/generic/map.h>
#include <util/generic/vector.h>

#include <util/memory/tempbuf.h>
#include <util/stream/str.h>
#include <library/cpp/string_utils/quote/quote.h>
#include <util/string/vector.h>

#include <stdexcept>

namespace NWebmaster {

//do not own any data
template<typename TStringType>
struct TPostRequest {
    inline TPostRequest(const TStringType& url)
        : Url(&url)
        , Data(nullptr)
        , Headers(nullptr)
        , Tout(1000)
    {
    }

    inline TPostRequest& SetData(const TStringType& data) noexcept {
        Data = &data;

        return *this;
    }

    inline TPostRequest& SetContentType(const TStringType& contentType) noexcept {
        ContentType = contentType;

        return *this;
    }

    inline TPostRequest& SetTimeout(long timeout) noexcept {
        Tout = timeout;

        return *this;
    }

    inline TPostRequest& SetHeaders(const std::vector<TStringType>& headers) noexcept {
        Headers = &headers;

        return *this;
    }

    TAtomicSharedPtr<TStringType> Perform();

    const TStringType *Url;
    const TStringType *Data;
    TStringType ContentType;
    const std::vector<TStringType>* Headers;
    long Tout;
};

template<typename TStringType>
TAtomicSharedPtr<TStringType> TPostRequest<TStringType>::Perform() {
    NNeh::TParsedLocation loc(TStringBuf(Url->c_str(), Url->length()));

    TStringStream ss;

    ss << "POST /"sv << loc.Service << " HTTP/1.1\r\nHost: "sv << loc.EndPoint << "\r\n"sv;

    if (Data) {
        ss << "Content-Length: "sv << Data->size() << "\r\n"sv;
    }

    if (Headers) {
        for (const auto& h: *Headers) {
            ss << h << "\r\n"sv;
        }
    }

    if (!ContentType.empty()) {
        ss << "Content-Type: "sv << ContentType << "\r\n"sv;
    }

    ss << "\r\n"sv;

    if (Data) {
        ss << *Data;
    }

    #if defined(_freebsd_)
    NNeh::TResponseRef ret = NNeh::Request(NNeh::TMessage(TStringType("full2://") + loc.EndPoint + "/" + loc.Service, ss.Str()))->Wait(TDuration::MilliSeconds(Tout));
    #else
    NNeh::TResponseRef ret = NNeh::Request(NNeh::TMessage(TStringType("full://") + loc.EndPoint + "/" + loc.Service, ss.Str()))->Wait(TDuration::MilliSeconds(Tout));
    #endif

    if (!ret) {
        ythrow yexception() << "timeout"sv;
    }

    if (ret->IsError()) {
        ythrow yexception() << ret->GetErrorText();
    }

    return TAtomicSharedPtr<TStringType>(new TStringType(ret->Data.data(), ret->Data.size()));

    //ythrow yexception() << CurrentExceptionMessage();
}

template <typename TStringType>
TStringType HttpGet(const TStringType& _url, size_t timeout) {
    TStringType url(_url);

    #if defined(_freebsd_)
    if (url.find("http://") == 0) {
        url = "http2://" + url.substr(7);
    }
    #endif

    NNeh::TResponseRef r = NNeh::Request(url)->Wait(TDuration::MilliSeconds(timeout));

    if (r && !r->IsError()) {
        return TStringType(r->Data.data(), r->Data.size());
    }

    ythrow yexception() << "unable to process HttpGet request";
}

template <typename TStringType>
TStringType HttpPost(const TStringType& url, const TStringType& data, const TStringType &contentType = "", size_t timeout = 1000) {
    TPostRequest<TStringType> post(url);
    post.SetTimeout(timeout);
    post.SetContentType(contentType);
    post.SetData(data);
    TAtomicSharedPtr<TStringType> res = post.Perform();
    return *res;
}

template <typename TStringType>
struct TMultiHttpHandler;

template <>
struct TMultiHttpHandler<TString> {
    TMultiHttpHandler() {
    }

    TMultiHttpHandler(const TString &urls) {
        Handlers = SplitString(urls, ",");
    }

public:
    TVector<TString> Handlers;
    TMap<TString, TString> Params;
};

template <typename TStringType>
TStringType HttpGet(const TMultiHttpHandler<TStringType>& handler, size_t timeout);

template <typename TStringType>
TStringType HttpPost(const TMultiHttpHandler<TStringType>& handler, const TStringType& data, const TStringType &contentType, size_t timeout);

void Quote(TString& url, const char* safe = "");

} //namespace NWebmaster
