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

from odoo import Command
from odoo.fields import Domain
from odoo.tests import Form, tagged, TransactionCase
from odoo.exceptions import UserError, ValidationError


@tagged("recruitment")
@tagged('at_install', '-post_install')  # LEGACY at_install
class TestRecruitmentTalentPool(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.t_talent_pool_1, cls.t_talent_pool_2 = cls.env["hr.talent.pool"].create(
            [{"name": "Test Talent Pool 1"}, {"name": "Test Talent Pool 2"}]
        )

        cls.t_applicant_1, cls.t_applicant_2 = cls.env["hr.applicant"].create(
            [{"partner_name": "Test Applicant 1"}, {"partner_name": "Test Applicant 2"}]
        )

        cls.t_job_1, cls.t_job_2, cls.t_job_3 = cls.env["hr.job"].create(
            [
                {"name": "Job 1"},
                {"name": "Job 2"},
                {"name": "Job 3"},
            ]
        )
        cls.mail_template = cls.env['mail.template'].create({
            'name': 'Test stage template',
            'model_id': cls.env['ir.model']._get_id('hr.applicant'),
            'subject': 'Job application test',
        })

    def test_add_applicant_to_one_talent_pool(self):
        """
        Test that a applicant is duplicated and linked to a pool when creating a talent.
        """
        talent_pool_applicant = self.t_talent_pool_1.talent_ids
        self.assertFalse(talent_pool_applicant, "There should not be any applicants in the talent pool")

        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids = self.t_talent_pool_1
        wizard.applicant_ids = self.t_applicant_1
        talent_pool_applicant = wizard.save()._add_applicants_to_pool()

        self.assertTrue(
            talent_pool_applicant, "An applicant('talent') should be created when adding an applicant to a pool"
        )
        self.assertNotEqual(
            self.t_applicant_1, talent_pool_applicant, "The 'talent' and the applicant should be two different records"
        )
        self.assertEqual(
            talent_pool_applicant.talent_pool_ids,
            self.t_talent_pool_1,
            "The talent should be linked to the talent pool",
        )

    def test_create_talent_in_pool(self):
        talent = self.env["hr.applicant"].with_context(default_talent_pool_ids=self.t_talent_pool_1.ids).create({
            'partner_name': 'Talent in a pool',
        })

        self.assertEqual(talent.talent_pool_ids, self.t_talent_pool_1)
        self.assertEqual(talent.pool_applicant_id, talent)

        job_wizard = Form(self.env["job.add.applicants"].with_context({"default_applicant_ids": talent.ids}))
        job_wizard.job_ids = self.t_job_1
        job_1_applicant = job_wizard.save()._add_applicants_to_job()

        self.assertEqual(job_1_applicant.pool_applicant_id, talent)

    def test_add_applicant_to_multiple_talent_pools(self):
        """
        Test that a applicant is only duplicated once and linked to multiple pools when creating a talent.
        """
        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids.add(self.t_talent_pool_1)
        wizard.talent_pool_ids.add(self.t_talent_pool_2)
        wizard.applicant_ids = self.t_applicant_1
        talent_pool_applicant = wizard.save()._add_applicants_to_pool()

        self.assertTrue(
            talent_pool_applicant, "An applicant('talent') should be created when adding an applicant to a pool"
        )
        self.assertEqual(
            len(talent_pool_applicant), 1, "Exactly one 'talent' should be created when adding an applicant to a pool"
        )
        self.assertEqual(
            talent_pool_applicant.talent_pool_ids,
            self.t_talent_pool_1 | self.t_talent_pool_2,
            "The 'talent' should belong to both talent pools",
        )

        # Testing that adding an applicant to a pool with the wizard then to another pool works
        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids.add(self.t_talent_pool_1)
        wizard.applicant_ids = self.t_applicant_2
        talent_pool_applicant_1 = wizard.save()._add_applicants_to_pool()

        self.assertTrue(
            talent_pool_applicant_1, "An applicant('talent') should be created when adding an applicant to a pool",
        )
        self.assertEqual(
            len(talent_pool_applicant_1), 1, "Exactly one 'talent' should be created when adding an applicant to a pool",
        )
        self.assertEqual(
            talent_pool_applicant_1.talent_pool_ids, self.t_talent_pool_1, "The 'talent' should belong to the talent pool",
        )

        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids.add(self.t_talent_pool_2)
        wizard.applicant_ids = self.t_applicant_2
        talent_pool_applicant_2 = wizard.save()._add_applicants_to_pool()

        self.assertEqual(
            talent_pool_applicant_2, talent_pool_applicant_1, "A second talent for the same applicant should not have been created",
        )
        self.assertEqual(
            talent_pool_applicant_2.talent_pool_ids,
            self.t_talent_pool_1 | self.t_talent_pool_2,
            "The 'talent' should belong to both talent pools",
        )

    def test_add_multiple_applicants_to_multiple_talent_pools(self):
        """
        Test that multiple applicants are only duplicated once and linked to multiple pools when creating talents.
        """
        talent_pool_applicants = self.t_talent_pool_1.talent_ids | self.t_talent_pool_2.talent_ids
        self.assertFalse(talent_pool_applicants, "There should not be any applicants in the talent pools")

        with Form(self.env["talent.pool.add.applicants"]) as wizard:
            wizard.talent_pool_ids.add(self.t_talent_pool_1)
            wizard.talent_pool_ids.add(self.t_talent_pool_2)
            wizard.applicant_ids.add(self.t_applicant_1)
            wizard.applicant_ids.add(self.t_applicant_2)

        talent_pool_applicants = wizard.record._add_applicants_to_pool()

        self.assertTrue(
            talent_pool_applicants, "An applicant('talent') should be created when adding an applicant to a pool"
        )
        self.assertEqual(
            len(talent_pool_applicants),
            2,
            "Exactly two 'talents' should be created when adding two applicants to pools",
        )
        for applicant in talent_pool_applicants:
            self.assertEqual(
                applicant.talent_pool_ids,
                self.t_talent_pool_1 | self.t_talent_pool_2,
                f"Talent {applicant.partner_name} should belong to two talent pools",
            )

    def test_add_applicant_is_only_duplicated_once(self):
        """
        Test that a talent is not duplicated when added to two different pools in two different steps.
        """
        with Form(self.env["talent.pool.add.applicants"]) as wizard:
            wizard.talent_pool_ids = self.t_talent_pool_1
            wizard.applicant_ids = self.t_applicant_1
        tp_applicant_1 = wizard.record._add_applicants_to_pool()

        self.assertTrue(tp_applicant_1, "An applicant('talent') should be created when adding an applicant to a pool")
        self.assertEqual(
            len(tp_applicant_1), 1, "Exactly one 'talent' should be created when adding an applicant to a pool"
        )

        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids = self.t_talent_pool_1
        wizard.applicant_ids = self.t_applicant_1
        tp_applicant_2 = wizard.save()._add_applicants_to_pool()
        self.assertEqual(tp_applicant_1, tp_applicant_2, "A second talent for the same applicant should not have been created")
        self.assertEqual(
            self.env['hr.applicant'].search_count([('talent_pool_ids', 'in', self.t_talent_pool_1.id)]),
            1,
            "Exactly one talent should be created when adding an applicant to the same pool multiple times",
        )

        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids = self.t_talent_pool_2
        wizard.applicant_ids = tp_applicant_1
        tp_applicant_2 = wizard.save()._add_applicants_to_pool()

        self.assertEqual(tp_applicant_1, tp_applicant_2, "tp_applicant_1 and tp_applicant_2 should be the same record")
        self.assertEqual(
            tp_applicant_1.talent_pool_ids,
            self.t_talent_pool_1 | self.t_talent_pool_2,
            f"tp_applicant_1 should be linked to {self.t_talent_pool_1.name} and {self.t_talent_pool_2.name}",
        )

    def test_tags_are_added_to_talent(self):
        """
        Test that a tag is added to the talent but not the applicant when creating talents.
        """
        tag = self.env["hr.applicant.category"].create({"name": "Test Tag"})

        talent_pool_applicant = (
            self.env["talent.pool.add.applicants"]
            .create(
                {
                    "applicant_ids": self.t_applicant_1,
                    "talent_pool_ids": self.t_talent_pool_1,
                    "categ_ids": tag,
                }
            )
            ._add_applicants_to_pool()
        )
        self.assertTrue(talent_pool_applicant, "A 'talent' should have been created")
        self.assertFalse(self.t_applicant_1.categ_ids, "The original applicant should not have any linked tags")
        self.assertEqual(
            talent_pool_applicant.categ_ids, tag, "The 'talent' should have the tag 'Test Tag' linked to it"
        )

    def test_add_talent_to_one_job(self):
        """
        Test that a talent is duplicated when added to a job
        """
        self.t_applicant_1.attachment_ids = [Command.link(attachment_id) for attachment_id in self.env['ir.attachment'].create([{
            'name': 'Test',
            'res_model': self.t_applicant_1._name,
            'res_id': self.t_applicant_1.id
        }] * 2).ids]

        pool_wizard = Form(self.env["talent.pool.add.applicants"])
        pool_wizard.talent_pool_ids = self.t_talent_pool_1
        pool_wizard.applicant_ids = self.t_applicant_1
        talent_pool_applicant = pool_wizard.save()._add_applicants_to_pool()

        recuritment_stage = self.env["hr.recruitment.stage"].create({
            "name": "Recruitment Stage",
            "job_ids": self.t_job_2.ids,
            "template_id": self.mail_template.id,
            "sequence": 0,
        })

        self.assertEqual(
            len(talent_pool_applicant), 1, "Exactly one 'talent' should be created when adding an applicant to a pool"
        )
        self.assertEqual(
            len(talent_pool_applicant.attachment_ids), len(self.t_applicant_1.attachment_ids),
            "Talent Pool Applicant should have the same attachments then the original one."
        )

        job_wizard = Form(
            self.env["job.add.applicants"].with_context({"default_applicant_ids": talent_pool_applicant.ids})
        )
        job_wizard.job_ids = self.t_job_2
        job_2_applicant = job_wizard.save()._add_applicants_to_job()
        self.flush_tracking()

        all_applications = self.env["hr.applicant"].search(Domain("partner_name", "=", "Test Applicant 1"))
        self.assertEqual(
            len(all_applications),
            3,
            """There should be three applications with the name 'Test Applicant 1' - The original, the talent and the one created through the job.add.applicants wizard""",
        )
        self.assertEqual(
            job_2_applicant,
            self.t_job_2.application_ids,
            "Job_2_applicant, created through the wizard, should be linked to Job 2",
        )
        self.assertNotEqual(
            job_2_applicant, talent_pool_applicant, "Job_2_applicant and the talent should not be the same record"
        )

        # Make sure that the stage was populated correctly during creation not in compute,
        # If it was passed in creation the record will have the mail linked to the stage
        self.assertEqual(job_2_applicant.stage_id, recuritment_stage)
        self.assertEqual(job_2_applicant.message_ids[0].subject, self.mail_template.subject)

    def test_add_talent_to_multiple_jobs(self):
        """
        Test that a talent is duplicated multiple times when added to multiple jobs.
        """
        pool_wizard = Form(self.env["talent.pool.add.applicants"])
        pool_wizard.talent_pool_ids = self.t_talent_pool_1
        pool_wizard.applicant_ids = self.t_applicant_1
        pool_wizard.save().action_add_applicants_to_pool()

        talent_pool_applicant = self.t_talent_pool_1.talent_ids
        self.assertEqual(
            len(talent_pool_applicant), 1, "Exactly one 'talent' should be created when adding an applicant to a pool"
        )

        job_wizard = Form(
            self.env["job.add.applicants"].with_context({"default_applicant_ids": talent_pool_applicant.ids})
        )
        job_wizard.job_ids.add(self.t_job_2)
        job_wizard.job_ids.add(self.t_job_3)
        new_job_applicants = job_wizard.save()._add_applicants_to_job()

        all_applications = self.env["hr.applicant"].search(Domain("partner_name", "=", "Test Applicant 1"))
        self.assertEqual(
            len(all_applications),
            4,
            """There should be four applications with the name 'Test Applicant' - The original, the talent and one each for job_2 and job_3, created through the wizard""",
        )
        self.assertEqual(
            new_job_applicants.job_id,
            self.t_job_2 | self.t_job_3,
            "new_job_applicants, created through the wizard, should be linked to Job 2 and Job 3",
        )

    def test_add_multiple_talents_to_multiple_jobs(self):
        """
        Test that multiple talents are duplicated multiple times when added to multiple jobs.
        """
        pool_wizard = Form(self.env["talent.pool.add.applicants"])
        pool_wizard.talent_pool_ids = self.t_talent_pool_1
        pool_wizard.applicant_ids.add(self.t_applicant_1)
        pool_wizard.applicant_ids.add(self.t_applicant_2)
        talent_pool_applicants = pool_wizard.save()._add_applicants_to_pool()
        self.assertEqual(
            len(talent_pool_applicants), 2, "Exactly two 'talents' should be created when adding an applicant to a pool"
        )

        job_wizard = Form(
            self.env["job.add.applicants"].with_context({"default_applicant_ids": talent_pool_applicants.ids})
        )
        job_wizard.job_ids.add(self.t_job_2)
        job_wizard.job_ids.add(self.t_job_3)
        new_job_applicants = job_wizard.save()._add_applicants_to_job()

        all_a_1_applications = self.env["hr.applicant"].search(Domain("partner_name", "=", "Test Applicant 1"))
        all_a_2_applications = self.env["hr.applicant"].search(Domain("partner_name", "=", "Test Applicant 2"))
        self.assertEqual(
            len(all_a_1_applications),
            4,
            """There should be four applications with the name 'Test Applicant 1' - The original, the talent and one each for job_2 and job_3, created through the wizard""",
        )
        self.assertEqual(
            len(all_a_2_applications),
            4,
            """There should be four applications with the name 'Test Applicant 2' - The original, the talent and one each for job_2 and job_3, created through the wizard""",
        )
        new_job_applicants = new_job_applicants.mapped(lambda a: {"name": a.partner_name, "job": a.job_id})
        expected = [
            {"name": "Test Applicant 1", "job": self.t_job_2},
            {"name": "Test Applicant 1", "job": self.t_job_3},
            {"name": "Test Applicant 2", "job": self.t_job_2},
            {"name": "Test Applicant 2", "job": self.t_job_3},
        ]
        self.assertEqual(
            new_job_applicants,
            expected,
            "new_job_applicants, created through the wizard, should be linked to Job 2 and Job 3",
        )

    def test_update_applicant_after_adding_to_pool(self):
        """
        Test that an applicant's fields (e.g., email) can still be updated after adding them to a talent pool.
        """
        self.env["talent.pool.add.applicants"].create({
            "applicant_ids": self.t_applicant_1.ids,
            "talent_pool_ids": self.t_talent_pool_1,
        }).action_add_applicants_to_pool()

        new_email = "updated@gmail.com"
        self.t_applicant_1.write({"email_from": new_email})
        self.assertEqual(
            self.t_applicant_1.email_from,
            new_email,
            "The email_from field should be updated successfully",
        )

    def test_talent_must_have_at_least_one_pool(self):
        """
        Ensure that removing all the talent pool from a talent created raises a ValidationError.
        """
        wizard = Form(self.env["talent.pool.add.applicants"])
        wizard.talent_pool_ids.add(self.t_talent_pool_1)
        wizard.applicant_ids.add(self.t_applicant_1)
        talent = wizard.save()._add_applicants_to_pool()
        with self.assertRaises(ValidationError):
            talent.write({"talent_pool_ids": [(5, 0, 0)]})

    def test_attachment_following_applicant_from_pool(self):
        self.t_applicant_1.attachment_ids = [Command.link(attachment_id) for attachment_id in self.env['ir.attachment'].create([{
            'name': 'Test %s' % i,
            'res_model': self.t_applicant_1._name,
            'res_id': self.t_applicant_1.id
        } for i in range(2)]).ids]
        job_wizard = Form(
            self.env["job.add.applicants"].with_context({"default_applicant_ids": self.t_applicant_1.ids})
        )
        job_wizard.job_ids = self.t_job_2
        job_2_applicant = job_wizard.save()._add_applicants_to_job()
        # last attachment from origin applicant should be copied on the target applicant
        self.assertEqual(len(job_2_applicant.attachment_ids), 1)
        self.assertEqual(self.t_applicant_1.attachment_ids[0].name, job_2_applicant.attachment_ids.name)
        self.assertEqual(job_2_applicant.attachment_ids[0].res_id, job_2_applicant.id)
        # Just ensure that attachment from origin applicant still exists
        self.assertEqual(len(self.t_applicant_1.attachment_ids), 2)
        self.assertEqual(self.t_applicant_1.attachment_ids[0].res_id, self.t_applicant_1.id)

    def flush_tracking(self):
        """ Force the creation of tracking values. """
        self.env.flush_all()
        self.cr.flush()

    def test_copy_applicant(self):
        """Test that a talent should not be copied."""
        self.env["talent.pool.add.applicants"].create({
            "applicant_ids": self.t_applicant_1.ids,
            "talent_pool_ids": self.t_talent_pool_1.ids,
        }).action_add_applicants_to_pool()

        talent = self.t_applicant_1.pool_applicant_id

        with self.assertRaises(UserError, msg="You cannot duplicate the talent(s)."):
            talent.copy()
