#include "staticserver.h"

#include <saas/library/searchserver/protocol.h>
#include <library/cpp/logger/global/global.h>
#include <saas/util/network/neh_server.h>

#include <library/cpp/http/server/http_ex.h>
#include <library/cpp/http/server/options.h>
#include <library/cpp/http/static/static.h>
#include <library/cpp/testing/unittest/tests_data.h>

#include <util/datetime/base.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <util/generic/yexception.h>
#include <util/stream/output.h>
#include <util/system/datetime.h>
#include <util/system/defaults.h>

using NUtil::TAbstractNehServer;

struct TBasicServer::IImpl: THttpServer, THttpServer::ICallBack {
    unsigned Count;
    IImpl(ui16 port)
        : THttpServer(this, THttpServerOptions(port).SetMaxConnections(3000).
            SetMaxQueueSize(10000).SetThreads(128))
        , Count(0)
    {
    }
};

TBasicServer::TBasicServer(IImpl* impl)
    : Impl(impl)
{
}

TBasicServer::~TBasicServer()
{
    Stop();
}

ui16 TBasicServer::GetPort() const
{
    return Impl->Options().Port;
}

void TBasicServer::Stop() {
    if (!!Impl) {
        Impl->Shutdown();
        Impl->Wait();
        Impl.Reset(nullptr);
    }
}

namespace {
class TLocalPortsManager {
    private:
        TPortManager PM;
    public:
        TLocalPortsManager()
            {}
        ui16 GetPortsRange(ui16 port, ui16 range) {
            return PM.GetPortsRange(port, range);
        }
    };
 }

template <class TImpl>
TImpl* CreateServerImpl()
{
    auto impl = MakeHolder<TImpl>(Singleton<TLocalPortsManager>()->GetPortsRange(23456, 4));
    impl->Start();
    return impl.Release();
}

template <class TClient>
struct TBasicServerImpl: TBasicServer::IImpl {
    TBasicServerImpl(ui16 port)
        : IImpl(port)
    {
    }

    TClientRequest* CreateClient() override
    {
        return new TClient();
    }
};

struct TSilentServerClient: THttpClientRequestEx {
    bool Reply(void*) override
    {
        return true;
    }
};

TSilentServer::TSilentServer()
    : TBasicServer(CreateServerImpl<TBasicServerImpl<TSilentServerClient> >())
{
}

template <class CStatic>
class TStaticNehServer: public TAbstractNehServer {
private:
    class TClient: public IObjectInQueue {
    private:
        NNeh::IRequestRef Request;
        NNeh::TRequestOut Output;
        CStatic* Static;
        TServerRequestData RD;
    public:
        TClient(CStatic* staticServer, NNeh::IRequestRef req)
            : Request(req)
            , Output(Request.Get())
            , Static(staticServer)
        {}

        void Process(void* /*ThreadSpecificResource*/) override {
            THolder<TClient> cleanup(this);
            TStringStream path;

            path << "/"sv << Request->Service();

            RD.Parse(path.Data());
            RD.AppendQueryString(Request->Data());
            RD.Scan();

            Static->PathHandle(RD, Output, false);
        }
    };
private:
    TAbstractNehServer::TOptions DefaultConfig(ui16 port) {
        TAbstractNehServer::TOptions result;
        result.Port = port;
        result.Schemes.push_back(MetaSearchNehProtocol);
        result.nThreads = 1;
        return result;
    }
private:
    CStatic* Static;
public:
    TStaticNehServer(ui16 port, CStatic* staticServer)
        : TAbstractNehServer(DefaultConfig(port))
        , Static(staticServer)
    {
        Start();
    }

    ~TStaticNehServer() {
        Stop();
    }

    TAutoPtr<IObjectInQueue> DoCreateClientRequest(ui64 /*id*/, NNeh::IRequestRef req) final {
        return new TClient(Static, req);
    }
    virtual void OnFailRequest(NNeh::IRequestRef /*req*/) {
    }
};

class TStaticServerImpl: public TBasicServer::IImpl,
    public CHttpServerStatic {
private:
    TDuration Delay;
    bool IsKV = false;
    TMap<TString, TString> Docs;

    TStaticNehServer<TStaticServerImpl> NehServer;

public:
    inline TStaticServerImpl(ui16 port)
        : IImpl(port)
        , NehServer(port + 1, this)
    {
    }

    inline bool AddDoc(const char* path, TString doc, TString mime)
    {
        if (CHttpServerStatic::AddDoc(path, (ui8*)doc.data(), doc.size(), mime.data())) {
            Docs[path] = doc;
            return true;
        } else {
            return false;
        }
    }

    inline void SetDelay(const TDuration& delay)
    {
        Delay = delay;
    }

    inline void SetKV(const bool isKV) {
        IsKV = isKV;
    }

    TClientRequest* CreateClient() override;

    void PathHandle(TServerRequestData& RD, IOutputStream& out, bool http = true)
    {
        Sleep(Delay);
        TString path{RD.ScriptName()};
        TStringBuf query = RD.Query();
        if (!query.empty()) {
            static const TStringBuf ignored[] = {
                TStringBuf("mslevel"),
                TStringBuf("qtree"),
                TStringBuf("ruid"),
                TStringBuf("timeout"),
                TStringBuf("gta"),
                TStringBuf("ag"),
                TStringBuf("reqid"),
                TStringBuf("raId"),
                TStringBuf("msgv"),
                TStringBuf("user_request"),
                TStringBuf("uuid")
            };
            for (auto&& param : ignored) {
                RD.CgiParam.EraseAll(param);
            }
            if (IsKV) {
                static const TStringBuf kvIgnored[] = {
                    TStringBuf("dump"),
                    TStringBuf("sp_meta_search"),
                    TStringBuf("meta_search"),
                    TStringBuf("fake_uuid"),
                    TStringBuf("yandexuid"),
                    TStringBuf("report_format"),
                    TStringBuf("ms"),
                    TStringBuf("normal_kv_report")
                };
                for (auto&& param : kvIgnored) {
                    RD.CgiParam.EraseAll(param);
                }
            }
            path += '?';
            path += RD.CgiParam.Print();
        }
        if (strcmp(path.data(), "/?&info=doccount&ms=i")) {
            if (HasDoc(path.data())) {
                ++Count;
            } else {
                Cerr << "No such document: " << path << Endl;
            }
        }
        Cerr << "Incoming request " << path << " to static server " << (const void*)this << Endl;
        if (http) {
            CHttpServerStatic::PathHandle(path.data(), out);
        } else {
            out << Docs[path];
        }
    }
};

class TStaticServerClient: public THttpClientRequestEx {
private:
    const TDuration Delay;

public:
    TStaticServerClient(const TDuration& delay)
        : Delay(delay)
    {
    }

    bool Reply(void*) override {
        if (ProcessHeaders()) {
            RD.Scan();
            static_cast<TStaticServerImpl*>(HttpServ())->PathHandle(RD, Output());
        }
        return true;
    }
};

TClientRequest* TStaticServerImpl::CreateClient()
{
    return new TStaticServerClient(Delay);
}

TStaticServer::TStaticServer(const TDuration& delay)
    : TBasicServer(CreateServerImpl<TStaticServerImpl>())
{
    Ptr<TStaticServerImpl>()->SetDelay(delay);
}

TStaticServer::TStaticServer(ui16 port, const TDuration& delay)
    : TBasicServer(new TStaticServerImpl(port))
{
    Ptr<TStaticServerImpl>()->SetDelay(delay);
}

bool TStaticServer::AddDoc(const char* path, TString doc, TString mime)
{
    TStringBuf original(path);
    TString script = TString{original.Before('?')};

    if (original.find('?') != TString::npos) {
        TCgiParameters params;
        params.Scan(original.After('?'));
        script += '?';
        script += params.Print();
    }
    Cerr << "Adding " << script << " to static server " << (const void*)this << Endl;
    return Ptr<TStaticServerImpl>()->AddDoc(script.data(), doc, mime);
}

void TStaticServer::SetKV(const bool isKV) {
    Ptr<TStaticServerImpl>()->SetKV(isKV);
}

const TString TStaticServer::DefaultMime = "text/plain";

unsigned TBasicServer::GetCallsCount() const
{
    return Impl->Count;
}
