# Copyright: Copyright 2011-2017 by Luc Saffre.
# License: BSD, see LICENSE for more details.

"""
Two TestCase classes for writing tests be run using Django's test
runner (i.e. `manage.py test`).
"""

from __future__ import print_function

import logging
logger = logging.getLogger(__name__)

from django.conf import settings
from django.test import TestCase as DjangoTestCase
from django.test import Client
from django.db import connection, reset_queries
from django.utils import translation

import json
from atelier.utils import AttrDict
from lino.core.signals import testcase_setup # , database_ready

from .test import CommonTestCase


class DjangoManageTestCase(DjangoTestCase, CommonTestCase):
    """
    Adds some extensions to the Django TestCase.
    """

    longMessage = True  # see unittest. used for check_json_result

    override_djangosite_settings = dict()
    """If specified, this is a dict of :class:`Site<lino.core.site.Site>`
    attributes to override before running the test.

    """

    defining_module = None
    """When you decorate your subclass of TestCase, you must also specify::
    
        defining_module = __name__

    Because a decorator will change your class's `__module__`
    attribute and :meth:`test_them_all` would search for test methods
    in the wrong module.

    """

    def __call__(self, *args, **kw):
        """Does some initialization and sends the :attr:`testcase_setup
        <lino.utils.testcase_setup>` signal, then calls super.

        """
        if self.override_djangosite_settings:
            settings.SITE.override_defaults(
                **self.override_djangosite_settings)
        # Make sure that every test runs with the same language.
        # Without this it is possible that some other language may
        # have been activated by previous tests:
        if settings.LANGUAGE_CODE:
            translation.activate(settings.LANGUAGE_CODE)
        testcase_setup.send(self)
        return super(DjangoManageTestCase, self).__call__(*args, **kw)

    def tearDown(self):
        super(DjangoManageTestCase, self).tearDown()

    def setUp(self):
        super(DjangoManageTestCase, self).setUp()
        # 20151203 database_ready.send(self)

    def check_sql_queries(self, *expected):
        """Checks whether the specified expected SQL queries match to those
        who actually have been emitted.

        """
        for i, x1 in enumerate(expected):
            if len(connection.queries) <= i:
                self.fail("SQL %d expected %s, found nothing" % (i, x1))
            sql = connection.queries[i]['sql'].strip()
            x2 = x1.split('[...]')
            if len(x2) == 2:
                s = x2.pop().strip()
                if not sql.endswith(s):
                    self.fail("SQL %d doesn't end with %s:---\n%s\n---" %
                              (i, s, sql))

            self.assertEqual(len(x2), 1)
            s = x2[0].strip()
            if not sql.startswith(s):
                self.fail("SQL %d doesn't start with %s:---\n%s\n---" %
                          (i, s, sql))
        if len(expected) < len(connection.queries):
            for q in connection.queries[len(expected):]:
                logger.warning("Unexpected SQL:---\n%s\n---", q['sql'])
            self.fail("Found unexpected SQL")
        reset_queries()

    def get_json_dict(self, *args, **kwargs):
        return self.client_json_dict(self.client.get, *args, **kwargs)
    
    def post_json_dict(self, *args, **kwargs):
        return self.client_json_dict(self.client.post, *args, **kwargs)
    
    def put_json_dict(self, *args, **kwargs):
        return self.client_json_dict(self.client.put, *args, **kwargs)
    
    def client_json_dict(self, meth, username, url, *data, **extra):
        """Send a POST or PUT to client with given username, url and data. The
        client is expected to respond with a JSON encoded
        response. Parse the response's content (which is expected to
        contain a dict), convert this dict to an AttrDict before
        returning it.

        """
        ar = settings.SITE.login(username)
        self.client.force_login(ar.user)
        extra[settings.SITE.remote_user_header] = username
        # extra.update(REMOTE_USER=username)
        # print(20170609, settings.MIDDLEWARE_CLASSES)
        # print(20170609, settings.AUTHENTICATION_BACKENDS)
        res = meth(url, *data, **extra)
        if res.status_code != 200:
            raise Exception("{} gave status code {} instead of 200".format(
                url, res.status_code))