// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include <gtest/gtest.h>
#include <gflags/gflags.h>
#include "brpc/policy/sofa_pbrpc_protocol.h"
#include "brpc/channel.h"
#include "brpc/server.h"
#include "brpc/interceptor.h"
#include "brpc/nshead_service.h"
#include "echo.pb.h"

namespace brpc {
namespace policy {
DECLARE_bool(use_http_error_code);
}
}

int main(int argc, char* argv[]) {
    ::testing::InitGoogleTest(&argc, argv);
    GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
    return RUN_ALL_TESTS();
}

const int EREJECT = 4000;
int g_index = 0;
const int port = 8613;
const std::string EXP_REQUEST = "hello";
const std::string EXP_RESPONSE = "world";
const std::string NSHEAD_EXP_RESPONSE = "error";

class EchoServiceImpl : public ::test::EchoService {
public:
    EchoServiceImpl() = default;
    ~EchoServiceImpl() override = default;
    void Echo(google::protobuf::RpcController* cntl_base,
              const ::test::EchoRequest* request,
              ::test::EchoResponse* response,
              google::protobuf::Closure* done) override {
        brpc::ClosureGuard done_guard(done);
        ASSERT_EQ(EXP_REQUEST, request->message());
        response->set_message(EXP_RESPONSE);
    }
};

// Adapt your own nshead-based protocol to use brpc
class MyNsheadProtocol : public brpc::NsheadService {
public:
    void ProcessNsheadRequest(const brpc::Server&,
                              brpc::Controller* cntl,
                              const brpc::NsheadMessage& request,
                              brpc::NsheadMessage* response,
                              brpc::NsheadClosure* done) {
        // This object helps you to call done->Run() in RAII style. If you need
        // to process the request asynchronously, pass done_guard.release().
        brpc::ClosureGuard done_guard(done);

        response->head = request.head;
        if (cntl->Failed()) {
            ASSERT_TRUE(cntl->Failed());
            ASSERT_EQ(EREJECT, cntl->ErrorCode());
            response->body.append(NSHEAD_EXP_RESPONSE);
            return;
        }
        response->body.append(EXP_RESPONSE);
    }
};

class MyInterceptor : public brpc::Interceptor {
public:
    MyInterceptor() = default;

    ~MyInterceptor() override = default;

    bool Accept(const brpc::Controller* controller,
                int& error_code,
                std::string& error_txt) const override {
        if (g_index % 2 == 0) {
            error_code = EREJECT;
            error_txt = "reject g_index=0";
            return false;
        }

        return true;
    }
};

class InterceptorTest : public ::testing::Test {
public:
    InterceptorTest() {
        EXPECT_EQ(0, _server.AddService(&_echo_svc,
                                        brpc::SERVER_DOESNT_OWN_SERVICE));
        brpc::ServerOptions options;
        options.interceptor = new MyInterceptor;
        options.nshead_service = new MyNsheadProtocol;
        options.server_owns_interceptor = true;
        EXPECT_EQ(0, _server.Start(port, &options));
    }

    ~InterceptorTest() override = default;

    static void CallMethod(test::EchoService_Stub& stub,
                           ::test::EchoRequest& req,
                           ::test::EchoResponse& res) {
        for (g_index = 0; g_index < 1000; ++g_index) {
            brpc::Controller cntl;
            stub.Echo(&cntl, &req, &res, NULL);
            if (g_index % 2 == 0) {
                ASSERT_TRUE(cntl.Failed());
                ASSERT_EQ(EREJECT, cntl.ErrorCode());
            } else {
                ASSERT_FALSE(cntl.Failed());
                EXPECT_EQ(EXP_RESPONSE, res.message()) << cntl.ErrorText();
            }
        }
    }

private:
    brpc::Server _server;
    EchoServiceImpl _echo_svc;
};

TEST_F(InterceptorTest, sanity) {
    ::test::EchoRequest req;
    ::test::EchoResponse res;
    req.set_message(EXP_REQUEST);

    // PROTOCOL_BAIDU_STD
    {
        brpc::Channel channel;
        brpc::ChannelOptions options;
        ASSERT_EQ(0, channel.Init("localhost", port, &options));
        test::EchoService_Stub stub(&channel);
        CallMethod(stub, req, res);
    }

    // PROTOCOL_HTTP
    {
        brpc::Channel channel;
        brpc::ChannelOptions options;
        options.protocol = brpc::PROTOCOL_HTTP;
        ASSERT_EQ(0, channel.Init("localhost", port, &options));
        test::EchoService_Stub stub(&channel);
        // Set the x-bd-error-code header of http response to brpc error code.
        brpc::policy::FLAGS_use_http_error_code = true;
        CallMethod(stub, req, res);
    }

    // PROTOCOL_HULU_PBRPC
    {
        brpc::Channel channel;
        brpc::ChannelOptions options;
        options.protocol = brpc::PROTOCOL_HULU_PBRPC;
        ASSERT_EQ(0, channel.Init("localhost", port, &options));
        test::EchoService_Stub stub(&channel);
        CallMethod(stub, req, res);
    }

    // PROTOCOL_SOFA_PBRPC
    {
        brpc::Channel channel;
        brpc::ChannelOptions options;
        options.protocol = brpc::PROTOCOL_SOFA_PBRPC;
        ASSERT_EQ(0, channel.Init("localhost", port, &options));
        test::EchoService_Stub stub(&channel);
        CallMethod(stub, req, res);
    }

    // PROTOCOL_NSHEAD
    {
        brpc::Channel channel;
        brpc::ChannelOptions options;
        options.protocol = brpc::PROTOCOL_NSHEAD;
        ASSERT_EQ(0, channel.Init("localhost", port, &options));
        brpc::NsheadMessage request;
        for (g_index = 0; g_index < 1000; ++g_index) {
            brpc::Controller cntl;
            brpc::NsheadMessage response;
            channel.CallMethod(NULL, &cntl, &request, &response, NULL);
            if (g_index % 2 == 0) {
                ASSERT_EQ(NSHEAD_EXP_RESPONSE, response.body.to_string());
            } else {
                ASSERT_EQ(EXP_RESPONSE, response.body.to_string());
            }
        }
    }
}