# -*- coding: utf-8; -*-

from unittest import TestCase

from rattail.config import make_config

try:
    import sqlalchemy as sa
    from rattail import auth as mod
    from rattail.db import Session
    from rattail.db.auth import (set_user_password, administrator_role, 
                                 guest_role, authenticated_role)
except ImportError:
    pass
else:

    class TestAuthHandler(TestCase):

        def setUp(self):
            self.config = self.make_config()
            self.handler = self.make_handler()

        def make_config(self):
            return make_config([], extend=False)

        def make_handler(self):
            return mod.AuthHandler(self.config)

        def test_authenticate_user(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # should fail if there are no users!
            result = self.handler.authenticate_user(session, 'myuser', 'mypass')
            self.assertIsNone(result)

            # okay now add a user and make sure it does work
            myuser = model.User(username='myuser')
            set_user_password(myuser, 'mypass')
            session.add(myuser)
            result = self.handler.authenticate_user(session, 'myuser', 'mypass')
            self.assertIsInstance(result, model.User)
            self.assertEqual(result.username, 'myuser')

        def test_generate_preferred_username(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # default is just hard-coded
            result = self.handler.generate_preferred_username(session)
            self.assertEqual(result, 'newuser')

            # but if we specify a person then will return 'first.last'
            person = model.Person(first_name='Fred', last_name='Flintstone')
            result = self.handler.generate_preferred_username(session, person=person)
            self.assertEqual(result, 'fred.flintstone')

        def test_generate_unique_username(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # default is just hard-coded
            result = self.handler.generate_unique_username(session)
            self.assertEqual(result, 'newuser')

            # unless we make a user with that name, then it must use a counter
            user = model.User(username='newuser')
            session.add(user)
            result = self.handler.generate_unique_username(session)
            self.assertEqual(result, 'newuser01')

            # if we specify a person then will return 'first.last'
            person = model.Person(first_name='Fred', last_name='Flintstone')
            result = self.handler.generate_unique_username(session, person=person)
            self.assertEqual(result, 'fred.flintstone')

            # unless username is taken, in which case it must use a counter
            user = model.User(username='fred.flintstone')
            session.add(user)
            person = model.Person(first_name='Fred', last_name='Flintstone')
            result = self.handler.generate_unique_username(session, person=person)
            self.assertEqual(result, 'fred.flintstone01')

        def test_make_user(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # making a user with no info at all, gets hard-coded username
            user = self.handler.make_user()
            self.assertIsInstance(user, model.User)
            self.assertEqual(user.username, 'newuser')

            # or we can specify the username directly
            user = self.handler.make_user(username='foobar')
            self.assertIsInstance(user, model.User)
            self.assertEqual(user.username, 'foobar')

            # if we specify a person then username will be like 'first.last'
            person = model.Person(first_name='Fred', last_name='Flintstone')
            user = self.handler.make_user(session, person=person)
            self.assertEqual(user.username, 'fred.flintstone')

        def test_delete_user(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # make a user, then delete - it should work
            user = model.User(username='foobar')
            session.add(user)
            session.commit()
            self.assertIn(user, session)
            self.handler.delete_user(user)
            session.commit()
            self.assertNotIn(user, session)

        def test_has_permission(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # anonymous does not have any permission by default
            result = self.handler.has_permission(session, None, 'common.feedback')
            self.assertFalse(result)

            # make a role and user, but each should still not have permission
            role = model.Role(name='foobar')
            user = model.User(username='whatever')
            user.roles.append(role)
            session.add(user)
            result = self.handler.has_permission(session, role, 'common.feedback')
            self.assertFalse(result)
            result = self.handler.has_permission(session, user, 'common.feedback')
            self.assertFalse(result)

            # grant permission, then check again
            role.permissions.append('common.feedback')
            result = self.handler.has_permission(session, role, 'common.feedback')
            self.assertTrue(result)
            result = self.handler.has_permission(session, user, 'common.feedback')
            self.assertTrue(result)

        def test_get_permissions(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # admin does not have any permissions by default
            admin = administrator_role(session)
            result = self.handler.get_permissions(session, admin)
            self.assertEqual(result, set())

            # we can grant perm to guest, and then all can inherit
            guest = guest_role(session)
            guest.permissions.append('common.feedback')
            result = self.handler.get_permissions(session, None)
            self.assertEqual(result, set(['common.feedback']))

            # make a user, make sure it gets same perms
            user = model.User(username='betty')
            session.add(user)
            result = self.handler.get_permissions(session, user)
            self.assertEqual(result, set(['common.feedback']))

            # but it has no perms if we exclude guest when checking
            result = self.handler.get_permissions(session, user, include_guest=False)
            self.assertEqual(result, set())

            # grant perms to authenticated, make sure that works
            authd = authenticated_role(session)
            authd.permissions.append('common.consume_batch_id')
            result = self.handler.get_permissions(session, user, include_guest=False)
            self.assertEqual(result, set(['common.consume_batch_id']))

            # and user still does not have perms if we exclude authenticated
            result = self.handler.get_permissions(session, user, include_guest=False,
                                                  include_authenticated=False)
            self.assertEqual(result, set())

            # finally add user to new role, make sure all works
            role = model.Role(name='Site Admin')
            user.roles.append(role)
            role.permissions.append('common.change_app_theme')
            result = self.handler.get_permissions(session, user)
            self.assertEqual(result, set(['common.change_app_theme',
                                          'common.consume_batch_id', 
                                          'common.feedback']))

            # now let's set a node type and corresponding roles, grant some 
            # selective perms, then make sure all comes back okay
            self.config.setdefault('rattail', 'node_type', 'type1')
            type1_users = model.Role(name='Node Type 1 Users', node_type='type1')
            type2_users = model.Role(name='Node Type 2 Users', node_type='type2')
            user.roles.extend([type1_users, type2_users])
            type1_users.permissions.append('products.list')
            type2_users.permissions.append('customers.list')

            # our node is configured as type 1, so even though user belongs to
            # type2 role it should not inherit that permission on *this* node
            result = self.handler.get_permissions(session, user)
            self.assertEqual(result, set(['common.change_app_theme', 
                                          'common.consume_batch_id',
                                          'common.feedback',
                                          'products.list']))

        def test_grant_permission(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # make a role, grant a perm, confirm
            role = model.Role(name='FooBar')
            self.assertEqual(role.permissions, [])
            self.handler.grant_permission(role, 'perm01')
            self.assertEqual(role.permissions, ['perm01'])

            # make sure it doesn't double-add
            self.handler.grant_permission(role, 'perm01')
            self.assertEqual(role.permissions, ['perm01'])

        def test_revoke_permission(self):
            engine = sa.create_engine('sqlite://')
            model = self.config.get_model()
            model.Base.metadata.create_all(bind=engine)
            session = Session(bind=engine)

            # make a role, grant perms, then revoke one and check
            role = model.Role(name='FooBar')
            role.permissions.extend(['perm01', 'perm02'])
            self.handler.revoke_permission(role, 'perm01')
            self.assertEqual(role.permissions, ['perm02'])

            # make sure it doesn't try to somehow double-revoke
            self.handler.revoke_permission(role, 'perm01')
            self.assertEqual(role.permissions, ['perm02'])
