import glob
import logging
import platform
#import os
import shutil

logging.basicConfig()
logger = logging.getLogger(name=__name__)

try:
    import unittest2 as unittest
except ImportError:
    import unittest

from mock import Mock, patch  # MagicMock

from leap.eip.eipconnection import EIPConnection
from leap.eip.exceptions import ConnectionRefusedError
from leap.eip import specs as eipspecs
from leap.testing.basetest import BaseLeapTest

_system = platform.system()

PROVIDER = "testprovider.example.org"


class NotImplementedError(Exception):
    pass


@patch('OpenVPNConnection._get_or_create_config')
@patch('OpenVPNConnection._set_ovpn_command')
class MockedEIPConnection(EIPConnection):

    def _set_ovpn_command(self):
        self.command = "mock_command"
        self.args = [1, 2, 3]


class EIPConductorTest(BaseLeapTest):

    __name__ = "eip_conductor_tests"
    provider = PROVIDER

    def setUp(self):
        # XXX there's a conceptual/design
        # mistake here.
        # If we're testing just attrs after init,
        # init shold not be doing so much side effects.

        # for instance:
        # We have to TOUCH a keys file because
        # we're triggerig the key checks FROM
        # the constructor. me not like that,
        # key checker should better be called explicitelly.

        # XXX change to keys_checker invocation
        # (see config_checker)

        keyfiles = (eipspecs.provider_ca_path(domain=self.provider),
                    eipspecs.client_cert_path(domain=self.provider))
        for filepath in keyfiles:
            self.touch(filepath)
            self.chmod600(filepath)

        # we init the manager with only
        # some methods mocked
        self.manager = Mock(name="openvpnmanager_mock")
        self.con = MockedEIPConnection()
        self.con.provider = self.provider

        # XXX watch out. This sometimes is throwing the following error:
        # NoSuchProcess: process no longer exists (pid=6571)
        # because of a bad implementation of _check_if_running_instance

        self.con.run_openvpn_checks()

    def tearDown(self):
        pass

    def doCleanups(self):
        super(BaseLeapTest, self).doCleanups()
        self.cleanupSocketDir()
        del self.con

    def cleanupSocketDir(self):
        ptt = ('/tmp/leap-tmp*')
        for tmpdir in glob.glob(ptt):
            shutil.rmtree(tmpdir)

    #
    # tests
    #

    def test_vpnconnection_defaults(self):
        """
        default attrs as expected
        """
        con = self.con
        self.assertEqual(con.autostart, True)
        # XXX moar!

    def test_ovpn_command(self):
        """
        set_ovpn_command called
        """
        self.assertEqual(self.con.command,
                         "mock_command")
        self.assertEqual(self.con.args,
                         [1, 2, 3])

    # config checks

    def test_config_checked_called(self):
        # XXX this single test is taking half of the time
        # needed to run tests. (roughly 3 secs for this only)
        # We should modularize and inject Mocks on more places.

        oldcon = self.con
        del(self.con)
        config_checker = Mock()
        self.con = MockedEIPConnection(config_checker=config_checker)
        self.assertTrue(config_checker.called)
        self.con.run_checks()
        self.con.config_checker.run_all.assert_called_with(
            skip_download=False)

        # XXX test for cert_checker also
        self.con = oldcon

    # connect/disconnect calls

    def test_disconnect(self):
        """
        disconnect method calls private and changes status
        """
        self.con._disconnect = Mock(
            name="_disconnect")

        # first we set status to connected
        self.con.status.set_current(self.con.status.CONNECTED)
        self.assertEqual(self.con.status.current,
                         self.con.status.CONNECTED)

        # disconnect
        self.con.terminate_openvpn_connection = Mock()
        self.con.disconnect()
        self.con.terminate_openvpn_connection.assert_called_once_with(
            shutdown=False)
        self.con.terminate_openvpn_connection = Mock()
        self.con.disconnect(shutdown=True)
        self.con.terminate_openvpn_connection.assert_called_once_with(
            shutdown=True)

        # new status should be disconnected
        # XXX this should evolve and check no errors
        # during disconnection
        self.assertEqual(self.con.status.current,
                         self.con.status.DISCONNECTED)

    def test_connect(self):
        """
        connect calls _launch_openvpn private
        """
        self.con._launch_openvpn = Mock()
        self.con.connect()
        self.con._launch_openvpn.assert_called_once_with()

    # XXX tests breaking here ...

    def test_good_poll_connection_state(self):
        """
        """
        #@patch --
        # self.manager.get_connection_state

        #XXX review this set of poll_state tests
        #they SHOULD NOT NEED TO MOCK ANYTHING IN THE
        #lower layers!! -- status, vpn_manager..
        #right now we're testing implementation, not
        #behavior!!!
        good_state = ["1345466946", "unknown_state", "ok",
                      "192.168.1.1", "192.168.1.100"]
        self.con.get_connection_state = Mock(return_value=good_state)
        self.con.status.set_vpn_state = Mock()

        state = self.con.poll_connection_state()
        good_state[1] = "disconnected"
        final_state = tuple(good_state)
        self.con.status.set_vpn_state.assert_called_with("unknown_state")
        self.assertEqual(state, final_state)

    # TODO between "good" and "bad" (exception raised) cases,
    # we can still test for malformed states and see that only good
    # states do have a change (and from only the expected transition
    # states).

    def test_bad_poll_connection_state(self):
        """
        get connection state raises ConnectionRefusedError
        state is None
        """
        self.con.get_connection_state = Mock(
            side_effect=ConnectionRefusedError('foo!'))
        state = self.con.poll_connection_state()
        self.assertEqual(state, None)


    # XXX more things to test:
    # - called config routines during initz.
    # - raising proper exceptions with no config
    # - called proper checks on config / permissions


if __name__ == "__main__":
    unittest.main()