#include "rtyserver_emulator.h"

#include <saas/library/daemon_base/daemon/base_http_client.h>
#include <saas/library/daemon_base/daemon/daemon.h>
#include <saas/library/daemon_base/module/module.h>
#include <saas/util/queue.h>

#include <library/cpp/json/json_writer.h>
#include <library/cpp/digest/md5/md5.h>
#include <library/cpp/protobuf/json/proto2json.h>
#include <library/cpp/yconf/conf.h>
#include <search/idl/meta.pb.h>


TEmulatorConfig::TEmulatorConfig(const TServerConfigConstructorParams& params)
    : DaemonConfig(*params.Daemon)
{
    TAnyYandexConfig config;
    if(!config.ParseMemory(params.Text.data())) {
        TString err;
        config.PrintErrors(err);
        FAIL_LOG("%s", err.data());
    }

    TYandexConfig::Section* emSection = config.GetFirstChild("Emulator");
    TYandexConfig::TSectionsMap sections = emSection->GetAllChildren();

    TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Search");
    VERIFY_WITH_LOG(iter != sections.end(), "No Search section");
    Search.Init(*iter->second);

    iter = sections.find("Index");
    VERIFY_WITH_LOG(iter != sections.end(), "No Index section");
    Index.Init(*iter->second);

    iter = sections.find("Controller");
    VERIFY_WITH_LOG(iter != sections.end(), "No Controller section");
    Controller.Init(*iter->second);
};

class THttpEmulatorClient : public TBaseHttpClient {
    class TEmulatorSearchRequest : public TEmulatorServer::TRequest {
    public:
        TEmulatorSearchRequest(TEmulatorServer& server, THttpEmulatorClient* owner)
            : TRequest(server)
            , Owner(owner)
        {}

        void DoReply() override {
            Owner->RealReply();
        }

        THolder<THttpEmulatorClient> Owner;
    };

public:
    THttpEmulatorClient(TEmulatorServer& server)
        : Server(server)
    {}

    bool Reply(void* /*ThreadSpecificResource*/) override {
        Output().Flush();
        if (!ProcessHeaders())
            return true;
        RD.Scan();
        if (ProcessSpecialRequest())
            return true;
        Server.Add(new TEmulatorSearchRequest(Server, this));
        return false;
    }

    virtual void RealReply() = 0;

private:
    TEmulatorServer& Server;
};

class TSearchEmulatorClient : public THttpEmulatorClient {
public:
    void RealReply() override {
        ReplyPrototype.MutableBalancingInfo()->SetElapsed((Now() - Created).MicroSeconds());
        Output() << "HTTP/1.1 200 Ok\r\n"
                 << "Content-Type:text/plain\r\n\r\n";
        if (RD.CgiParam.Find("hr") != RD.CgiParam.end() && RD.CgiParam.Has("hr", "da"))
            Output() << ReplyPrototype.Utf8DebugString();
        else if (RD.CgiParam.Has("format", "json") || RD.CgiParam.Has("hr", "json")) {
            NJson::TJsonValue value;
            NProtobufJson::Proto2Json(ReplyPrototype, value);
            NJson::WriteJson(&Output(), &value, true);
        } else
            ReplyPrototype.SerializeToArcadiaStream(&Output());
    }

    TSearchEmulatorClient(const NMetaProtocol::TReport& reply, TEmulatorServer& server)
        : THttpEmulatorClient(server)
        , Created(Now())
        , ReplyPrototype(reply)
    {
        ReplyPrototype.MutableBalancingInfo()->SetWaitInQueue((Now() - Created).MicroSeconds());
    }

private:
    TInstant Created;
    NMetaProtocol::TReport ReplyPrototype;
};

class TSearchNehEmulatorClient : public TNehEmulatorRequest {
public:
    TSearchNehEmulatorClient(TEmulatorServer& server, NNeh::IRequestRef req, const NMetaProtocol::TReport& report)
        : TNehEmulatorRequest(server, req)
    {
        report.SerializeToArcadiaStream(&DataSaver);
    }
};

struct TSearchNehEmulatorClientAdder : public IObjectInQueue {
    TSearchNehEmulatorClientAdder(TEmulatorServer& server, NNeh::IRequestRef req, const NMetaProtocol::TReport& report)
        : Request(MakeIntrusive<TSearchNehEmulatorClient>(server, req, report))
        , Server(server)
    {}

    void Process(void*) override {
        THolder<TSearchNehEmulatorClientAdder> suicide(this);
        Server.Add(Request);
    }
private:
    TEmulatorServer::TRequest::TPtr Request;
    TEmulatorServer& Server;
};

TSearchEmulatorServer::TSearchEmulatorServer(const TEmulatorServerConfig& config)
    : TEmulatorServer(config)
    , HttpServer(this, config.Server)
    , NehServer(this)
    , ReplyPrototype(GenerateReport())
{}

bool TSearchEmulatorServer::Start() {
    TEmulatorServer::Start();
    NehServer.Start();
    return HttpServer.Start();
}

void TSearchEmulatorServer::Stop() {
    HttpServer.Stop();
    NehServer.Stop();
    TEmulatorServer::Stop();
}

TClientRequest* TSearchEmulatorServer::CreateClient() {
    return new TSearchEmulatorClient(ReplyPrototype, *this);
}

TSearchEmulatorServer::TNehServer::TNehServer(TSearchEmulatorServer* owner)
    : TSearchNehServer(TOptions(owner->GetNehOptions(), TString("tcp2")))
    , Owner(owner)
{}

TAutoPtr<IObjectInQueue> TSearchEmulatorServer::TNehServer::DoCreateClientRequest(ui64, NNeh::IRequestRef req) {
    return new TSearchNehEmulatorClientAdder(*Owner, req, Owner->ReplyPrototype);
}

THttpServerOptions TSearchEmulatorServer::GetNehOptions() const {
    THttpServerOptions Options;
    Options = Config.Server;
    Options.Port++;
    return Options;
}

NMetaProtocol::TReport TSearchEmulatorServer::GenerateReport() const {
    NMetaProtocol::TReport report;

    report.AddGrouping();
    report.MutableGrouping(0)->AddGroup();

    report.MutableHead()->SetVersion(1);
    report.MutableHead()->SetSegmentId("host_name");
    report.MutableHead()->SetIndexGeneration(0);
    report.MutableDebugInfo()->SetBaseSearchCount(1);
    report.MutableDebugInfo()->SetBaseSearchNotRespondCount(0);
    report.MutableDebugInfo()->SetAnswerIsComplete(true);
    report.MutableErrorInfo()->SetGotError(NMetaProtocol::TErrorInfo::NO);

    const ui32 docCount = 10;
    for (ui32 i = 0; i < docCount; ++i) {
        auto* doc = report.MutableGrouping(0)->MutableGroup(0)->AddDocument();
        unsigned relevance = (docCount - i) * 100 / docCount;
        report.MutableGrouping(0)->MutableGroup(0)->SetRelevance(relevance);
        TString title = Sprintf("Document #%u", i);
        doc->MutableArchiveInfo()->SetTitle(title);
        doc->SetDocId(MD5::Calc(title));
        doc->SetRelevance(relevance);
    }

    report.AddTotalDocCount(docCount);
    report.AddTotalDocCount(docCount);
    report.AddTotalDocCount(docCount);

    return report;
}


class TControllerEmulatorClient: public THttpEmulatorClient {
public:
    TControllerEmulatorClient(const TEmulatorConfig& config, TEmulatorServer& server)
        : THttpEmulatorClient(server)
        , Config(config)
    {}

    void RealReply() override {
        const TCgiParameters& cgi = RD.CgiParam;
        TCgiParameters::const_iterator command = cgi.Find("command");
        if (command == cgi.end())
            Output() << HttpBadRequest();
        else if (command->second == "get_config")
            Output() << HttpConfigForMtester();
        else
            Output() << HttpUnsupported();
    }
private:
    TString HttpConfigForMtester() {
        return
            "HTTP/1.1 200 Ok\r\n"
            "Content-Type:text/json\r\n"
            "Connection: close\r\n\r\n"
            "{"
            "\"command\": \"get_config\","
            "\"result\": {"
            "\"DoStoreArchive\": \"1\","
            "\"IsPrefixedIndex\": \"0\","
            "\"Searcher.QueryLanguage.body_text\": \"ZONE\","
            "\"Searcher.HttpOptions.Port\": \"" + ToString(Config.Search.Server.Port) + "\","
            "\"Indexer.Common.HttpOptions.Port\": \"" + ToString(Config.Index.Server.Port) + "\""
            "}}";
    }
    TString HttpBadRequest() {
        return
            "HTTP/1.1 400 Bad Request\r\n"
            "Connection: close\r\n\r\n";
    }
    TString HttpUnsupported() {
        return
            "HTTP/1.1 501 Method Not Implemented\r\n"
            "Connection: close\r\n\r\n";
    }
    const TEmulatorConfig& Config;
};

TControllerEmulatorServer::TControllerEmulatorServer(const TEmulatorConfig& config)
    : TEmulatorServer(config.Controller)
    , HttpServer(this, config.Controller.Server)
    , ConfigAll(config)
{}

bool TControllerEmulatorServer::Start() {
    TEmulatorServer::Start();
    return HttpServer.Start();
}

void TControllerEmulatorServer::Stop() {
    HttpServer.Stop();
    TEmulatorServer::Stop();
}

TClientRequest* TControllerEmulatorServer::CreateClient() {
    return new TControllerEmulatorClient(ConfigAll, *this);
}

TEmulator::TEmulator(const TConfig& config)
    : Config(config)
    , IndexServer(config.Index)
    , SearchServer(config.Search)
    , ControllerServer(config)
{
    IsActive = true;
    IndexServer.Start();
}

void TEmulator::Run() {
    VERIFY_WITH_LOG(IndexServer.Start(), "cannot start index server");
    VERIFY_WITH_LOG(SearchServer.Start(), "cannot start search server");
 //   if (!ControllerServer.Start())
   //     ERROR_LOG << "cannot start controller server, error: " << ControllerServer.GetError();
}

void TEmulator::Stop(ui32 /*rigidStopLevel*/, const TCgiParameters* /*cgiParams*/) {
    SearchServer.Stop();
    IndexServer.Stop();
 //   ControllerServer.Stop();
    IsActive = false;
}

