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

from odoo.tests import Form, tagged

from .test_project_base import TestProjectCommon


@tagged('-at_install', 'post_install')
class TestProjectMilestone(TestProjectCommon):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.milestone_pigs, cls.milestone_goats = cls.env['project.milestone'].with_context({'mail_create_nolog': True}).create([{
            'name': 'Test Milestone',
            'project_id': cls.project_pigs.id,
        }, {
            'name': 'Test Milestone Goats',
            'project_id': cls.project_goats.id,
        }])

    def test_milestones_settings_change(self):
        # To be sure the feature is disabled globally to begin the test.
        self.env['res.config.settings'] \
            .create({'group_project_milestone': False}) \
            .execute()
        self.assertFalse(self.env.user.has_group('project.group_project_milestone'), 'The "Milestones" feature should not be globally enabled by default.')
        self.assertFalse(self.project_pigs.allow_milestones, 'The "Milestones" feature should not be enabled by default.')
        self.env['res.config.settings'] \
            .create({'group_project_milestone': True}) \
            .execute()
        self.assertTrue(self.env.user.has_group('project.group_project_milestone'), 'The "Milestones" feature should globally be enabled.')
        self.assertTrue(self.project_pigs.allow_milestones, 'The "Milestones" feature should be enabled by default on the project when the feature is enabled.')
        project = self.env['project.project'].create({'name': 'Test allow_milestones on New Project'})
        self.assertTrue(project.allow_milestones, 'The "Milestones" feature should be enabled by default when the feature is enabled globally.')

        with Form(self.env['project.project']) as project_form:
            project_form.name = 'My Mouses Project'
            self.assertTrue(project_form.allow_milestones, 'New projects allow_milestones should be True by default.')

    def test_change_project_in_task(self):
        """ Test when a task is linked to a milestone and when we change its project the milestone is removed (and
            we fallback on the parent milestone if it belongs to the same project)

            Test Case:
            =========
            1) Set a milestone on the task
            2) Change the project of that task
            3) Check no milestone is linked to the task (or the one of its parent is used if relevant)
        """
        # A. No parent task
        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(self.task_1.milestone_id, self.milestone_pigs)


        self.task_1.project_id = self.project_goats
        self.assertFalse(self.task_1.milestone_id, 'No milestone should be linked to the task since its project has changed')

        # B. Parent task with no milestone set
        task_2 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({
            'name': 'Child MilestoneTask',
            'user_ids': self.user_projectmanager,
            'project_id': self.project_pigs.id,
            'parent_id': self.task_1.id,
            'milestone_id': self.milestone_pigs.id,
        })
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)

        task_2.project_id = self.project_goats
        self.assertFalse(task_2.milestone_id, 'No milestone should be linked to the task since its project has changed and its parent task has no milestone')

        # C. Parent task with a milestone set but on a different project
        self.task_1.project_id = self.project_pigs
        self.task_1.milestone_id = self.milestone_pigs
        task_2.project_id = self.project_pigs
        task_2.milestone_id = self.milestone_pigs
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)

        task_2.project_id = self.project_goats
        self.assertFalse(task_2.milestone_id, 'No milestone should be linked to the task since its project has changed and its parent task belongs to another project')

        # D. Parent task with a milestone set on the same project
        self.task_1.project_id = self.project_goats
        self.task_1.milestone_id = self.milestone_goats
        task_2.project_id = self.project_pigs
        task_2.milestone_id = self.milestone_pigs
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)
        task_2.project_id = self.project_goats
        self.assertEqual(task_2.milestone_id, self.milestone_goats,
                         'The milestone of the task should be replaced by the one of its parent task as they now belong to the same project')

        # E. No milestone for private task
        task_2.parent_id = False
        self.assertEqual(task_2.milestone_id, self.milestone_goats)
        task_2.project_id = False
        self.assertFalse(task_2.milestone_id, 'No milestone should be linked to a private task')

    def test_duplicate_project_duplicates_milestones_on_tasks(self):
        """
        Test when we duplicate the project with tasks linked to its' milestones,
        that the tasks in the new project are also linked to the duplicated milestones of the new project
        We can't really robustly test that the mapping of task -> milestone is the same in the old and new project,
        the workaround way of testing the mapping is basing ourselves on unique names and check that those are equals in the test.
        """
        # original unique_names, used to map between the original -> copy
        unique_name_1 = "unique_name_1"
        unique_name_2 = "unique_name_2"
        unique_names = [unique_name_1, unique_name_2]
        project = self.env['project.project'].create({
            'name': 'Test project',
            'allow_milestones': True,
        })
        milestones = self.env['project.milestone'].create([{
            'name': unique_name_1,
            'project_id': project.id,
        }, {
            'name': unique_name_2,
            'project_id': project.id,
        }])
        tasks = self.env['project.task'].create([{
            'name': unique_name_1,
            'project_id': project.id,
            'milestone_id': milestones[0].id,
        }, {
            'name': unique_name_2,
            'project_id': project.id,
            'milestone_id': milestones[1].id,
        }])
        self.assertEqual(tasks[0].milestone_id, milestones[0])
        self.assertEqual(tasks[1].milestone_id, milestones[1])
        project_copy = project.copy()
        self.assertNotEqual(project_copy.milestone_ids, False)
        self.assertEqual(project.milestone_ids.mapped('name'), project_copy.milestone_ids.mapped('name'))
        self.assertNotEqual(project_copy.task_ids, False)
        for milestone in project_copy.task_ids.milestone_id:
            self.assertTrue(milestone in project_copy.milestone_ids)
        for unique_name in unique_names:
            orig_task = project.task_ids.filtered(lambda t: t.name == unique_name)
            copied_task = project_copy.task_ids.filtered(lambda t: t.name == unique_name)
            self.assertEqual(orig_task.name, copied_task.name, "The copied_task should be a copy of the original task")
            self.assertNotEqual(copied_task.milestone_id, False,
                                "We should copy the milestone and it shouldn't be reset to false from _compute_milestone_id")
            self.assertEqual(orig_task.milestone_id.name, copied_task.milestone_id.name,
                             "the copied milestone should be a copy of the original ")

    def test_basic_milestone_write(self):
        """ Testing basic milestone/project write operation on task, i.e:
                1. Set/change the milestone of a task
                2. Change the milestone/project of a task simultaneously
                3. Set/change to an invalid milestone
        """
        extra_milestone_pigs = self.env['project.milestone'].with_context({'mail_create_nolog': True}).create({
            'name': 'Test Extra Milestone',
            'project_id': self.project_pigs.id,
        })

        # 1. Set/change the milestone of a task
        self.task_1.project_id = self.project_pigs
        self.assertEqual(self.task_1.project_id, self.project_pigs)
        self.assertFalse(self.task_1.milestone_id)

        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(self.task_1.milestone_id, self.milestone_pigs,
                         "Assignation of a valid milestone to a task with no milestone is not working properly.")
        self.task_1.milestone_id = extra_milestone_pigs
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs,
                         "Change of the milestone of a task to a milestone from the same project is not working properly.")

        # 2. Change the milestone/project of a task simultaneously
        self.assertEqual(self.task_1.project_id, self.project_pigs)
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs)

        self.task_1.write({
            'milestone_id': self.milestone_goats.id,
            'project_id': self.project_goats.id,
        })
        self.assertEqual(self.task_1.milestone_id, self.milestone_goats,
                         "Changing the project of a task and its milestone simultaneously is not working properly.")
        self.assertEqual(self.task_1.project_id, self.project_goats,
                         "Changing the project of a task and its milestone simultaneously is not working properly.")

        # 3. Set/change to an invalid milestone
        self.assertEqual(self.task_1.project_id, self.project_goats)
        self.assertEqual(self.task_1.milestone_id, self.milestone_goats)

        self.task_1.milestone_id = self.milestone_pigs
        self.assertFalse(self.task_1.milestone_id,
                         "Setting the milestone of a task to an invalid value should reset the value of milestone_id.")


    def test_set_milestone_parent_task(self):
        """ When a milestone is set on a parent task, it is set as well on its child tasks if they have no milestone set yet and
            if they belong to the same project (or they have no project set).

            Test Case:
            =========
            1) Set a milestone on the task (or not)
            2) Change the milestone of its parent
            3) Check the result
        """
        # A. Child task with no milestone set and belonging to the same project
        task_2, task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create([{
            'name': 'Child MilestoneTask',
            'user_ids': self.user_projectmanager,
            'project_id': self.project_pigs.id,
            'parent_id': self.task_1.id,
        }, {
            'name': 'Grand-child MilestoneTask',
            'user_ids': self.user_projectmanager,
            'project_id': self.project_pigs.id,
        }])
        self.assertFalse(self.task_1.milestone_id)
        self.assertFalse(task_2.milestone_id)

        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(task_2.milestone_id, self.milestone_pigs,
                         "The milestone of the parent task should be set to its subtasks if they belong to the same project (or the subtask has not project set) and the subtask has no milestone already set.")

        # B. Child task with a milestone already set
        extra_milestone_pigs = self.env['project.milestone'].with_context({'mail_create_nolog': True}).create({
            'name': 'Test Extra Milestone',
            'project_id': self.project_pigs.id,
        })
        self.task_1.milestone_id = False
        task_2.milestone_id = extra_milestone_pigs
        self.assertFalse(self.task_1.milestone_id)
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs)

        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs, "The milestone of the child task should not be modified has it has already one set.")

        # C. Child task with no milestone set but belonging to another project
        self.task_1.project_id = self.project_goats
        task_2.milestone_id = False
        self.assertFalse(self.task_1.milestone_id)
        self.assertFalse(task_2.milestone_id)

        self.task_1.milestone_id = self.milestone_goats
        self.assertFalse(task_2.milestone_id, "The milestone of the parent task should not be set to its child task has they belong to different projects.")

        # D. Recursion test (grand-parent task's milestone set to grand-child task)
        task_3.parent_id = task_2
        self.task_1.project_id = task_2.project_id = task_3.project_id = self.project_pigs
        self.task_1.milestone_id = task_2.milestone_id = task_3.milestone_id = False
        self.assertFalse(task_2.milestone_id)
        self.assertFalse(task_3.milestone_id)

        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(task_3.milestone_id, self.milestone_pigs, "The milestone of the parent task should be set to its (grand)child tasks recursively.")

        # E. Recursion test 2 (grand-child task has no milestone but first level child does)
        self.task_1.milestone_id = False
        task_2.milestone_id = self.milestone_pigs
        task_3.milestone_id = False
        self.assertFalse(self.task_1.milestone_id)
        self.assertFalse(task_3.milestone_id)
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)

        self.task_1.milestone_id = extra_milestone_pigs
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)
        self.assertFalse(task_3.milestone_id,
                         "The milestone of the parent task should be set to its (grand)child tasks recursively. If a child task milestone should not be updated, it stops the recursion.")

        # F. Update of the parent's milestone, trigger the update of the subtask's milestone if they were the same before change
        self.task_1.milestone_id = self.milestone_pigs
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)
        self.assertEqual(self.task_1.milestone_id, self.milestone_pigs)
        self.task_1.milestone_id = extra_milestone_pigs
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs,
                         "If parent and child tasks share the same milestone, the update of the parent's milestone should trigger the update of its child's milestone.")

        # G. Same as F but project and milestone of the parent task are changed at the same time -> The milestone of the child should not change
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs)
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs)

        self.task_1.write({
            'project_id': self.project_goats.id,
            'milestone_id': self.milestone_goats.id,
        })
        self.assertEqual(self.task_1.milestone_id, self.milestone_goats)
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs,
                         "The child milestone should not be updated if the parent task's project is changed.")

        # H. Same as G but project the project writen value is the same as the previous one -> No actual change of project_id so update the subtask milestone
        self.task_1.project_id = self.project_pigs
        self.task_1.milestone_id = extra_milestone_pigs
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs)
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs)

        self.task_1.write({
            'project_id': self.project_pigs.id,
            'milestone_id': self.milestone_pigs.id,
        })
        self.assertEqual(self.task_1.milestone_id, self.milestone_pigs)
        self.assertEqual(task_2.milestone_id, self.milestone_pigs,
                         "The child milestone should be updated as the project of the parent task does not actually change.")

        # I. Same case as G but the display_on_project is set to False on the child task -> Both project and milestone of the subtask should be updated
        self.task_1.write({
            'project_id': self.project_pigs.id,
            'milestone_id': extra_milestone_pigs.id,
        })
        task_2.display_in_project = False
        self.assertEqual(task_2.milestone_id, extra_milestone_pigs)
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs)

        self.task_1.write({
            'project_id': self.project_goats.id,
            'milestone_id': self.milestone_goats.id,
        })
        self.assertEqual(self.task_1.milestone_id, self.milestone_goats)
        self.assertEqual(task_2.project_id, self.project_goats)
        self.assertEqual(task_2.milestone_id, self.milestone_goats,
                         "The child milestone should be updated if the parent task's project is changed only if dislay_on_project is set to False for the subtask.")

        # J. Same case as F but subtask is closed -> no update of its milestone
        self.task_1.project_id = task_2.project_id = self.project_pigs
        self.task_1.milestone_id = task_2.milestone_id = self.milestone_pigs
        task_2.state = '1_done'
        self.assertEqual(task_2.milestone_id, self.milestone_pigs)
        self.assertEqual(self.task_1.milestone_id, self.milestone_pigs)

        self.task_1.write({
            'milestone_id': extra_milestone_pigs.id,
        })
        self.assertEqual(self.task_1.milestone_id, extra_milestone_pigs)
        self.assertEqual(task_2.milestone_id, self.milestone_pigs,
                         "The child milestone should not be updated if it is closed.")
