# -*- coding: utf-8 -*-
# Part of  Talisman . See LICENSE file for full copyright and licensing details.

from collections import OrderedDict
from itertools import chain
from lxml import etree

from odoo.addons.hr.tests.common import TestHrCommon
from odoo.tests import new_test_user, tagged, Form
from odoo.exceptions import AccessError

@tagged('post_install', '-at_install')
class TestSelfAccessProfile(TestHrCommon):

    def test_access_my_profile(self):
        """ A simple user should be able to read all fields in his profile """
        james = new_test_user(self.env, login='hel', groups='base.group_user', name='Simple employee', email='ric@example.com')
        james = james.with_user(james)
        self.env['hr.employee'].create({
            'name': 'James',
            'user_id': james.id,
        })
        view = self.env.ref('hr.res_users_view_form_profile')
        view_infos = james.get_view(view.id)
        fields = [el.get('name') for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')]
        james.read(fields)

    def test_readonly_fields(self):
        """ Employee related fields should be readonly if self editing is not allowed """
        self.env['ir.config_parameter'].sudo().set_param('hr.hr_employee_self_edit', False)
        james = new_test_user(self.env, login='hel', groups='base.group_user', name='Simple employee', email='ric@example.com')
        james = james.with_user(james)
        self.env['hr.employee'].create({
            'name': 'James',
            'user_id': james.id,
        })

        view = self.env.ref('hr.res_users_view_form_profile')
        fields = james._fields
        view_infos = james.get_view(view.id)
        employee_related_fields = {
            el.get('name')
            for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')
            if fields[el.get('name')].related and fields[el.get('name')].related.split('.')[0] == 'employee_id'
        }

        form = Form(james, view=view)
        for field in employee_related_fields:
            with self.assertRaises(AssertionError, msg="Field '%s' should be readonly in the employee profile when self editing is not allowed." % field):
                form.__setattr__(field, 'some value')


    def test_profile_view_fields(self):
        """ A simple user should see all fields in profile view, even if they are protected by groups """
        view = self.env.ref('hr.res_users_view_form_profile')

        # For reference, check the view with user with every groups protecting user fields
        all_groups_xml_ids = chain(*[
            field.groups.split(',')
            for field in self.env['res.users']._fields.values()
            if field.groups
            if field.groups != '.' # "no-access" group on purpose
        ])
        all_groups = self.env['res.groups']
        for xml_id in all_groups_xml_ids:
            all_groups |= self.env.ref(xml_id.strip())
        user_all_groups = new_test_user(self.env, groups='base.group_user', login='hel', name='God')
        user_all_groups.write({'groups_id': [(4, group.id, False) for group in all_groups]})
        view_infos = self.env['res.users'].with_user(user_all_groups).get_view(view.id)
        full_fields = [el.get('name') for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')]

        # Now check the view for a simple user
        user = new_test_user(self.env, login='gro', name='Grouillot')
        view_infos = self.env['res.users'].with_user(user).get_view(view.id)
        fields = [el.get('name') for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')]

        # Compare both
        self.assertEqual(full_fields, fields, "View fields should not depend on user's groups")

    def test_access_my_profile_toolbar(self):
        """ A simple user shouldn't have the possibilities to see the 'Change Password' action"""
        james = new_test_user(self.env, login='jam', groups='base.group_user', name='Simple employee', email='jam@example.com')
        james = james.with_user(james)
        self.env['hr.employee'].create({
            'name': 'James',
            'user_id': james.id,
        })
        view = self.env.ref('hr.res_users_view_form_profile')
        available_actions = james.get_views([(view.id, 'form')], {'toolbar': True})['views']['form']['toolbar']['action']
        change_password_action = self.env.ref("base.change_password_wizard_action")

        self.assertFalse(any(x['id'] == change_password_action.id for x in available_actions))

        # An ERP manager should have the possibilities to see the 'Change Password'
        john = new_test_user(self.env, login='joh', groups='base.group_erp_manager', name='ERP Manager', email='joh@example.com')
        john = john.with_user(john)
        self.env['hr.employee'].create({
            'name': 'John',
            'user_id': john.id,
        })
        view = self.env.ref('hr.res_users_view_form_profile')
        available_actions = john.get_views([(view.id, 'form')], {'toolbar': True})['views']['form']['toolbar']['action']
        self.assertTrue(any(x['id'] == change_password_action.id for x in available_actions))


class TestSelfAccessRights(TestHrCommon):

    @classmethod
    def setUpClass(cls):
        super(TestSelfAccessRights, cls).setUpClass()
        cls.richard = new_test_user(cls.env, login='ric', groups='base.group_user', name='Simple employee', email='ric@example.com')
        cls.richard_emp = cls.env['hr.employee'].create({
            'name': 'Richard',
            'user_id': cls.richard.id,
            'private_phone': '21454',
        })
        cls.hubert = new_test_user(cls.env, login='hub', groups='base.group_user', name='Simple employee', email='hub@example.com')
        cls.hubert_emp = cls.env['hr.employee'].create({
            'name': 'Hubert',
            'user_id': cls.hubert.id,
        })

        cls.protected_fields_emp = OrderedDict([(k, v) for k, v in cls.env['hr.employee']._fields.items() if v.groups == 'hr.group_hr_user'])
        # Compute fields and id field are always readable by everyone
        cls.read_protected_fields_emp = OrderedDict([(k, v) for k, v in cls.env['hr.employee']._fields.items() if not v.compute and k != 'id'])
        cls.self_protected_fields_user = OrderedDict([
            (k, v)
            for k, v in cls.env['res.users']._fields.items()
            if v.groups == 'hr.group_hr_user' and k in cls.env['res.users'].SELF_READABLE_FIELDS
        ])

    # Read hr.employee #
    def testReadSelfEmployee(self):
        with self.assertRaises(AccessError):
            self.hubert_emp.with_user(self.richard).read(self.protected_fields_emp.keys())

    def testReadOtherEmployee(self):
        with self.assertRaises(AccessError):
            self.hubert_emp.with_user(self.richard).read(self.protected_fields_emp.keys())

    # Write hr.employee #
    def testWriteSelfEmployee(self):
        for f in self.protected_fields_emp:
            with self.assertRaises(AccessError):
                self.richard_emp.with_user(self.richard).write({f: 'dummy'})

    def testWriteOtherEmployee(self):
        for f in self.protected_fields_emp:
            with self.assertRaises(AccessError):
                self.hubert_emp.with_user(self.richard).write({f: 'dummy'})

    # Read res.users #
    def testReadSelfUserEmployee(self):
        for f in self.self_protected_fields_user:
            self.richard.with_user(self.richard).read([f])  # should not raise

    def testReadOtherUserEmployee(self):
        with self.assertRaises(AccessError):
            self.hubert.with_user(self.richard).read(self.self_protected_fields_user)

    # Write res.users #
    def testWriteSelfUserEmployeeSettingFalse(self):
        for f, v in self.self_protected_fields_user.items():
            with self.assertRaises(AccessError):
                self.richard.with_user(self.richard).write({f: 'dummy'})

    def testWriteSelfUserEmployee(self):
        self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', True)
        for f, v in self.self_protected_fields_user.items():
            val = None
            if v.type == 'char' or v.type == 'text':
                val = '0000' if f == 'pin' else 'dummy'
            if val is not None:
                self.richard.with_user(self.richard).write({f: val})

    def testWriteSelfUserPreferencesEmployee(self):
        # self should always be able to update non hr.employee fields if
        # they are in SELF_READABLE_FIELDS
        self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
        # should not raise
        vals = [
            {'tz': "Australia/Sydney"},
            {'email': "new@example.com"},
            {'signature': "<p>I'm Richard!</p>"},
            {'notification_type': "email"},
        ]
        for v in vals:
            # should not raise
            self.richard.with_user(self.richard).write(v)

    def testWriteOtherUserPreferencesEmployee(self):
        # self should always be able to update non hr.employee fields if
        # they are in SELF_READABLE_FIELDS
        self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
        vals = [
            {'tz': "Australia/Sydney"},
            {'email': "new@example.com"},
            {'signature': "<p>I'm Richard!</p>"},
            {'notification_type': "email"},
        ]
        for v in vals:
            with self.assertRaises(AccessError):
                self.hubert.with_user(self.richard).write(v)

    def testWriteSelfPhoneEmployee(self):
        # phone is a related from res.partner (from base) but added in SELF_READABLE_FIELDS
        self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
        with self.assertRaises(AccessError):
            self.richard.with_user(self.richard).write({'phone': '2154545'})

    def testWriteOtherUserEmployee(self):
        for f in self.self_protected_fields_user:
            with self.assertRaises(AccessError):
                self.hubert.with_user(self.richard).write({f: 'dummy'})

    def testSearchUserEMployee(self):
        # Searching user based on employee_id field should not raise bad query error
        self.env['res.users'].with_user(self.richard).search([('employee_id', 'ilike', 'Hubert')])

    def test_onchange_readable_fields_with_no_access(self):
        """
            The purpose is to test that the onchange logic takes into account `SELF_READABLE_FIELDS`.

            The view contains fields that are in `SELF_READABLE_FIELDS` (example: `private_street`).
            Even if the user does not have read access to the employee,
            it should not cause an access error if these fields are in `SELF_READABLE_FIELDS`.
        """
        self.env['res.lang']._activate_lang("fr_FR")
        with Form(self.richard.with_user(self.richard), view='hr.res_users_view_form_profile') as form:
            # triggering an onchange should not trigger some access error
            form.lang = "fr_FR"
            form.tz = "Europe/Brussels"
