# coding=utf-8
"""
"""
from __future__ import absolute_import, division, print_function, \
    unicode_literals

import datetime
import sys

import mock
import pytz
from pytest import mark
from wtforms.form import Form

from abilian.core.entities import Entity
from abilian.services.security import READ, WRITE, Anonymous, Owner, Role
from abilian.testing import BaseTestCase

from . import FormPermissions, fields, filters


def user_tz():
    # This one is GMT+8 and has no DST (tests should pass any time in year)
    return 'Asia/Hong_Kong'


USER_TZ = pytz.timezone(user_tz())


# test filters
def test_strip():
    assert filters.strip(None) == ''
    assert filters.strip(4) == 4
    assert filters.strip(' a string ') == 'a string'
    assert filters.strip(' voilà ') == 'voilà'


def test_uppercase():
    assert filters.uppercase(None) is None
    assert filters.uppercase(4) == 4
    assert filters.uppercase(' a string ') == ' A STRING '
    assert filters.uppercase(' Voilà ') == ' VOILÀ '


def test_lowercase():
    assert filters.lowercase(None) is None
    assert filters.lowercase(4) == 4
    assert filters.lowercase(' A STRING ') == ' a string '
    assert filters.lowercase(' VOILÀ ') == ' voilà '


# FormPermissions
def test_form_permissions_controller():
    security_mock = mock.Mock()
    has_role = security_mock.has_role = mock.Mock()
    has_role.return_value = True
    current_app_mock = mock.Mock()
    current_app_mock.services = dict(security=security_mock)
    MarkRole = Role('tests:mark-role')
    _MARK = object()
    _ENTITY_MARK = Entity()

    with mock.patch('abilian.web.forms.current_app', current_app_mock):
        # default role
        fp = FormPermissions()
        assert fp.has_permission(READ)
        assert has_role.called
        assert has_role.call_args[-1]['role'] == [Anonymous]

        has_role.reset_mock()
        assert fp.has_permission(READ, obj=_MARK)
        assert not has_role.called

        has_role.reset_mock()
        assert fp.has_permission(READ, obj=_ENTITY_MARK)
        assert has_role.called
        assert has_role.call_args[-1]['object'] is _ENTITY_MARK

        # change default
        has_role.reset_mock()
        fp = FormPermissions(default=MarkRole)
        fp.has_permission(READ)
        assert has_role.call_args[-1]['role'] == [MarkRole]

        has_role.reset_mock()
        fp.has_permission(READ, field='test')
        assert has_role.call_args[-1]['role'] == [MarkRole]

        has_role.reset_mock()
        fp = FormPermissions(default=MarkRole, read=Anonymous)
        fp.has_permission(READ)
        assert has_role.call_args[-1]['role'] == [Anonymous]

        has_role.reset_mock()
        fp.has_permission(READ, field='test')
        assert has_role.call_args[-1]['role'] == [MarkRole]

        has_role.reset_mock()
        fp.has_permission(WRITE)
        assert has_role.call_args[-1]['role'] == [MarkRole]

        # field roles
        has_role.reset_mock()
        fp = FormPermissions(
            default=MarkRole,
            read=Anonymous,
            fields_read={'test': Owner},
        )
        fp.has_permission(READ)
        assert has_role.call_args[-1]['role'] == [Anonymous]

        has_role.reset_mock()
        fp.has_permission(READ, field='test')
        assert has_role.call_args[-1]['role'] == [Owner]

        has_role.reset_mock()
        fp.has_permission(READ, field='test')
        assert has_role.call_args[-1]['role'] == [Owner]

        # dynamic roles
        has_role.reset_mock()
        dyn_roles = mock.Mock()
        dyn_roles.return_value = [MarkRole]
        fp = FormPermissions(read=dyn_roles)
        fp.has_permission(READ)
        assert dyn_roles.call_args == [
            dict(permission=READ, field=None, obj=None),
        ]
        assert has_role.call_args[-1]['role'] == [MarkRole]

        has_role.reset_mock()
        dyn_roles.reset_mock()
        fp = FormPermissions(read=[Owner, dyn_roles])
        fp.has_permission(READ)
        assert dyn_roles.call_args == [
            dict(permission=READ, field=None, obj=None),
        ]
        assert has_role.call_args[-1]['role'] == [Owner, MarkRole]


class FieldsTestCase(BaseTestCase):

    def create_app(self):
        app = super(FieldsTestCase, self).create_app()
        app.extensions['babel'].timezone_selector_func = None
        app.extensions['babel'].timezoneselector(user_tz)
        return app

    @mark.skipif(sys.version_info >= (3, 0), reason="Doesn't work yet on Py3k")
    def test_datetime_field(self):
        """
        Test fields supports date with year < 1900
        """
        obj = mock.Mock()

        headers = {'Accept-Language': 'fr-FR,fr;q=0.8'}
        with self.app.test_request_context(headers=headers):
            f = fields.DateTimeField(use_naive=False).bind(Form(), 'dt')
            f.process_formdata(['17/06/1789 | 10:42'])
            # 1789: applied offset for HongKong is equal to LMT+7:37:00,
            # thus we compare with tzinfo=user_tz
            assert f.data == datetime.datetime(
                1789,
                6,
                17,
                10,
                42,
                tzinfo=USER_TZ,
            )
            # UTC stored
            assert f.data.tzinfo is pytz.UTC
            # displayed in user current timezone
            assert f._value() == '17/06/1789 10:42'

            # non-naive mode: test process_data change TZ to user's TZ
            f.process_data(f.data)
            assert f.data.tzinfo is USER_TZ
            assert f.data == datetime.datetime(
                1789,
                6,
                17,
                10,
                42,
                tzinfo=USER_TZ,
            )

            f.populate_obj(obj, 'dt')
            assert obj.dt == datetime.datetime(
                1789,
                6,
                17,
                10,
                42,
                tzinfo=USER_TZ,
            )

            # test more recent date: offset is GMT+8
            f.process_formdata(['23/01/2011 | 10:42'])
            assert f.data == datetime.datetime(
                2011,
                1,
                23,
                2,
                42,
                tzinfo=pytz.utc,
            )

            # NAIVE mode: dates without timezone. Those are the problematic ones when
            # year < 1900: strptime will raise an Exception use naive dates; by
            # default
            f = fields.DateTimeField().bind(Form(), 'dt')
            f.process_formdata(['17/06/1789 | 10:42'])
            # UTC stored
            assert f.data.tzinfo is pytz.UTC
            assert f.data == datetime.datetime(
                1789,
                6,
                17,
                10,
                42,
                tzinfo=pytz.UTC,
            )

            # naive stored
            f.populate_obj(obj, 'dt')
            assert obj.dt == datetime.datetime(1789, 6, 17, 10, 42)

    @mark.skipif(sys.version_info >= (3, 0), reason="Doesn't work yet on Py3k")
    def test_datetimefield_force_4digit_year(self):
        # use 'en': short date pattern is 'M/d/yy'
        headers = {'Accept-Language': 'en'}
        with self.app.test_request_context(headers=headers):
            f = fields.DateTimeField().bind(Form(), 'dt')
            f.data = datetime.datetime(2011, 1, 23, 10, 42, tzinfo=pytz.utc)
            assert f._value() == '1/23/2011, 6:42 PM'

    def test_date_field(self):
        """
        Test fields supports date with year < 1900
        """
        headers = {'Accept-Language': 'fr-FR,fr;q=0.8'}
        with self.app.test_request_context(headers=headers):
            f = fields.DateField().bind(Form(), 'dt')
            f.process_formdata(['17/06/1789'])
            assert f.data == datetime.date(1789, 6, 17)
            assert f._value() == '17/06/1789'

    def test_datefield_force_4digit_year(self):
        # use 'en': short date pattern is 'M/d/yy'
        headers = {'Accept-Language': 'en'}
        with self.app.test_request_context(headers=headers):
            f = fields.DateField().bind(Form(), 'dt')
            f.data = datetime.date(2011, 1, 23)
            assert f._value() == '1/23/2011'
