# Copyright 2018-19 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright (c) 2022 brain-tec AG (https://braintec.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from unittest import mock

from lxml import etree

from odoo.exceptions import ValidationError
from odoo.tests import Form
from odoo.tests.common import tagged

from ..models.tier_validation import BASE_EXCEPTION_FIELDS as BEF
from ..models.tier_validation import TierValidation as TV
from .common import CommonTierValidation


@tagged("post_install", "-at_install")
class TierTierValidation(CommonTierValidation):
    def test_01_auto_validation(self):
        """When the user can validate all future reviews, it is not needed
        to request a validation, the action can be done straight forward."""
        self.test_record.with_user(self.test_user_1.id).action_confirm()
        self.assertEqual(self.test_record.state, "confirmed")

    def test_02_no_auto_validation(self):
        """User with no right to validate future reviews must request a
        validation."""
        with self.assertRaises(ValidationError):
            self.test_record.with_user(self.test_user_2.id).action_confirm()

    def test_03_request_validation_approved(self):
        """User 2 request a validation and user 1 approves it."""
        self.assertFalse(self.test_record.review_ids)
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        record = self.test_record.with_user(self.test_user_1.id)
        record.validate_tier()
        self.assertEqual(record.validation_status, "validated")

    def test_04_request_validation_rejected(self):
        """Request validation, rejection and reset."""
        self.assertFalse(self.test_record.review_ids)
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        record = self.test_record.with_user(self.test_user_1.id)
        record.reject_tier()
        self.assertTrue(record.review_ids)
        self.assertEqual(record.validation_status, "rejected")
        record.restart_validation()
        self.assertFalse(record.review_ids)

    def test_05_under_validation(self):
        """Write is forbidden in a record under validation."""
        self.assertFalse(self.test_record.review_ids)
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        record = self.test_record.with_user(self.test_user_1.id)
        with self.assertRaises(ValidationError):
            record.write({"test_field": 0.5})

    def test_06_validation_process_open(self):
        """Operation forbidden while a validation process is open."""
        self.assertFalse(self.test_record.review_ids)
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        record = self.test_record.with_user(self.test_user_1.id)
        with self.assertRaises(ValidationError):
            record.action_confirm()

    def test_07_search_reviewers(self):
        """Test search methods."""
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        record = self.test_record.with_user(self.test_user_1.id)
        self.assertIn(self.test_user_1, record.reviewer_ids)
        res = self.test_model.search([("reviewer_ids", "in", self.test_user_1.id)])
        self.assertTrue(res)

    def test_10_systray_counter(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions for both tester models
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
            }
        )
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
            }
        )
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model_2.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
            }
        )
        # Request validation
        self.test_record.with_user(self.test_user_2.id).request_validation()
        test_record.with_user(self.test_user_2.id).request_validation()
        self.test_record_2.with_user(self.test_user_2.id).request_validation()
        # Get review user count as systray icon would do and check count value
        docs = self.test_user_1.with_user(self.test_user_1).review_user_count()
        self.assertEqual(
            next(filter(lambda doc: doc["model"] == "tier.validation.tester", docs))[
                "pending_count"
            ],
            2,
        )
        self.assertEqual(
            next(filter(lambda doc: doc["model"] == "tier.validation.tester2", docs))[
                "pending_count"
            ],
            1,
        )

    def test_11_add_comment(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "has_comment": True,
            }
        )
        # Request validation
        review = test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(review)
        # Let _compute_can_review assign status 'pending' instead of waiting
        review.flush_recordset()
        record = test_record.with_user(self.test_user_1.id)
        res = record.validate_tier()
        ctx = res.get("context")
        wizard = Form(self.env["comment.wizard"].with_context(**ctx))
        wizard.comment = "Test Comment"
        wiz = wizard.save()
        wiz.add_comment()
        self.assertTrue(test_record.review_ids.filtered("comment"))
        # Check notify
        comment = test_record.with_user(
            self.test_user_1.id
        )._notify_accepted_reviews_body()
        self.assertEqual(comment, "A review was accepted. (Test Comment)")
        comment = test_record.with_user(
            self.test_user_1.id
        )._notify_rejected_review_body()
        self.assertEqual(comment, "A review was rejected by John. (Test Comment)")

    def test_11_add_comment_rejection(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "has_comment": True,
            }
        )
        # Request validation
        review = test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(review)
        record = test_record.with_user(self.test_user_1.id)
        res = record.reject_tier()  # Rejection
        ctx = res.get("context")
        wizard = Form(self.env["comment.wizard"].with_context(**ctx))
        wizard.comment = "Test Comment"
        wiz = wizard.save()
        wiz.add_comment()
        self.assertTrue(test_record.review_ids.filtered("comment"))
        # Check notify
        comment = test_record.with_user(
            self.test_user_1.id
        )._notify_accepted_reviews_body()
        self.assertEqual(comment, "A review was accepted. (Test Comment)")
        comment = test_record.with_user(
            self.test_user_1.id
        )._notify_rejected_review_body()
        self.assertEqual(comment, "A review was rejected by John. (Test Comment)")

    def test_12_approve_sequence(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "sequence": 30,
            }
        )
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_2.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "sequence": 10,
            }
        )
        # Request validation
        self.assertFalse(self.test_record.review_ids)
        reviews = test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)

        docs1 = self.test_user_2.with_user(self.test_user_1).review_user_count()
        for doc in docs1:
            self.assertEqual(doc.get("pending_count"), 1)
        docs2 = self.test_user_2.with_user(self.test_user_2).review_user_count()
        for doc in docs2:
            self.assertEqual(doc.get("pending_count"), 0)

        record1 = test_record.with_user(self.test_user_1.id)
        self.assertTrue(record1.can_review)
        record2 = test_record.with_user(self.test_user_2.id)
        self.assertFalse(record2.can_review)
        # User 1 validates the record, 2 review should be approved.
        self.assertFalse(any(r.status == "approved" for r in record1.review_ids))
        record1.validate_tier()
        self.assertTrue(any(r.status == "approved" for r in record1.review_ids))

    def test_12_approve_sequence_same_user(self):
        """Similar to test_12_approve_sequence, but all same users,
        the approve_sequence still apply correctly"""
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "sequence": 20,
            }
        )
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "sequence": 10,
            }
        )
        # Request validation
        self.assertFalse(self.test_record.review_ids)
        reviews = test_record.with_user(self.test_user_1.id).request_validation()
        self.assertTrue(reviews)

        record1 = test_record.with_user(self.test_user_1.id)
        self.assertTrue(record1.can_review)
        # Validation will be all by sequence
        self.assertEqual(len(record1.review_ids), 2)
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        record1.validate_tier()
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "approved"))
        )
        record1.validate_tier()
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        self.assertEqual(
            2, len(record1.review_ids.filtered(lambda x: x.status == "approved"))
        )
        record1.validate_tier()
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        self.assertEqual(
            2, len(record1.review_ids.filtered(lambda x: x.status == "approved"))
        )

    def test_12_approve_sequence_same_user_bypassed(self):
        """Similar to test_12_approve_sequence, with all same users,
        but approve_sequence_bypass is True"""
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "approve_sequence_bypass": True,
                "sequence": 20,
            }
        )
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
                "approve_sequence_bypass": True,
                "sequence": 10,
            }
        )
        # Request validation
        self.assertFalse(self.test_record.review_ids)
        reviews = test_record.with_user(self.test_user_1.id).request_validation()
        self.assertTrue(reviews)

        record1 = test_record.with_user(self.test_user_1.id)
        self.assertTrue(record1.can_review)
        # When the first tier is validated, all the rest will be approved.
        self.assertEqual(len(record1.review_ids), 2)
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            1, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        record1.validate_tier()
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "pending"))
        )
        self.assertEqual(
            0, len(record1.review_ids.filtered(lambda x: x.status == "waiting"))
        )
        self.assertEqual(
            2, len(record1.review_ids.filtered(lambda x: x.status == "approved"))
        )

    def test_13_onchange_review_type(self):
        tier_def_id = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
            }
        )
        self.assertTrue(tier_def_id.reviewer_id)
        tier_def_id.review_type = "group"
        tier_def_id.onchange_review_type()
        self.assertFalse(tier_def_id.reviewer_id)

    def test_14_onchange_review_type(self):
        tier_def_id = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
            }
        )
        self.assertTrue(tier_def_id.reviewer_id)
        tier_def_id.review_type = "group"
        tier_def_id.onchange_review_type()
        self.assertFalse(tier_def_id.reviewer_id)

    def test_15_review_user_count(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 2.5})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_1.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "has_comment": True,
            }
        )
        # Request validation
        review = test_record.with_user(self.test_user_2).request_validation()
        self.assertTrue(review)
        self.assertTrue(self.test_user_1.review_ids)
        self.assertTrue(test_record.review_ids)
        # Used by front-end
        count = self.test_user_1.with_user(self.test_user_1).review_user_count()
        self.assertEqual(len(count), 1)
        # False Review - test notification message bodies
        self.assertIn("created", self.test_record._notify_created_review_body())
        self.assertNotEqual(self.test_record.validation_status, "validated")
        self.assertIn("requested", self.test_record._notify_requested_review_body())
        self.assertIn("rejected", self.test_record._notify_rejected_review_body())
        self.assertIn("accepted", self.test_record._notify_accepted_reviews_body())

    def test_16_review_user_count_on_rejected(self):
        """If document is rejected, it should always removed from tray"""
        # Create new test record
        test_record3 = self.test_model.create({"test_field": 1.0})
        # Create tier definitions
        self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_2.id,
                "definition_domain": "[('test_field', '=', 1.0)]",
            }
        )
        test_record3.with_user(self.test_user_2).request_validation()
        record1 = test_record3.with_user(self.test_user_1)
        self.assertTrue(record1.can_review)
        self.assertTrue(
            self.test_user_1.with_user(self.test_user_1).review_user_count()
        )
        self.assertTrue(
            self.test_user_2.with_user(self.test_user_2).review_user_count()
        )
        # user 1 reject first tier
        record1.reject_tier()
        self.assertFalse(record1.can_review)
        # both user 1 and 2 has nothing left in tray
        self.assertFalse(
            self.test_user_1.with_user(self.test_user_1).review_user_count()
        )
        self.assertFalse(
            self.test_user_2.with_user(self.test_user_2).review_user_count()
        )

    def test_17_search_records_no_validation(self):
        """Search for records that have no validation process started"""
        records = self.env["tier.validation.tester"].search(
            [("reviewer_ids", "=", False)]
        )
        self.assertEqual(len(records), 1)
        self.test_record.with_user(self.test_user_2.id).request_validation()
        self.test_record.with_user(self.test_user_1.id)
        records = self.env["tier.validation.tester"].search(
            [("reviewer_ids", "=", False)]
        )
        self.assertEqual(len(records), 0)

    def test_18_test_review_by_res_users_field(self):
        selected_field = self.env["ir.model.fields"].search(
            [("model", "=", self.test_model._name), ("name", "=", "user_id")]
        )
        test_record = self.test_model.create(
            {"test_field": 2.5, "user_id": self.test_user_2.id}
        )

        definition = self.env["tier.definition"].create(
            {
                "model_id": self.tester_model.id,
                "review_type": "field",
                "reviewer_field_id": selected_field.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
            }
        )

        reviews = test_record.request_validation()
        review = reviews.filtered(lambda r: r.definition_id == definition)
        self.assertTrue(review)
        self.assertEqual(review.reviewer_ids, self.test_user_2)

    def test_19_waiting_tier(self):
        # Create new test record
        tier_review_obj = self.env["tier.review"]
        test_record = self.test_model.create({"test_field": 3.5})
        # Request validation
        review = test_record.request_validation()

        self.assertTrue(review)
        # both reviews should be waiting when created
        review_1 = tier_review_obj.browse(review.ids[0])
        review_2 = tier_review_obj.browse(review.ids[1])
        self.assertTrue(review_1.status == "waiting")
        self.assertTrue(review_2.status == "waiting")
        # and then normal workflow will follow...
        review_1.invalidate_model()
        review_1._compute_can_review()
        self.assertTrue(review_1.status == "pending")
        # first reviewer does not want notifications
        # chatter should be empty
        self.assertFalse(test_record.message_ids)
        self.assertTrue(review_1.done_by.id is False)
        self.assertTrue(review_1.reviewed_date is False)
        self.assertTrue(review_2.status == "waiting")
        self.assertTrue(review_2.done_by.id is False)
        self.assertTrue(review_2.reviewed_date is False)
        record = test_record.with_user(self.test_user_1.id)
        record.invalidate_model()
        record.validate_tier()
        self.assertTrue(review_1.status == "approved")
        self.assertFalse(review_1.reviewed_date is False)
        self.assertTrue(review_1.done_by.id == self.test_user_1.id)
        self.assertTrue(review_2.status == "pending")
        self.assertTrue(review_2.done_by.id is False)
        self.assertTrue(review_2.reviewed_date is False)

    def test_20_no_sequence(self):
        # Create new test record
        tier_review_obj = self.env["tier.review"]
        test_record2 = self.test_model.create({"test_field": 0.9})
        # request validation
        review = test_record2.request_validation()
        self.assertTrue(review)
        review_1 = tier_review_obj.browse(review.ids[0])
        self.assertTrue(review_1.status == "waiting")
        review_1.invalidate_model()
        review_1._compute_can_review()
        self.assertTrue(review_1.status == "pending")
        msg2 = test_record2.message_ids[0].body
        request = test_record2._notify_requested_review_body()
        self.assertIn(request, msg2)

    def test_21_notify_on_create(self):
        # notify on create
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": True,
                "notify_on_accepted": False,
                "notify_on_rejected": False,
                "notify_on_restarted": False,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )
        test_record_1 = self.test_model.create({"test_field": 1})
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record_1.request_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # do not notify on create
        tier_definition.write({"notify_on_create": False})
        test_record_2 = self.test_model.create({"test_field": 1})
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record_2.request_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

    def test_22_notify_on_accepted(self):
        self.test_user_2.write(
            {
                "group_ids": [(6, 0, self.env.ref("base.group_system").ids)],
            }
        )

        # notify on accepted
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": False,
                "notify_on_accepted": True,
                "notify_on_rejected": False,
                "notify_on_restarted": False,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )
        test_record_1 = self.test_model.create({"test_field": 1})
        test_record_1.request_validation()
        record = test_record_1.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.validate_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # do not notify on accepted
        tier_definition.write({"notify_on_accepted": False})
        test_record_2 = self.test_model.create({"test_field": 1})
        test_record_2.request_validation()
        test_record_2.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record_2.validate_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

    def test_23_notify_on_rejected(self):
        self.test_user_2.write(
            {
                "group_ids": [(6, 0, self.env.ref("base.group_system").ids)],
            }
        )

        # notify on rejected
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": False,
                "notify_on_accepted": False,
                "notify_on_rejected": True,
                "notify_on_restarted": False,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )
        test_record_1 = self.test_model.create({"test_field": 1})
        test_record_1.request_validation()
        record = test_record_1.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.reject_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # do not notify on rejected
        tier_definition.write({"notify_on_rejected": False})
        test_record_2 = self.test_model.create({"test_field": 1})
        test_record_2.request_validation()
        test_record_2.with_user(self.test_user_2.id)

        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record_2.reject_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

    def test_24_notify_on_restarted(self):
        self.test_user_2.write(
            {
                "group_ids": [(6, 0, self.env.ref("base.group_system").ids)],
            }
        )

        # notify on restarted
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": False,
                "notify_on_accepted": False,
                "notify_on_rejected": False,
                "notify_on_restarted": True,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )
        test_record_1 = self.test_model.create({"test_field": 1})
        test_record_1.request_validation()
        record = test_record_1.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.restart_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # do not notify on restarted
        tier_definition.write({"notify_on_restarted": False})
        test_record_2 = self.test_model.create({"test_field": 1})
        test_record_2.request_validation()
        test_record_2.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record_2.restart_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

    def test_25_all_notification(self):
        self.test_user_2.write(
            {
                "group_ids": [(6, 0, self.env.ref("base.group_system").ids)],
            }
        )

        # notify on restarted
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": True,
                "notify_on_accepted": True,
                "notify_on_rejected": True,
                "notify_on_restarted": True,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )

        test_record = self.test_model.create({"test_field": 1})

        # request validation
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record.request_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # accept validation
        record = test_record.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.validate_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # restart validation
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.restart_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

        # reject validation
        record.request_validation()
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.reject_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1 + 1)

    def test_26_no_notification(self):
        self.test_user_2.write(
            {
                "group_ids": [(6, 0, self.env.ref("base.group_system").ids)],
            }
        )

        # notify on restarted
        tier_definition = self.env["tier.definition"].search([])
        tier_definition.write(
            {
                "notify_on_create": False,
                "notify_on_accepted": False,
                "notify_on_rejected": False,
                "notify_on_restarted": False,
                "review_type": "group",
                "reviewer_group_id": self.env.ref("base.group_system").id,
            }
        )

        test_record = self.test_model.create({"test_field": 1})

        # request validation
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        test_record.request_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

        # accept validation
        record = test_record.with_user(self.test_user_2.id)
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.validate_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

        # restart validation
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.restart_validation()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

        # reject validation
        record.request_validation()
        notifications_no_1 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        record.reject_tier()
        notifications_no_2 = len(
            self.env["mail.notification"].search(
                [("res_partner_id", "=", self.test_user_1.partner_id.id)]
            )
        )
        self.assertEqual(notifications_no_2, notifications_no_1)

    def test_27_change_field_exception_validation(self):
        """Test under and after validations"""
        # Cannot create `tier.validation.exception` records because
        # `tier.validation.tester` are fake model and its fields are
        # not propagated to the DDBB and cannot read from `ir.model.fields`.
        # We will use the mock.patch instead.
        _tvf = ["test_validation_field"]
        _rv = _tvf + BEF
        self.assertEqual(self.test_record.test_validation_field, 0)
        self.assertFalse(self.test_record.review_ids)
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        self.assertTrue(reviews)
        self.assertTrue(self.test_record.review_ids)
        # Unable to write test_validation_field under validation
        with self.assertRaises(ValidationError):
            self.test_record.with_user(self.test_user_2.id).write(
                {"test_validation_field": 1}
            )
        # Able to write test_validation_field under validation
        with mock.patch.object(
            TV, "_get_under_validation_exceptions", return_value=_rv
        ):
            self.test_record.with_user(self.test_user_2.id).write(
                {"test_validation_field": 2}
            )
        self.assertEqual(self.test_record.test_validation_field, 2)
        # Validate record
        record = self.test_record.with_user(self.test_user_1.id)
        record.validate_tier()
        record.action_confirm()
        self.assertEqual(record.validation_status, "validated")
        # Unable to write test_validation_field after validation
        with self.assertRaises(ValidationError):
            # Simulate there are fields, but not test_validation_field
            with mock.patch.object(TV, "_get_validation_exceptions", return_value=BEF):
                self.test_record.with_user(self.test_user_2.id).write(
                    {"test_validation_field": 3}
                )
        # Able to write test_validation_field after validation
        with mock.patch.multiple(
            TV,
            _get_validation_exceptions=mock.MagicMock(return_value=_tvf),
            _get_after_validation_exceptions=mock.MagicMock(return_value=_rv),
        ):
            self.test_record.with_user(self.test_user_2.id).write(
                {"test_validation_field": 4}
            )
        self.assertEqual(self.test_record.test_validation_field, 4)

    def test_28_computed_state_field(self):
        """Test the regular flow on a model where state is a computed field"""
        # The record cannot be confirmed without validation
        with self.assertRaisesRegex(
            ValidationError,
            "This action needs to be validated",
        ):
            with self.env.cr.savepoint():
                self.test_record_computed.action_confirm()
                # Flush manually to trigger the _write
                self.test_record_computed.flush_recordset()
        self.assertEqual(self.test_record_computed.state, "draft")
        # The validation is performed
        self.test_record_computed.request_validation()
        self.test_record_computed.invalidate_recordset()
        self.assertEqual(self.test_record_computed.review_ids.status, "waiting")
        self.test_record_computed.with_user(self.test_user_1).validate_tier()
        self.test_record_computed.invalidate_recordset()
        self.assertEqual(self.test_record_computed.review_ids.status, "approved")
        # After validation, the record can be confirmed
        self.test_record_computed.action_confirm()
        self.test_record_computed.flush_recordset()
        self.assertEqual(self.test_record_computed.state, "confirmed")
        # After cancelling, the reviews are removed
        self.test_record_computed.action_cancel()
        self.test_record_computed.flush_recordset()
        self.assertFalse(self.test_record_computed.review_ids)
        self.test_record_computed.invalidate_recordset()

    def test_29_allow_write_for_reviewers(self):
        reviews = self.test_record.with_user(self.test_user_2.id).request_validation()
        record = self.test_record.with_user(self.test_user_1.id)
        record.invalidate_recordset()
        with self.assertRaises(ValidationError):
            record.with_user(self.test_user_1.id).write({"test_field": 0.3})
        reviews.definition_id.with_user(self.test_user_1.id).write(
            {"allow_write_for_reviewer": True}
        )
        record.with_user(self.test_user_1.id).write({"test_field": 0.3})

    def test_30_request_validation_diff_company(self):
        """
        Test validation request behavior with multi-company setup.

        Setup:
        - Main company has 2 tier definitions:
          - One for User1 (sequence 30)
          - One for User3 (sequence 20)
        - Other company has 1 tier definition:
          - One for User3 (sequence 30)

        When record's company is set to 'other company':
        - Only User3's tier definition from other company should be applied
        - Should create only 1 review
        """
        self.assertFalse(self.test_record_2.review_ids)
        self.assertFalse(self.test_record_2.company_id)
        self.assertEqual(self.test_user_3_multi_company.env.company, self.main_company)

        self.test_record_2.company_id = self.other_company

        reviews = self.test_record_2.with_user(
            self.test_user_3_multi_company.id
        ).request_validation()

        self.assertEqual(len(reviews), 1)

    def test_31_request_validation_same_company(self):
        """
        Test validation request behavior with multi-company setup.

        Setup:
        - Main company has 2 tier definitions:
          - One for User1 (sequence 30)
          - One for User3 (sequence 20)
        - Other company has 1 tier definition:
          - One for User3 (sequence 30)

        When record's company is set to 'main company':
        - Both User1 and User3's tier definitions from main company should be applied
        - Should create 2 reviews
        """
        self.assertFalse(self.test_record_2.review_ids)
        self.assertFalse(self.test_record_2.company_id)
        self.assertEqual(self.test_user_3_multi_company.env.company, self.main_company)

        self.test_record_2.company_id = self.main_company

        reviews = self.test_record_2.with_user(
            self.test_user_3_multi_company.id
        ).request_validation()

        self.assertEqual(len(reviews), 2)

    def test_30_request_validation(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 1.0})
        # Create tier definitions for both tester models
        self.tier_definition.write(
            {
                "approve_sequence": True,
                "notify_on_create": True,
            }
        )
        def_2 = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_2.id,
                "sequence": 20,
                "approve_sequence": True,
                "notify_on_create": False,
                "notify_on_accepted": True,
            }
        )
        def_3 = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_3_multi_company.id,
                "sequence": 10,
                "approve_sequence": True,
                "notify_on_create": False,
                "notify_on_accepted": True,
            }
        )
        mt_tier_validation_requested = self.env.ref(
            "base_tier_validation.mt_tier_validation_requested"
        )
        mt_tier_validation_accepted = self.env.ref(
            "base_tier_validation.mt_tier_validation_accepted"
        )
        test_record.request_validation()
        review_1 = test_record.review_ids.filtered(
            lambda x: x.definition_id == self.tier_definition
        )
        self.assertEqual(review_1.status, "pending")
        review_2 = test_record.review_ids.filtered(lambda x: x.definition_id == def_2)
        self.assertEqual(review_2.status, "waiting")
        review_3 = test_record.review_ids.filtered(lambda x: x.definition_id == def_3)
        self.assertEqual(review_3.status, "waiting")
        followers = test_record.message_follower_ids
        self.assertIn(self.test_user_1.partner_id, followers.mapped("partner_id"))
        follower_1 = followers.filtered(
            lambda x: x.partner_id == self.test_user_1.partner_id
        )
        self.assertIn(mt_tier_validation_requested, follower_1.subtype_ids)
        self.assertNotIn(mt_tier_validation_accepted, follower_1.subtype_ids)
        self.assertNotIn(self.test_user_2.partner_id, followers.mapped("partner_id"))
        self.assertNotIn(
            self.test_user_3_multi_company.partner_id, followers.mapped("partner_id")
        )
        old_messages = test_record.message_ids
        test_record.with_user(self.test_user_1).validate_tier()
        new_messages = test_record.message_ids - old_messages
        self.assertEqual(len(new_messages), 1)
        self.assertEqual(new_messages.subtype_id, mt_tier_validation_accepted)
        self.assertEqual(self.test_user_2.partner_id, new_messages.notified_partner_ids)
        self.assertEqual(review_1.status, "approved")
        self.assertEqual(review_2.status, "pending")
        self.assertEqual(review_3.status, "waiting")
        followers = test_record.message_follower_ids
        self.assertIn(self.test_user_1.partner_id, followers.mapped("partner_id"))
        self.assertIn(self.test_user_2.partner_id, followers.mapped("partner_id"))
        follower_2 = followers.filtered(
            lambda x: x.partner_id == self.test_user_2.partner_id
        )
        self.assertNotIn(mt_tier_validation_requested, follower_2.subtype_ids)
        self.assertIn(mt_tier_validation_accepted, follower_2.subtype_ids)
        self.assertNotIn(
            self.test_user_3_multi_company.partner_id, followers.mapped("partner_id")
        )
        old_messages = test_record.message_ids
        test_record.with_user(self.test_user_2).validate_tier()
        new_messages = test_record.message_ids - old_messages
        self.assertEqual(len(new_messages), 1)
        self.assertEqual(new_messages.subtype_id, mt_tier_validation_accepted)
        self.assertEqual(
            self.test_user_3_multi_company.partner_id, new_messages.notified_partner_ids
        )
        self.assertEqual(review_1.status, "approved")
        self.assertEqual(review_2.status, "approved")
        self.assertEqual(review_3.status, "pending")
        followers = test_record.message_follower_ids
        self.assertIn(self.test_user_1.partner_id, followers.mapped("partner_id"))
        self.assertIn(self.test_user_2.partner_id, followers.mapped("partner_id"))
        self.assertIn(
            self.test_user_3_multi_company.partner_id, followers.mapped("partner_id")
        )
        follower_3 = followers.filtered(
            lambda x: x.partner_id == self.test_user_3_multi_company.partner_id
        )
        self.assertNotIn(mt_tier_validation_requested, follower_3.subtype_ids)
        self.assertIn(mt_tier_validation_accepted, follower_3.subtype_ids)
        old_messages = test_record.message_ids
        test_record.with_user(self.test_user_3_multi_company).validate_tier()
        new_messages = test_record.message_ids - old_messages
        self.assertEqual(len(new_messages), 0)

    def test_31_request_validation(self):
        # Create new test record
        test_record = self.test_model.create({"test_field": 1.0})
        # Create tier definitions for both tester models
        self.tier_definition.write(
            {
                "approve_sequence": True,
                "notify_on_create": True,
            }
        )
        def_2 = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_2.id,
                "sequence": 20,
                "approve_sequence": True,
                "notify_on_create": True,
                "notify_on_accepted": True,
            }
        )
        def_3 = self.tier_def_obj.create(
            {
                "model_id": self.tester_model.id,
                "review_type": "individual",
                "reviewer_id": self.test_user_3_multi_company.id,
                "sequence": 10,
                "approve_sequence": True,
                "notify_on_create": True,
                "notify_on_accepted": True,
            }
        )
        test_record.request_validation()
        review_1 = test_record.review_ids.filtered(
            lambda x: x.definition_id == self.tier_definition
        )
        self.assertEqual(review_1.status, "pending")
        review_2 = test_record.review_ids.filtered(lambda x: x.definition_id == def_2)
        self.assertEqual(review_2.status, "waiting")
        review_3 = test_record.review_ids.filtered(lambda x: x.definition_id == def_3)
        self.assertEqual(review_3.status, "waiting")
        followers = test_record.message_follower_ids
        self.assertIn(self.test_user_1.partner_id, followers.mapped("partner_id"))
        self.assertIn(self.test_user_2.partner_id, followers.mapped("partner_id"))
        self.assertIn(
            self.test_user_3_multi_company.partner_id, followers.mapped("partner_id")
        )

    def test_32_test_review_by_res_groups_field(self):
        """Test using field-based validation with groups"""
        selected_field = self.env["ir.model.fields"].search(
            [("model", "=", self.test_model._name), ("name", "=", "group_id")]
        )
        test_record = self.test_model.create(
            {"test_field": 2.5, "group_id": self.test_group.id}
        )

        definition = self.env["tier.definition"].create(
            {
                "model_id": self.tester_model.id,
                "review_type": "field",
                "reviewer_field_id": selected_field.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
            }
        )

        reviews = test_record.request_validation()
        review = reviews.filtered(lambda r: r.definition_id == definition)
        self.assertTrue(review)
        self.assertEqual(review.reviewer_ids, self.test_user_2 | self.test_user_1)

    def test_33_test_review_by_wrong_field_type(self):
        """Test using field-based validation with groups"""
        selected_field = self.env["ir.model.fields"].search(
            [("model", "=", self.test_model._name), ("name", "=", "menu_id")]
        )
        test_record = self.test_model.create(
            {
                "test_field": 2.5,
                "menu_id": self.env["ir.ui.menu"].search([], limit=1).id,
            }
        )
        self.assertTrue(test_record.menu_id)
        self.env["tier.definition"].create(
            {
                "model_id": self.tester_model.id,
                "review_type": "field",
                "reviewer_field_id": selected_field.id,
                "definition_domain": "[('test_field', '>', 1.0)]",
                "approve_sequence": True,
            }
        )
        with self.assertRaisesRegex(
            ValidationError,
            "Validation reviewer field should be of the appropriate type",
        ):
            test_record.request_validation()


@tagged("at_install")
class TierTierValidationView(CommonTierValidation):
    def test_view_manual(self):
        view = self.env[self.test_record._name].get_view(False, "form")
        with Form(self.test_record) as f:
            self.assertNotIn("review_ids", f._values)
            form = etree.fromstring(view["arch"])
            self.assertFalse(form.xpath("//field[@name='review_ids']"))
            self.assertFalse(form.xpath("//field[@name='can_review']"))
            self.assertFalse(form.xpath("//button[@name='request_validation']"))

    def test_view_automatic(self):
        view = self.env[self.test_record_2._name].get_view(False, "form")
        with Form(self.test_record_2) as f:
            self.assertIn("review_ids", f._values)
            form = etree.fromstring(view["arch"])
            self.assertTrue(form.xpath("//field[@name='review_ids']"))
            self.assertTrue(form.xpath("//field[@name='can_review']"))
            self.assertTrue(form.xpath("//button[@name='request_validation']"))

    def test_get_view(self):
        view = self.test_record_2.get_view()
        model = "tier.validation.tester2"
        self.assertEqual(view["model"], model)
        self.assertEqual(view["models"].keys(), {model, "tier.review"})
        self.assertIn("id", view["models"][model])
        self.assertIn("need_validation", view["models"][model])
        self.assertIn("next_review", view["models"][model])
        self.assertIn("review_ids", view["models"][model])
