#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <myapp/rcon/client.h>
#include <myapp/rcon/server.h>
#include <myapp/rcon/unixprovider.h>
#include "async/pipemock.h"
#include "rcon/peerprovidermock.h"
#include "logging/nulllogger.h"
#include "async/uveventloop.h"
#include "async/uvpipe.h"

using namespace ::testing;
using namespace myapp::async;
using namespace myapp::rcon;

class UnixProviderTests : public Test
{
public:
    UnixProviderTests()
    {
    }

    using pipe_mock_t = NiceMock<pipe_mock>;
    std::unique_ptr<pipe_mock_t> pipeMock_ = std::make_unique<pipe_mock_t>();
};

TEST_F(UnixProviderTests, Server_CallsPipeBindAndListen)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";

    EXPECT_CALL(*pipeMock_, bind(StrEq(unixSock)))
        .Times(1);
    EXPECT_CALL(*pipeMock_, listen(Ge(1), _))
        .Times(1);

    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
    provider->server(unixSock);
}

TEST_F(UnixProviderTests, Server_CallsPipeAcceptWhenPeerConnected)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";

    EXPECT_CALL(*pipeMock_, listen(_, _))
        .WillOnce(InvokeArgument<1>());

    EXPECT_CALL(*pipeMock_, accept())
        .WillOnce(Return(ByMove(std::make_unique<pipe_mock_t>())));

    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
    provider->server(unixSock);
}

TEST_F(UnixProviderTests, Server_CallbacksWhenPeerConnected)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";
    bool called = false;

    EXPECT_CALL(*pipeMock_, listen(_, _))
        .WillOnce(InvokeArgument<1>());

    EXPECT_CALL(*pipeMock_, accept())
        .WillOnce(Return(ByMove(std::make_unique<pipe_mock_t>())));

    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
    provider->on_callback([&called](auto conn_ptr) { called = true; });
    provider->server(unixSock);
    ASSERT_TRUE(called);
}

TEST_F(UnixProviderTests, Client_CallbacksWhenConnected)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";
    bool called = false;

    EXPECT_CALL(*pipeMock_, connect(_, _))
        .WillOnce(InvokeArgument<1>());

    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
    provider->on_callback([&called](auto conn_ptr) { called = true; });
    provider->client(unixSock);
    ASSERT_TRUE(called);
}

//TEST_F(UnixProviderTests, ServerOnData_CallsPipeWrite)
//{
//    auto const request = u8"abc\n";
//    auto const response = u8"def\n";
//
//    EXPECT_CALL(*pipeMock_, write(StrEq(response), _))
//        .Times(1);
//
//    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
//    provider->set_data_handler([=](std::string msg) -> std::string
//    {
//        EXPECT_THAT(msg, StrEq(request));
//        return response;
//    });
//
//    provider->set_on_data(request);
//}
//
//TEST_F(UnixProviderTests, ClientWrite_CallsPipeWrite)
//{
//    auto const request = u8"abc\n";
//
//    EXPECT_CALL(*pipeMock_, write(StrEq(request), _))
//            .Times(1);
//
//    auto provider = std::make_unique<unix_provider>(std::move(pipeMock_));
//    provider->client_write(request);
//}

class RconServerTests : public Test
{
public:
    RconServerTests()
    {
    }

    using peer_provider_mock_t = NiceMock<peer_provider_mock>;
    std::unique_ptr<peer_provider_mock_t> peerProviderMock_ = std::make_unique<peer_provider_mock_t>();

    std::shared_ptr<null_logger> loggerMock_ = std::make_shared<null_logger>();
};

TEST_F(RconServerTests, Start_CallsProvider)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";

    EXPECT_CALL(*peerProviderMock_, server(StrEq(unixSock)))
        .Times(1);

    server srv(loggerMock_, std::move(peerProviderMock_));
    srv.start(unixSock);
}


class RconClientTests : public Test
{
public:
    RconClientTests()
    {
    }

    using peer_provider_mock_t = NiceMock<peer_provider_mock>;
    std::unique_ptr<peer_provider_mock_t> peerProviderMock_ = std::make_unique<peer_provider_mock_t>();

    std::shared_ptr<null_logger> loggerMock_ = std::make_shared<null_logger>();

};

TEST_F(RconClientTests, Connect_CallsProvider)
{
    auto const unixSock = u8"/tmp/myapptest_rconservertests.sock";

    EXPECT_CALL(*peerProviderMock_, client(StrEq(unixSock)))
        .Times(1);

    client cli(loggerMock_, std::move(peerProviderMock_));
    cli.connect(unixSock);
}
