# -*- coding: utf-8 -*-

import pytest
import json
from contextlib import contextmanager
from .test_base_class import TestBaseClass
from aerospike import exception as e

import aerospike


@contextmanager
def open_as_connection(config):
    """
    Context manager to let us open aerospike connections with
    specified config
    """
    as_connection = TestBaseClass.get_new_connection(config)

    # Connection is setup, so yield it
    yield as_connection

    # close the connection
    as_connection.close()


# adds cls.connection_config to this class
@pytest.mark.usefixtures("connection_config")
class TestConnect(object):
    def test_connect_positive(self):
        """
        Invoke connect() with positive parameters.
        """
        config = self.connection_config.copy()

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()

    def test_connect_positive_with_policy(self):
        """
        Invoke connect() with positive parameters and policy.
        """

        config = self.connection_config.copy()
        config["policies"] = {"read": {"total_timeout": 10000}}

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()

    @pytest.mark.skip(reason="This doesn't actually use multiple hosts")
    def test_connect_positive_with_multiple_hosts(self):
        """
        Invoke connect() with multiple hosts.
        """
        config = self.connection_config.copy()
        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()

    def test_connect_positive_unicode_hosts(self):
        """
        Invoke connect() with unicode hosts.
        """

        #  Convert to and from json, to force unicode
        #  This works in py 2.7 and 3.5
        uni = json.dumps(self.connection_config["hosts"][0])
        hostlist = json.loads(uni)
        config = {"hosts": [tuple(hostlist)]}
        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()

    @pytest.mark.skipif(TestBaseClass.tls_in_use(), reason="no default port for tls")
    def test_connect_hosts_missing_port(self):
        """
        Invoke connect() with missing port in config dict.
        """
        config = {"hosts": [("127.0.0.1")]}

        with open_as_connection(config) as client:
            assert client.is_connected()

    def test_connect_positive_shm_key(self):
        """
        Invoke connect() with shm_key specified
        """
        config = self.connection_config.copy()
        config["shm"] = {"shm_key": 3}

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()
            assert client.shm_key() == 3

    # This test doesn't actually check if the options work properly
    # It just checks that the setting is configured in the Python client (basically a coverage test)
    @pytest.mark.parametrize(
        "other_config",
        [
            {"min_conns_per_node": 0},
            {"app_id": "app_name"}
        ]
    )
    def test_connect_positive_config_options(self, other_config):
        """
        Invoke connect() with min_conns_per_node specified
        """
        config = self.connection_config.copy()
        config.update(other_config)

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()

    def test_invalid_app_id(self):
        config = self.connection_config.copy()
        config["app_id"] = 1

        with pytest.raises(TypeError):
            aerospike.client(config)

    def test_connect_positive_shm_key_default(self):
        """
        Invoke connect() with shm enabled but shm_key not specified
        """
        config = self.connection_config.copy()
        config["shm"] = {"shm_max_nodes": 5}

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()
            assert client.shm_key() is not None

    def test_connect_positive_shm_not_enabled(self):
        """
        Invoke connect() with shm not anabled
        """
        config = self.connection_config.copy()

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()
            assert client.shm_key() is None

    def test_connect_positive_cluster_name(self):
        """
        Invoke connect() giving a cluster name. This is just a usage test (doesn't care if the server's cluster name
        matches or not)
        """
        config = self.connection_config.copy()
        config["cluster_name"] = "test-cluster"

        try:
            self.client = aerospike.client(config).connect()
        except e.ClientError:
            pass

    def test_connect_positive_reconnect(self):
        """
        Client call itself establishes connection.
        """
        config = self.connection_config.copy()

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()
            client.close()
            assert client.is_connected() is False
            if TestBaseClass.user is None and TestBaseClass.password is None:
                client.connect()
            else:
                client.connect(TestBaseClass.user, TestBaseClass.password)
            assert client.is_connected()

    def test_connect_on_connected_client(self):
        """
        Invoke connect on a client that is already connected.
        """
        config = self.connection_config.copy()

        with open_as_connection(config) as client:
            assert client is not None
            assert client.is_connected()
            client.connect()
            assert client.is_connected()

    # Tests for invalid Usage

    def test_connect_with_extra_args(self):
        with pytest.raises(TypeError):
            client = aerospike.client(self.connection_config)
            client.close()
            client.connect("username", "password", "extra arg")

    @pytest.mark.parametrize(
        "config, err, err_code, err_msg",
        [
            (1, e.ParamError, -2, "Config must be a dict"),
            ({}, e.ParamError, -2, "Hosts must be a list"),
            ({"": [("127.0.0.1", 3000)]}, e.ParamError, -2, "Hosts must be a list"),
            ({"hosts": [3000]}, e.ParamError, -2, "Invalid host"),
            # Errors that throw -10 can also throw 9
            ({"hosts": [("127.0.0.1", 2000)]}, (e.ClientError, e.TimeoutError), (-10, 9), "Failed to connect"),
            ({"hosts": [("127.0.0.1", "3000")]}, e.ClientError, -10, "Failed to connect"),
        ],
        ids=[
            "config not dict",
            "config empty dict",
            "config missing hosts key",
            "hosts missing address",
            "hosts port is incorrect",
            "hosts port is string",
        ],
    )
    def test_connect_invalid_configs(self, config, err, err_code, err_msg):
        with pytest.raises(err) as err_info:
            self.client = aerospike.client(config).connect()

        if type(err_code) == tuple:
            assert err_info.value.code in err_code
        else:
            assert err_info.value.code == err_code
        assert err_info.value.msg == err_msg
