from celery.tests.utils import unittest

from celery import routes
from celery import current_app
from celery.utils import maybe_promise
from celery.utils.functional import wraps
from celery.exceptions import QueueNotFound


def E(queues):
    def expand(answer):
        return routes.Router([], queues).expand_destination(answer)
    return expand


def with_queues(**queues):

    def patch_fun(fun):
        @wraps(fun)
        def __inner(*args, **kwargs):
            app = current_app
            prev_queues = app.conf.CELERY_QUEUES
            app.conf.CELERY_QUEUES = queues
            try:
                return fun(*args, **kwargs)
            finally:
                app.conf.CELERY_QUEUES = prev_queues
        return __inner
    return patch_fun


a_queue = {"exchange": "fooexchange",
           "exchange_type": "fanout",
               "binding_key": "xuzzy"}
b_queue = {"exchange": "barexchange",
           "exchange_type": "topic",
           "binding_key": "b.b.#"}
d_queue = {"exchange": current_app.conf.CELERY_DEFAULT_EXCHANGE,
           "exchange_type": current_app.conf.CELERY_DEFAULT_EXCHANGE_TYPE,
           "routing_key": current_app.conf.CELERY_DEFAULT_ROUTING_KEY}


class test_MapRoute(unittest.TestCase):

    @with_queues(foo=a_queue, bar=b_queue)
    def test_route_for_task_expanded_route(self):
        expand = E(current_app.conf.CELERY_QUEUES)
        route = routes.MapRoute({"celery.ping": {"queue": "foo"}})
        self.assertDictContainsSubset(a_queue,
                             expand(route.route_for_task("celery.ping")))
        self.assertIsNone(route.route_for_task("celery.awesome"))

    @with_queues(foo=a_queue, bar=b_queue)
    def test_route_for_task(self):
        expand = E(current_app.conf.CELERY_QUEUES)
        route = routes.MapRoute({"celery.ping": b_queue})
        self.assertDictContainsSubset(b_queue,
                             expand(route.route_for_task("celery.ping")))
        self.assertIsNone(route.route_for_task("celery.awesome"))

    def test_expand_route_not_found(self):
        expand = E(current_app.conf.CELERY_QUEUES)
        route = routes.MapRoute({"a": {"queue": "x"}})
        self.assertRaises(QueueNotFound, expand, route.route_for_task("a"))


class test_lookup_route(unittest.TestCase):

    def test_init_queues(self):
        router = routes.Router(queues=None)
        self.assertDictEqual(router.queues, {})

    @with_queues(foo=a_queue, bar=b_queue)
    def test_lookup_takes_first(self):
        R = routes.prepare(({"celery.ping": {"queue": "bar"}},
                            {"celery.ping": {"queue": "foo"}}))
        router = routes.Router(R, current_app.conf.CELERY_QUEUES)
        self.assertDictContainsSubset(b_queue,
                router.route({}, "celery.ping",
                    args=[1, 2], kwargs={}))

    @with_queues()
    def test_expands_queue_in_options(self):
        R = routes.prepare(())
        router = routes.Router(R, current_app.conf.CELERY_QUEUES,
                               create_missing=True)
        # apply_async forwards all arguments, even exchange=None etc,
        # so need to make sure it's merged correctly.
        route = router.route({"queue": "testq",
                              "exchange": None,
                              "routing_key": None,
                              "immediate": False},
                             "celery.ping",
                             args=[1, 2], kwargs={})
        self.assertDictContainsSubset({"exchange": "testq",
                                       "routing_key": "testq",
                                       "immediate": False},
                                       route)
        self.assertIn("queue", route)

    @with_queues(foo=a_queue, bar=b_queue)
    def test_expand_destaintion_string(self):
        x = routes.Router({}, current_app.conf.CELERY_QUEUES)
        dest = x.expand_destination("foo")
        self.assertEqual(dest["exchange"], "fooexchange")

    @with_queues(foo=a_queue, bar=b_queue, **{
        current_app.conf.CELERY_DEFAULT_QUEUE: d_queue})
    def test_lookup_paths_traversed(self):
        R = routes.prepare(({"celery.xaza": {"queue": "bar"}},
                            {"celery.ping": {"queue": "foo"}}))
        router = routes.Router(R, current_app.conf.CELERY_QUEUES)
        self.assertDictContainsSubset(a_queue,
                router.route({}, "celery.ping",
                    args=[1, 2], kwargs={}))
        self.assertEqual(router.route({}, "celery.poza"),
                dict(d_queue, queue=current_app.conf.CELERY_DEFAULT_QUEUE))


class test_prepare(unittest.TestCase):

    def test_prepare(self):
        from celery.datastructures import LocalCache
        o = object()
        R = [{"foo": "bar"},
                  "celery.datastructures.LocalCache",
                  o]
        p = routes.prepare(R)
        self.assertIsInstance(p[0], routes.MapRoute)
        self.assertIsInstance(maybe_promise(p[1]), LocalCache)
        self.assertIs(p[2], o)

        self.assertEqual(routes.prepare(o), [o])

    def test_prepare_item_is_dict(self):
        R = {"foo": "bar"}
        p = routes.prepare(R)
        self.assertIsInstance(p[0], routes.MapRoute)
