import mock

from stem import Flag

from twisted.internet import defer
from twisted.trial import unittest

from oppy.path import path
from oppy.path import util as path_util
from oppy.path.exceptions import NoUsableGuardsException


class PathTest(unittest.TestCase):

    @mock.patch('oppy.path.path.selectExitNode', return_value='exit_fprint')
    @mock.patch('oppy.path.path.selectGuardNode', return_value='guard_fprint')
    @mock.patch('oppy.path.path.selectMiddleNode', return_value='middle_fprint')
    def test_getPath(self, mock_sm, mock_sg, mock_se):
        mock_ns = mock.Mock()
        mock_gm = mock.Mock()

        mock_exit_rs = mock.Mock()
        mock_exit_rs.digest = 'exit digest'
        mock_middle_rs = mock.Mock()
        mock_middle_rs.digest = 'middle digest'
        mock_guard_rs = mock.Mock()
        mock_guard_rs.digest = 'guard digest'


        mock_consensus = mock.Mock()
        mock_consensus.routers = {'exit_fprint': mock_exit_rs,
                                  'middle_fprint': mock_middle_rs,
                                  'guard_fprint': mock_guard_rs}

        mock_consensus.bandwidth_weights = 'bandwidth weights'
        mock_exit_request = mock.Mock()
        mock_exit_request.port = 'port'

        mock_descriptors = {'exit digest': 'exit_fprint',
                            'middle digest': 'middle_fprint',
                            'guard digest': 'guard_fprint'}

        mock_ns.getMicroconsensus = mock.Mock()
        mock_ns.getMicroconsensus.return_value = defer.succeed(mock_consensus)
        mock_ns.getMicrodescriptorsForCircuit = mock.Mock()
        mock_ns.getMicrodescriptorsForCircuit.return_value = \
            defer.succeed(mock_descriptors)
        mock_gm.getUsableGuards = mock.Mock()
        mock_gm.getUsableGuards.return_value = defer.succeed('guards')

        mock_fast = True
        mock_stable = True
        mock_internal = False

        mock_bwweightscale = path.DEFAULT_BWWEIGHTSCALE
        
        ret = path.getPath(mock_ns, mock_gm, mock_exit_request, mock_fast,
                           mock_stable, mock_internal)

        mock_se.assert_called_once_with(mock_consensus.bandwidth_weights,
                                        mock_bwweightscale,
                                        mock_consensus.routers,
                                        mock_descriptors,
                                        mock_fast,
                                        mock_stable,
                                        mock_internal,
                                        mock_exit_request.port)

        mock_sg.assert_called_once_with(mock_consensus.routers,
                                        mock_descriptors,
                                        'guards',
                                        mock_fast,
                                        mock_stable,
                                        'exit_fprint',
                                        mock_exit_rs)
        
        mock_sm.assert_called_once_with(mock_consensus.bandwidth_weights,
                                        mock_bwweightscale,
                                        mock_consensus.routers,
                                        mock_descriptors,
                                        mock_fast,
                                        mock_stable,
                                        'exit_fprint',
                                        mock_exit_rs,
                                        'guard_fprint',
                                        mock_guard_rs)

        self.assertEqual(self.successResultOf(ret),
            path.Path(path.PathNode('guard_fprint', mock_guard_rs),
                      path.PathNode('middle_fprint', mock_middle_rs),
                      path.PathNode('exit_fprint', mock_exit_rs)))

    @mock.patch('oppy.path.path.selectExitNode', side_effect=ValueError())
    def test_getPath_exception(self, mock_se):
        mock_ns = mock.Mock()
        mock_gm = mock.Mock()

        mock_exit_rs = mock.Mock()
        mock_exit_rs.digest = 'exit digest'
        mock_middle_rs = mock.Mock()
        mock_middle_rs.digest = 'middle digest'
        mock_guard_rs = mock.Mock()
        mock_guard_rs.digest = 'guard digest'


        mock_consensus = mock.Mock()
        mock_consensus.routers = {'exit_fprint': mock_exit_rs,
                                  'middle_fprint': mock_middle_rs,
                                  'guard_fprint': mock_guard_rs}

        mock_consensus.bandwidth_weights = 'bandwidth weights'
        mock_exit_request = mock.Mock()
        mock_exit_request.port = 'port'

        mock_descriptors = {'exit digest': 'exit_fprint',
                            'middle digest': 'middle_fprint',
                            'guard digest': 'guard_fprint'}

        mock_ns.getMicroconsensus = mock.Mock()
        mock_ns.getMicroconsensus.return_value = defer.succeed(mock_consensus)
        mock_ns.getMicrodescriptorsForCircuit = mock.Mock()
        mock_ns.getMicrodescriptorsForCircuit.return_value = \
            defer.succeed(mock_descriptors)
        mock_gm.getUsableGuards = mock.Mock()
        mock_gm.getUsableGuards.return_value = defer.succeed('guards')

        mock_fast = True
        mock_stable = True
        mock_internal = False

        mock_bwweightscale = path.DEFAULT_BWWEIGHTSCALE

        self.assertEqual(
            self.failureResultOf(
                path.getPath(mock_ns,
                             mock_gm,
                             mock_exit_request,
                             mock_fast,
                             mock_stable,
                             mock_internal))\
                                .trap(path.PathSelectionFailedException),
            path.PathSelectionFailedException)

    @mock.patch('oppy.path.util.getPositionWeights', return_value='weights')
    @mock.patch('oppy.path.util.getWeightedNodes', return_value='nodes')
    @mock.patch('oppy.path.util.selectWeightedNode', return_value='blah')
    @mock.patch('oppy.path.path.filterExits', return_value=['exit1', 'exit2'])
    def test_selectExitNode(self, mock_fe, mock_swn, mock_gwn, mock_gpw):
        bw_weights = 'bw_weights'
        bwweightscale = 'bwweightscale'
        cons_rel_stats = 'cons_rel_stats'
        descriptors = 'descriptors'
        fast = 'fast'
        stable = 'stable'
        internal = 'internal'
        port = 'port'

        _ = path.selectExitNode(
            bw_weights,
            bwweightscale,
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        mock_fe.assert_called_once_with(
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        mock_gpw.assert_called_once_with(
            ['exit1', 'exit2'],
            cons_rel_stats,
            'e',
            bw_weights,
            bwweightscale)

        mock_gwn.assert_called_once_with(['exit1', 'exit2'], 'weights')

        mock_swn.assert_called_once_with('nodes')
        
    @mock.patch('oppy.path.util.getPositionWeights', return_value='weights')
    @mock.patch('oppy.path.util.getWeightedNodes', return_value='nodes')
    @mock.patch('oppy.path.util.selectWeightedNode', return_value='blah')
    @mock.patch('oppy.path.path.filterExits', return_value=['exit1', 'exit2'])
    def test_selectExitNode_internal(self, mock_fe, mock_swn, mock_gwn, mock_gpw):
        bw_weights = 'bw_weights'
        bwweightscale = 'bwweightscale'
        cons_rel_stats = 'cons_rel_stats'
        descriptors = 'descriptors'
        fast = 'fast'
        stable = 'stable'
        internal = True
        port = 'port'

        _ = path.selectExitNode(
            bw_weights,
            bwweightscale,
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        mock_fe.assert_called_once_with(
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        mock_gpw.assert_called_once_with(
            ['exit1', 'exit2'],
            cons_rel_stats,
            'm',
            bw_weights,
            bwweightscale)

    @mock.patch('oppy.path.path.filterExits', return_value=[])
    def test_selectExitNode_no_exits(self, mock_fe):
        bw_weights = 'bw_weights'
        bwweightscale = 'bwweightscale'
        cons_rel_stats = 'cons_rel_stats'
        descriptors = 'descriptors'
        fast = 'fast'
        stable = 'stable'
        internal = 'internal'
        port = 'port'

        self.assertRaises(ValueError,
                          path.selectExitNode,
                          bw_weights,
                          bwweightscale,
                          cons_rel_stats,
                          descriptors,
                          fast,
                          stable,
                          internal,
                          port)

    @mock.patch('oppy.path.util.getPositionWeights', return_value='weights')
    @mock.patch('oppy.path.util.getWeightedNodes', return_value='nodes')
    @mock.patch('oppy.path.util.selectWeightedNode', return_value='blah')
    @mock.patch('oppy.path.path.filterExits', return_value=['exit1'])
    def test_selectExitNode_one_exit(self, mock_fe, mock_swn, mock_gwn, mock_gpw):
        bw_weights = 'bw_weights'
        bwweightscale = 'bwweightscale'
        cons_rel_stats = 'cons_rel_stats'
        descriptors = 'descriptors'
        fast = 'fast'
        stable = 'stable'
        internal = 'internal'
        port = 'port'

        ret = path.selectExitNode(
            bw_weights,
            bwweightscale,
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        self.assertEqual(ret, 'exit1')

        mock_fe.assert_called_once_with(
            cons_rel_stats,
            descriptors,
            fast,
            stable,
            internal,
            port)

        self.assertEqual(mock_gpw.call_count, 0)
        self.assertEqual(mock_gwn.call_count, 0)
        self.assertEqual(mock_swn.call_count, 0)