# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase

from odoo.tests import tagged
from odoo.tests.common import new_test_user


@tagged("post_install", "-at_install")
class IrModelAccessTest(TransactionCase):

    @classmethod
    def setUpClass(cls):
        super(IrModelAccessTest, cls).setUpClass()

        cls.env['ir.model.access'].create({
            'name': "read",
            'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
            'group_id': cls.env.ref("base.group_public").id,
            'perm_read': False,
        })

        cls.env['ir.model.access'].create({
            'name': "read",
            'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
            'group_id': cls.env.ref("base.group_portal").id,
            'perm_read': True,
        })

        cls.env['ir.model.access'].create({
            'name': "read",
            'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
            'group_id': cls.env.ref("base.group_user").id,
            'perm_read': True,
        })

        cls.portal_user = new_test_user(
            cls.env, login="portalDude", groups="base.group_portal"
        )
        cls.public_user = new_test_user(
            cls.env, login="publicDude", groups="base.group_public"
        )
        cls.spreadsheet_user = new_test_user(
            cls.env, login="spreadsheetDude", groups="base.group_user"
        )

    def test_display_name_for(self):
        # Internal User with access rights can access the business name
        result = self.env['ir.model'].with_user(self.spreadsheet_user).display_name_for(["res.company"])
        self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}])
        # external user with access rights cannot access business name
        result = self.env['ir.model'].with_user(self.portal_user).display_name_for(["res.company"])
        self.assertEqual(result, [{"display_name": "res.company", "model": "res.company"}])
        # external user without access rights cannot access business name
        result = self.env['ir.model'].with_user(self.public_user).display_name_for(["res.company"])
        self.assertEqual(result, [{"display_name": "res.company", "model": "res.company"}])
        # admin has all rights
        result = self.env['ir.model'].display_name_for(["res.company"])
        self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}])
        # non existent model yields same result as a lack of access rights
        result = self.env['ir.model'].display_name_for(["unexistent"])
        self.assertEqual(result, [{"display_name": "unexistent", "model": "unexistent"}])
        # non existent model comes after existent model
        result = self.env['ir.model'].display_name_for(["res.company", "unexistent"])
        self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}, {"display_name": "unexistent", "model": "unexistent"}])
        # transient models
        result = self.env['ir.model'].display_name_for(["res.company", "base.language.export"])
        self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}, {"display_name": "base.language.export", "model": "base.language.export"}])

        # do not return results for transient models
        result = self.env['ir.model'].get_available_models()
        result = {values["model"] for values in result}
        self.assertIn("res.company", result)
        self.assertNotIn("base.language.export", result)


class TestIrModel(TransactionCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        # The test mode is necessary in this case.  After each test, we call
        # registry.reset_changes(), which opens a new cursor to retrieve custom
        # models and fields.  A regular cursor would correspond to the state of
        # the database before setUpClass(), which is not correct.  Instead, a
        # test cursor will correspond to the state of the database of cls.cr at
        # that point, i.e., before the call to setUp().
        cls.registry_enter_test_mode_cls()

        # model and records for banana stages
        cls.env['ir.model'].create({
            'name': 'Banana Ripeness',
            'model': 'x_banana_ripeness',
            'field_id': [
                Command.create({'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}),
            ]
        })
        # stage values are pairs (id, display_name)
        cls.ripeness_green = cls.env['x_banana_ripeness'].name_create('Green')
        cls.ripeness_okay = cls.env['x_banana_ripeness'].name_create('Okay, I guess?')
        cls.ripeness_gone = cls.env['x_banana_ripeness'].name_create('Walked away on its own')

        # model and records for bananas
        cls.bananas_model = cls.env['ir.model'].create({
            'name': 'Bananas',
            'model': 'x_bananas',
            'field_id': [
                Command.create({'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}),
                Command.create({'name': 'x_length', 'ttype': 'float', 'field_description': 'Length'}),
                Command.create({'name': 'x_color', 'ttype': 'integer', 'field_description': 'Color'}),
                Command.create({'name': 'x_ripeness_id', 'ttype': 'many2one',
                        'field_description': 'Ripeness', 'relation': 'x_banana_ripeness',
                        'group_expand': True}),
            ]
        })
        # add non-stored field that is not valid in order
        cls.env['ir.model.fields'].create({
            'name': 'x_is_yellow',
            'field_description': 'Is the banana yellow?',
            'ttype': 'boolean',
            'model_id': cls.bananas_model.id,
            'store': False,
            'depends': 'x_color',
            'compute': "for banana in self:\n    banana['x_is_yellow'] = banana.x_color == 9"
        })
        # default stage is ripeness_green
        cls.env['ir.default'].set('x_bananas', 'x_ripeness_id', cls.ripeness_green[0])
        cls.env['x_bananas'].create([{
            'x_name': 'Banana #1',
            'x_length': 3.14159,
            'x_color': 9,
        }, {
            'x_name': 'Banana #2',
            'x_length': 0,
            'x_color': 6,
        }, {
            'x_name': 'Banana #3',
            'x_length': 10,
            'x_color': 6,
        }])

    def setUp(self):
        # this cleanup is necessary after each test, and must be done last
        self.addCleanup(self.registry.reset_changes)
        super().setUp()

    def test_model_order_constraint(self):
        """Check that the order constraint is properly enforced."""
        VALID_ORDERS = ['id', 'id desc', 'id asc, x_length', 'x_color, x_length, create_uid']
        for order in VALID_ORDERS:
            self.bananas_model.order = order

        INVALID_ORDERS = ['', 'x_wat', 'id esc', 'create_uid,', 'id, x_is_yellow']
        for order in INVALID_ORDERS:
            with self.assertRaises(ValidationError):
                self.bananas_model.order = order

        # check that the constraint is checked at model creation
        fields_value = [
            Command.create({'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}),
            Command.create({'name': 'x_length', 'ttype': 'float', 'field_description': 'Length'}),
            Command.create({'name': 'x_color', 'ttype': 'integer', 'field_description': 'Color'}),
        ]
        self.env['ir.model'].create({
            'name': 'MegaBananas',
            'model': 'x_mega_bananas',
            'order': 'x_name asc, id desc',         # valid order
            'field_id': fields_value,
        })
        with self.assertRaises(ValidationError):
            self.env['ir.model'].create({
                'name': 'GigaBananas',
                'model': 'x_giga_bananas',
                'order': 'x_name asc, x_wat',       # invalid order
                'field_id': fields_value,
            })

        # ensure we can order by a stored field via inherits
        user_model = self.env['ir.model'].search([('model', '=', 'res.users')])
        user_model._check_order()  # must not raise

    def test_model_order_search(self):
        """Check that custom orders are applied when querying a model."""
        ORDERS = {
            'id asc': ['Banana #1', 'Banana #2', 'Banana #3'],
            'id desc': ['Banana #3', 'Banana #2', 'Banana #1'],
            'x_color asc, id asc': ['Banana #2', 'Banana #3', 'Banana #1'],
            'x_color asc, id desc': ['Banana #3', 'Banana #2', 'Banana #1'],
            'x_length asc, id': ['Banana #2', 'Banana #1', 'Banana #3'],
        }
        for order, names in ORDERS.items():
            self.bananas_model.order = order
            self.assertEqual(self.env['x_bananas']._order, order)

            bananas = self.env['x_bananas'].search([])
            self.assertEqual(bananas.mapped('x_name'), names, 'failed to order by %s' % order)

    def test_model_fold_search(self):
        """Check that custom orders are applied when querying a model."""
        self.assertEqual(self.bananas_model.fold_name, False)
        self.assertEqual(self.env['x_bananas']._fold_name, None)

        self.bananas_model.fold_name = 'x_name'
        self.assertEqual(self.env['x_bananas']._fold_name, 'x_name')

    def test_group_expansion(self):
        """Check that the basic custom group expansion works."""
        model = self.env['x_bananas'].with_context(read_group_expand=True)
        groups = model.formatted_read_group([], ['x_ripeness_id'], ['__count'])
        expected = [{
            'x_ripeness_id': self.ripeness_green,
            '__count': 3,
            '__extra_domain': [('x_ripeness_id', '=', self.ripeness_green[0])],
        }, {
            'x_ripeness_id': self.ripeness_okay,
            '__count': 0,
            '__extra_domain': [('x_ripeness_id', '=', self.ripeness_okay[0])],
        }, {
            'x_ripeness_id': self.ripeness_gone,
            '__count': 0,
            '__extra_domain': [('x_ripeness_id', '=', self.ripeness_gone[0])],
        }]
        self.assertEqual(groups, expected, 'should include 2 empty ripeness stages')

    def test_rec_name_deletion(self):
        """Check that deleting 'x_name' does not crash."""
        record = self.env['x_bananas'].create({'x_name': "Ifan Ben-Mezd"})
        self.assertEqual(record._rec_name, 'x_name')
        ClassRecord = self.registry[record._name]
        self.assertEqual(self.registry.field_depends[ClassRecord.display_name], ('x_name',))
        self.assertEqual(record.display_name, "Ifan Ben-Mezd")

        # unlinking x_name should fixup _rec_name and display_name
        self.env['ir.model.fields']._get('x_bananas', 'x_name').unlink()
        record = self.env['x_bananas'].browse(record.id)
        self.assertEqual(record._rec_name, None)
        self.assertEqual(self.registry.field_depends[ClassRecord.display_name], ())
        self.assertEqual(record.display_name, f"x_bananas,{record.id}")

    def test_monetary_currency_field(self):
        fields_value = [
            Command.create({'name': 'x_monetary', 'ttype': 'monetary', 'field_description': 'Monetary', 'currency_field': 'test'}),
        ]
        with self.assertRaises(ValidationError):
            self.env['ir.model'].create({
                'name': 'Paper Company Model',
                'model': 'x_paper_model',
                'field_id': fields_value,
            })

        fields_value = [
            Command.create({'name': 'x_monetary', 'ttype': 'monetary', 'field_description': 'Monetary', 'currency_field': 'x_falsy_currency'}),
            Command.create({'name': 'x_falsy_currency', 'ttype': 'one2many', 'field_description': 'Currency', 'relation': 'res.currency'}),
        ]
        with self.assertRaises(ValidationError):
            self.env['ir.model'].create({
                'name': 'Paper Company Model',
                'model': 'x_paper_model',
                'field_id': fields_value,
            })

        fields_value = [
            Command.create({'name': 'x_monetary', 'ttype': 'monetary', 'field_description': 'Monetary', 'currency_field': 'x_falsy_currency'}),
            Command.create({'name': 'x_falsy_currency', 'ttype': 'many2one', 'field_description': 'Currency', 'relation': 'res.partner'}),
        ]
        with self.assertRaises(ValidationError):
            self.env['ir.model'].create({
                'name': 'Paper Company Model',
                'model': 'x_paper_model',
                'field_id': fields_value,
            })

        fields_value = [
            Command.create({'name': 'x_monetary', 'ttype': 'monetary', 'field_description': 'Monetary', 'currency_field': 'x_good_currency'}),
            Command.create({'name': 'x_good_currency', 'ttype': 'many2one', 'field_description': 'Currency', 'relation': 'res.currency'}),
        ]
        model = self.env['ir.model'].create({
            'name': 'Paper Company Model',
            'model': 'x_paper_model',
            'field_id': fields_value,
        })
        monetary_field = model.field_id.search([['name', 'ilike', 'x_monetary']])
        self.assertEqual(len(monetary_field), 1,
                         "Should have the monetary field in the created ir.model")
        self.assertEqual(monetary_field.currency_field, "x_good_currency",
                         "The currency field in monetary should have x_good_currency as name")

    def test_invalid_field_domain(self):
        """Ensure assigning an invalid domain raises ValidationError."""
        field_ripeness_id = self.env['ir.model.fields']._get('x_bananas', 'x_ripeness_id')

        valid_domain = "[('x_name', '=', 'Green')]"
        invalid_domain = "[('x_name', '=', Green)]"  # Green without quotes

        field_ripeness_id.domain = valid_domain

        with self.assertRaises(ValidationError) as error:
            field_ripeness_id.domain = invalid_domain

        self.assertIn('An error occurred while evaluating the domain', str(error.exception))
        self.assertEqual(field_ripeness_id.domain, valid_domain)
