import mitogen.core
import mitogen.service
import testlib


class MyService(mitogen.service.Service):
    def __init__(self, router):
        super(MyService, self).__init__(router)
        self._counter = 0

    @mitogen.service.expose(policy=mitogen.service.AllowParents())
    def get_id(self):
        self._counter += 1
        return self._counter, id(self)

    @mitogen.service.expose(policy=mitogen.service.AllowParents())
    @mitogen.service.arg_spec({
        'foo': int
    })
    def test_arg_spec(self, foo):
        return foo

    @mitogen.service.expose(policy=mitogen.service.AllowParents())
    def privileged_op(self):
        return 'privileged!'

    @mitogen.service.expose(policy=mitogen.service.AllowAny())
    def unprivileged_op(self):
        return 'unprivileged!'


class MyService2(MyService):
    """
    A uniquely named service that lets us test framework activation and class
    activation separately.
    """


def call_service_in(context, service_name, method_name):
    return context.call_service(service_name, method_name)


class CallTest(testlib.RouterMixin, testlib.TestCase):
    def test_local(self):
        pool = mitogen.service.get_or_create_pool(router=self.router)
        self.assertEqual(
            'privileged!',
            mitogen.service.call(MyService, 'privileged_op')
        )
        pool.stop()

    def test_remote_bad_arg(self):
        c1 = self.router.local()
        self.assertRaises(
            mitogen.core.CallError,
            lambda: mitogen.service.call(
                MyService.name(),
                'test_arg_spec',
                foo='x',
                call_context=c1
            )
        )

    def test_local_unicode(self):
        pool = mitogen.service.get_or_create_pool(router=self.router)
        self.assertEqual(
            'privileged!',
            mitogen.service.call(MyService.name(), 'privileged_op')
        )
        pool.stop()

    def test_remote(self):
        c1 = self.router.local()
        self.assertEqual(
            'privileged!',
            mitogen.service.call(MyService, 'privileged_op',
                                 call_context=c1)
        )


class ActivationTest(testlib.RouterMixin, testlib.TestCase):
    def test_parent_can_activate(self):
        l1 = self.router.local()
        counter, id_ = l1.call_service(MyService, 'get_id')
        self.assertEqual(1, counter)
        self.assertIsInstance(id_, int)

    def test_sibling_cannot_activate_framework(self):
        l1 = self.router.local(name='l1')
        l2 = self.router.local(name='l2')
        exc = self.assertRaises(mitogen.core.CallError,
            lambda: l2.call(call_service_in, l1, MyService2.name(), 'get_id'))
        self.assertIn(mitogen.core.Router.refused_msg, exc.args[0])

    def test_sibling_cannot_activate_service(self):
        l1 = self.router.local()
        l2 = self.router.local()
        l1.call_service(MyService, 'get_id')  # force framework activation
        capture = testlib.LogCapturer()
        capture.start()
        try:
            exc = self.assertRaises(mitogen.core.CallError,
                lambda: l2.call(call_service_in, l1, MyService2.name(), 'get_id'))
        finally:
            capture.stop()
        msg = mitogen.service.Activator.not_active_msg % (MyService2.name(),)
        self.assertIn(msg, exc.args[0])

    def test_activates_only_once(self):
        l1 = self.router.local()
        counter, id_ = l1.call_service(MyService, 'get_id')
        counter2, id_2 = l1.call_service(MyService, 'get_id')
        self.assertEqual(1, counter)
        self.assertEqual(2, counter2)
        self.assertEqual(id_, id_2)


class PermissionTest(testlib.RouterMixin, testlib.TestCase):
    def test_sibling_unprivileged_ok(self):
        l1 = self.router.local()
        l1.call_service(MyService, 'get_id')
        l2 = self.router.local()
        self.assertEqual('unprivileged!',
            l2.call(call_service_in, l1, MyService.name(), 'unprivileged_op'))

    def test_sibling_privileged_bad(self):
        l1 = self.router.local()
        l1.call_service(MyService, 'get_id')
        l2 = self.router.local()
        capture = testlib.LogCapturer()
        capture.start()
        try:
            exc = self.assertRaises(mitogen.core.CallError, lambda:
                l2.call(call_service_in, l1, MyService.name(), 'privileged_op'))
        finally:
            capture.stop()
        msg = mitogen.service.Invoker.unauthorized_msg % (
            u'privileged_op',
            MyService.name(),
        )
        self.assertIn(msg, exc.args[0])


class CloseTest(testlib.RouterMixin, testlib.TestCase):
    klass = mitogen.service.Pool

    def test_receiver_closed(self):
        pool = self.klass(router=self.router, services=[])
        pool.stop()
        self.assertEqual(None, pool._receiver.handle)

        e = self.assertRaises(mitogen.core.ChannelError,
            lambda: self.router.myself().call_service(MyService, 'foobar'))
        self.assertEqual(e.args[0], self.router.invalid_handle_msg)
