#include <catch.hpp>

#include <pub_intl.hh>
#include <pub_plugins_intl.hh>
#include <cds-pubsub/pub.hh>

#include <cstdlib>
#include <map>
#include <stdexcept>
#include <string>

namespace
{
    class EnvGuard
    {
    public:
        EnvGuard( )
        {
            unsetenv( "PUBLISHER_MAX_DEPTH" );
        }
        ~EnvGuard( )
        {
            unsetenv( "PUBLISHER_MAX_DEPTH" );
        }
    };

    pub_sub::detail::PublisherOptions
    options_with_depth( const std::string& value )
    {
        setenv( "PUBLISHER_MAX_DEPTH", value.c_str( ), 1 );
        return pub_sub::detail::PublisherOptions::from_environment( );
    }
} // namespace

TEST_CASE( "PublisherOptions has sane defaults" )
{
    EnvGuard guard;

    auto options = pub_sub::detail::PublisherOptions::from_environment( );
    REQUIRE( options.max_depth == 8 );
}

TEST_CASE( "parse_max_depth accepts integers in 3-60" )
{
    REQUIRE( pub_sub::detail::PublisherOptions::parse_max_depth( "3" ) == 3 );
    REQUIRE( pub_sub::detail::PublisherOptions::parse_max_depth( "10" ) == 10 );
    REQUIRE( pub_sub::detail::PublisherOptions::parse_max_depth( "59" ) == 59 );
    REQUIRE( pub_sub::detail::PublisherOptions::parse_max_depth( "60" ) == 60 );
}

TEST_CASE( "parse_max_depth throws on anything else" )
{
    REQUIRE_THROWS_AS(
        pub_sub::detail::PublisherOptions::parse_max_depth( "2" ),
        std::runtime_error );
    REQUIRE_THROWS_AS(
        pub_sub::detail::PublisherOptions::parse_max_depth( "61" ),
        std::runtime_error );
    REQUIRE_THROWS_AS( pub_sub::detail::PublisherOptions::parse_max_depth( "" ),
                       std::runtime_error );
    REQUIRE_THROWS_AS(
        pub_sub::detail::PublisherOptions::parse_max_depth( "abc" ),
        std::runtime_error );
    REQUIRE_THROWS_AS(
        pub_sub::detail::PublisherOptions::parse_max_depth( "5x" ),
        std::runtime_error );
    REQUIRE_THROWS_AS(
        pub_sub::detail::PublisherOptions::parse_max_depth( "-1" ),
        std::runtime_error );
}

TEST_CASE( "PUBLISHER_MAX_DEPTH accepts integers in 3-60" )
{
    EnvGuard guard;

    REQUIRE( options_with_depth( "3" ).max_depth == 3 );
    REQUIRE( options_with_depth( "60" ).max_depth == 60 );
    REQUIRE( options_with_depth( "10" ).max_depth == 10 );
}

TEST_CASE( "Invalid PUBLISHER_MAX_DEPTH values keep the default" )
{
    EnvGuard guard;

    REQUIRE( options_with_depth( "2" ).max_depth == 8 );
    REQUIRE( options_with_depth( "61" ).max_depth == 8 );
    REQUIRE( options_with_depth( "" ).max_depth == 8 );
    REQUIRE( options_with_depth( "abc" ).max_depth == 8 );
    REQUIRE( options_with_depth( "5x" ).max_depth == 8 );
    REQUIRE( options_with_depth( "-1" ).max_depth == 8 );
}

TEST_CASE( "PublisherOptions::with_arguments applies overrides" )
{
    EnvGuard guard;

    pub_sub::detail::PublisherOptions base{ };

    REQUIRE( base.with_arguments( { } ).max_depth == 8 );
    REQUIRE( base.with_arguments( { { "max_depth", "3" } } ).max_depth == 3 );
    REQUIRE( base.with_arguments( { { "max_depth", "60" } } ).max_depth == 60 );
}

TEST_CASE( "PublisherOptions::with_arguments throws on invalid arguments" )
{
    EnvGuard guard;

    pub_sub::detail::PublisherOptions base{ };

    REQUIRE_THROWS_AS( base.with_arguments( { { "max_depth", "2" } } ),
                       std::runtime_error );
    REQUIRE_THROWS_AS( base.with_arguments( { { "max_depth", "61" } } ),
                       std::runtime_error );
    REQUIRE_THROWS_AS( base.with_arguments( { { "max_depth", "abc" } } ),
                       std::runtime_error );
    REQUIRE_THROWS_AS( base.with_arguments( { { "bogus", "1" } } ),
                       std::runtime_error );
}

TEST_CASE( "Connection string options override the environment for tcp" )
{
    EnvGuard guard;
    setenv( "PUBLISHER_MAX_DEPTH", "5", 1 );

    auto debug = pub_sub::detail::NullPubDebugNotices( );

    pub_sub::detail::TcpPublisherPluginApi plugin{
        pub_sub::detail::PublisherOptions::from_environment( )
    };

    {
        auto instance =
            plugin.publish( "tcp://127.0.0.1:0?max_depth=3", *debug, nullptr );
        auto tcp = dynamic_cast< pub_sub::detail::PublisherIntlTcp* >(
            instance.get( ) );
        REQUIRE( tcp != nullptr );
        REQUIRE( tcp->messages_.capacity( ) == 3 );
    }
    {
        /* without arguments the environment derived options apply */
        auto instance = plugin.publish( "tcp://127.0.0.1:0", *debug, nullptr );
        auto tcp = dynamic_cast< pub_sub::detail::PublisherIntlTcp* >(
            instance.get( ) );
        REQUIRE( tcp != nullptr );
        REQUIRE( tcp->messages_.capacity( ) == 5 );
    }
}

TEST_CASE( "The tcp plugin throws on invalid connection string options" )
{
    EnvGuard guard;

    auto debug = pub_sub::detail::NullPubDebugNotices( );

    pub_sub::detail::TcpPublisherPluginApi plugin{ };

    REQUIRE_THROWS_AS(
        plugin.publish( "tcp://127.0.0.1:0?max_depth=99", *debug, nullptr ),
        std::runtime_error );
    REQUIRE_THROWS_AS(
        plugin.publish( "tcp://127.0.0.1:0?bogus=1", *debug, nullptr ),
        std::runtime_error );
    REQUIRE_THROWS_AS(
        plugin.publish( "tcp://127.0.0.1:0?max_depth", *debug, nullptr ),
        std::runtime_error );
}

TEST_CASE( "The udp plugin accepts connection string options" )
{
    EnvGuard guard;

    auto debug = pub_sub::detail::NullPubDebugNotices( );

    pub_sub::detail::UdpPublisherPluginApi plugin{ };

    auto instance = plugin.publish(
        "udp://127.0.0.1/127.0.0.1:9000?max_depth=10", *debug, nullptr );
    auto udp =
        dynamic_cast< pub_sub::detail::PublisherIntlUdp* >( instance.get( ) );
    REQUIRE( udp != nullptr );
    REQUIRE( udp->messages_.capacity( ) == 10 );

    REQUIRE_THROWS_AS( plugin.publish( "udp://127.0.0.1/127.0.0.1:9000?"
                                       "max_depth=99",
                                       *debug,
                                       nullptr ),
                       std::runtime_error );
}

TEST_CASE( "Publisher::add_destination rejects invalid options" )
{
    EnvGuard guard;

    auto               debug = pub_sub::detail::NullPubDebugNotices( );
    pub_sub::Publisher pub( debug );

    REQUIRE_THROWS_AS( pub.add_destination( "tcp://127.0.0.1:0?max_depth=2" ),
                       std::runtime_error );
    REQUIRE_THROWS_AS( pub.add_destination( "tcp://127.0.0.1:0?unknown=1" ),
                       std::runtime_error );
    /* a valid option string still creates the destination */
    pub.add_destination( "tcp://127.0.0.1:0?max_depth=10" );
    REQUIRE( true );
}
