# Part of Odoo. See LICENSE file for full copyright and licensing details.

from contextlib import contextmanager
from dateutil.relativedelta import relativedelta
from unittest.mock import patch

from odoo import fields
from odoo.addons.mail.models.mail_activity import MailActivity
from odoo.addons.mail.tests.common import MailCommon
from odoo.tests.common import Form, tagged, HttpCase
from odoo.tools.misc import format_date


class ActivityScheduleCase(MailCommon):

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

        # prepare activities
        cls.activity_type_todo = cls.env.ref('mail.mail_activity_data_todo')
        cls.activity_type_todo.delay_count = 4
        cls.activity_type_call = cls.env.ref('mail.mail_activity_data_call')
        cls.activity_type_call.delay_count = 1

    def reverse_record_set(self, records):
        """ Get an equivalent recordset but with elements in reversed order. """
        return self.env[records._name].browse([record.id for record in reversed(records)])

    def get_last_activities(self, on_record, limit=None):
        """ Get the last activities on the record in id asc order. """
        return self.reverse_record_set(self.env['mail.activity'].search(
            [('res_model', '=', on_record._name), ('res_id', '=', on_record.id)], order='id desc', limit=limit))

    # ------------------------------------------------------------
    # ACTIVITIES MOCK
    # ------------------------------------------------------------

    @contextmanager
    def _mock_activities(self):
        activity_create_origin = MailActivity.create
        self._new_activities = self.env['mail.activity'].sudo()

        def _activity_create(model, *args, **kwargs):
            res = activity_create_origin(model, *args, **kwargs)
            self._new_activities += res.sudo()
            return res

        with patch.object(
                MailActivity, 'create', autospec=True, wraps=MailActivity,
                side_effect=_activity_create
             ) as activity_create_mocked:
            self.activity_create_mocked = activity_create_mocked
            yield

    def assertActivityCreatedOnRecord(self, record, activity_values):
        activity = self._new_activities.filtered(
            lambda act: act.res_model == record._name and act.res_id == record.id
        )
        for fname, fvalue in activity_values.items():
            with self.subTest(fname=fname):
                self.assertEqual(activity[fname], fvalue)

    def assertActivityDoneOnRecord(self, record, activity_type):
        last_message = record.message_ids[0]
        self.assertEqual(last_message.mail_activity_type_id, activity_type)
        self.assertIn(activity_type.name, last_message.body)
        self.assertIn('done', last_message.body)

    def assertActivitiesFromPlan(self, plan, record, force_base_date_deadline=None, force_responsible_id=None):
        """ Check that the last activities on the record correspond to the one
        that the plan must create (number of activities and activities content).

        :param <mail.activity.plan> plan: activity plan that has been applied on the record
        :param recordset record: record on which the plan has been applied
        :param date force_base_date_deadline: base plan date provided when scheduling the plan
        :param <res.user> force_responsible_id: responsible provided when scheduling the plan
        """
        expected_number_of_activity = len(plan.template_ids)
        activities = self._new_activities.filtered(
            lambda act: act.res_model == record._name and act.res_id == record.id
        )
        self.assertEqual(len(activities), expected_number_of_activity)

        for activity, template in zip(activities, plan.template_ids):
            self.assertEqual(activity.activity_type_id, template.activity_type_id)
            self.assertEqual(
                activity.date_deadline,
                (force_base_date_deadline or fields.Date.today()) + relativedelta(
                    **{template.activity_type_id.delay_unit: template.activity_type_id.delay_count}))
            self.assertEqual(activity.note, template.note)
            self.assertEqual(activity.summary, template.summary)
            self.assertFalse(activity.automated)
            if force_responsible_id:
                self.assertEqual(activity.user_id, force_responsible_id)
            else:
                self.assertEqual(activity.user_id, template.responsible_id or self.env.user)

    def assertMessagesFromPlan(self, plan, record, force_base_date_deadline=None, force_responsible_id=None):
        """ Check that the last posted message on the record correspond to the one
        that the plan must generate (number of activities and activities content).

        :param <mail.activity.plan> plan: activity plan that has been applied on the record
        :param recordset record: record on which the plan has been applied
        :param date force_base_date_deadline: deadline provided when scheduling the plan
        :param <res.user> force_responsible_id: responsible provided when scheduling the plan
        """
        message = record.message_ids[0]
        self.assertIn(f'The plan "{plan.name}" has been started', message.body)

        for template in plan.template_ids:
            date_deadline = (force_base_date_deadline or fields.Date.today()) + relativedelta(
                **{template.activity_type_id.delay_unit: template.activity_type_id.delay_count})
            if force_responsible_id:
                responsible_id = force_responsible_id
            else:
                responsible_id = template.responsible_id or self.env.user

            self.assertIn(template.summary, message.body)
            self.assertIn(f'{template.summary or template.activity_type_id.name}, '
                          f'assigned to {responsible_id.name}, due on the '
                          f'{format_date(self.env, date_deadline)}', message.body)

    def assertPlanExecution(self, plan, records, force_base_date_deadline=None, force_responsible_id=None):
        """ Check that the plan has created the right activities and send the
        right message on the records (see assertActivitiesFromPlan and
        assertMessagesFromPlan). """
        for record in records:
            self.assertActivitiesFromPlan(
                plan, record,
                force_base_date_deadline=force_base_date_deadline,
                force_responsible_id=force_responsible_id,
            )
            self.assertMessagesFromPlan(
                plan, record,
                force_base_date_deadline=force_base_date_deadline,
                force_responsible_id=force_responsible_id,
            )

    def _instantiate_activity_schedule_wizard(self, records, additional_context_value=None):
        """ Get a new Form with context default values referring to the records. """
        return Form(self.env['mail.activity.schedule'].with_context({
            'active_id': records.ids[0],
            'active_ids': records.ids,
            'active_model': records._name,
            **(additional_context_value if additional_context_value else {}),
        }))


@tagged("-at_install", "post_install")
class TestMailActivityChatter(HttpCase):

    def test_mail_activity_schedule_from_chatter(self):
        testuser = self.env['res.users'].create({
            'email': 'testuser@testuser.com',
            'name': 'Test User',
            'login': 'testuser',
            'password': 'testuser',
        })
        self.start_tour(
            f"/web#id={testuser.partner_id.id}&model=res.partner",
            "mail_activity_schedule_from_chatter",
            login="admin",
        )
