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

import base64

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


@tagged('recruitment')
class TestRecruitment(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.company = cls.env['res.company'].create({
            'name': 'Company Test',
            'country_id': cls.env.ref('base.us').id,
        })
        cls.env.user.company_id = cls.company
        cls.env.user.company_ids = [Command.set(cls.company.ids)]

        cls.TEXT = base64.b64encode(bytes("hr_recruitment", 'utf-8'))
        cls.Attachment = cls.env['ir.attachment']

    def test_infer_applicant_lang_from_context(self):
        # Prerequisites
        self.env['res.lang']._activate_lang('pl_PL')
        self.env['res.lang']._activate_lang('en_US')
        self.env['ir.default'].set('res.partner', 'lang', 'en_US')

        # Creating an applicant will create a partner (email_from inverse)
        applicant = self.env['hr.applicant'].sudo().with_context(lang='pl_PL').create({
            'partner_name': 'Test Applicant',
            'email_from': "test_aplicant@example.com"
        })
        self.assertEqual(applicant.partner_id.lang, 'pl_PL', 'Context langague not used for partner creation')

    def test_duplicate_email(self):
        # Tests that duplicate email matching is case insesitive
        dup1, dup2, no_dup = self.env['hr.applicant'].create([
            {
                'partner_name': 'Application 1',
                'email_from': 'laurie.poiret@aol.ru',
            },
            {
                'partner_name': 'Application 2',
                'email_from': 'laurie.POIRET@aol.ru',
            },
            {
                'partner_name': 'Application 3',
                'email_from': 'laure.poiret@aol.ru',
            },
        ])
        self.assertEqual(dup1.application_count, 2)
        self.assertEqual(dup2.application_count, 2)
        self.assertEqual(no_dup.application_count, 1)

    def test_similar_applicants_count(self):
        """Test that we find same applicant based on simmilar mail or phone."""
        A, B, C, D, E, F, _ = self.env['hr.applicant'].create([
            {
                'active': False,  # Refused/archived application should still count
                'partner_name': 'Application A',
                'email_from': 'abc@odoo.com',
                'partner_phone': '123',
            },
            {
                'partner_name': 'Application B',
                'partner_phone': '456',
            },
            {
                'partner_name': 'Application C',
                'email_from': 'def@odoo.com',
                'partner_phone': '123',
            },
            {
                'partner_name': 'Application D',
                'email_from': 'abc@odoo.com',
                'partner_phone': '456',
            },
            {
                'partner_name': 'Application E',
                'partner_phone': '',
            },
            {
                'partner_name': 'Application F',
                'email_from': 'ghi@odoo.com',
                'partner_phone': '789',
            },
            {
                'partner_name': 'Application G',
            },
        ])
        self.assertEqual(A.application_count, 3)  # A, C, D
        self.assertEqual(B.application_count, 2)  # B, D
        self.assertEqual(C.application_count, 2)  # C, A
        self.assertEqual(D.application_count, 3)  # D, A, B
        self.assertEqual(E.application_count, 0)  # Should not match with E and G as there is no data to use for matching.
        self.assertEqual(F.application_count, 1)  # F

    def test_talent_pool_count(self):
        tp_A, tp_B = self.env["hr.talent.pool"].create([{"name": "Cool Pool"}, {"name": "Other Pool"}])
        t_A, t_B = self.env["hr.applicant"].create(
            [
                {
                    "partner_name": "Talent A",
                    "email_from": "abc@example.com",
                    "partner_phone": "1234",
                    "linkedin_profile": "linkedin/talent",
                    "talent_pool_ids": [tp_A.id, tp_B.id],
                },
                {
                    "partner_name": "Talent B",
                    "email_from": "talent_b@example.com",
                    "partner_phone": "9999",
                    "talent_pool_ids": [tp_B.id],
                },
            ]
        )
        # The only way to create a talent is through the wizards. Talents that are
        # created through the wizard also assign their own ID as pool_applicant_id
        t_A.pool_applicant_id = t_A.id
        t_B.pool_applicant_id = t_B.id

        A, B, C, D, E, F, G = self.env["hr.applicant"].create(
            [
                {"partner_name": "A", "pool_applicant_id": t_A.id},
                {
                    "partner_name": "B",
                    "email_from": "def@example.com",
                    "partner_phone": "6789",
                    "linkedin_profile": "linkedin/b",
                    "pool_applicant_id": t_A.id,
                },
                {
                    "partner_name": "C",
                    "email_from": "def@example.com",
                },
                {
                    "partner_name": "D",
                    "partner_phone": "6789",
                },
                {
                    "partner_name": "E",
                    "linkedin_profile": "linkedin/b",
                },
                {
                    "partner_name": "F",
                    "email_from": "not_linked@example.com",
                    "partner_phone": "00000",
                    "linkedin_profile": "linkedin/not_linked",
                },
                {"partner_name": "G", "pool_applicant_id": t_B.id},
            ]
        )
        self.assertEqual(t_A.talent_pool_count, 2)
        self.assertEqual(t_B.talent_pool_count, 1)
        self.assertEqual(A.talent_pool_count, 2)
        self.assertEqual(B.talent_pool_count, 2)
        self.assertEqual(C.talent_pool_count, 2)
        self.assertEqual(D.talent_pool_count, 2)
        self.assertEqual(E.talent_pool_count, 2)
        self.assertEqual(F.talent_pool_count, 0)
        self.assertEqual(G.talent_pool_count, 1)

    def test_compute_and_search_is_applicant_in_pool(self):
        """
        Test that the _compute_is_applicant_in_pool and _search_is_applicant_in_pool
        methods return correct information.
        An application is considered to be in a pool if it is either directly linked
        to a pool (through pool_applicant_id or talents_pool_ids) or shares a phone number,
        email or linkedin with another directly linked application.
        """
        talent_pool = self.env["hr.talent.pool"].create({"name": "Cool Pool"})
        job = self.env["hr.job"].create(
            {
                "name": "Cool Job",
            }
        )
        A, B, C, D, E, F, G, H = self.env["hr.applicant"].create(
            [
                {
                    "partner_name": "Talent A",
                    "email_from": "mainTalentEmail@example.com",
                    "talent_pool_ids": talent_pool.ids,
                },
                {
                    "partner_name": "Applicant 1 B",
                    "email_from": "otherTalentEmail@example.com",
                    "partner_phone": "1234",
                    "linkedin_profile": "linkedin.com/in/applicant",
                    "job_id": job.id,
                },
                {
                    "partner_name": "Applicant 1 C",
                    "email_from": "otherTalentEmail@example.com",
                    "job_id": job.id,
                },
                {
                    "partner_name": "Applicant 1 D",
                    "partner_phone": "1234",
                    "job_id": job.id,
                },
                {
                    "partner_name": "Applicant 1 E",
                    "linkedin_profile": "linkedin.com/in/applicant",
                    "job_id": job.id,
                },
                {
                    "partner_name": "A different applicant F",
                    "email_from": "differentEmail@example.com",
                    "partner_phone": "9876",
                    "linkedin_profile": "linkedin.com/in/NotAnApplicant",
                    "job_id": job.id,
                },
                {
                    "partner_name": "Talent With No information G",
                    "talent_pool_ids": talent_pool.ids,
                },
                {
                    "partner_name": "Applicant With No information H",
                },
            ]
        )
        B.pool_applicant_id = A.id
        H.pool_applicant_id = G.id

        # Testing the compute

        # A is directly linked to Cool Pool through talent_pool_ids
        self.assertTrue(A.is_applicant_in_pool)
        # B is directly linked to Cool Pool through pool_applicant_id
        self.assertTrue(B.is_applicant_in_pool)
        # C is indirectly linked through email to B who is directly linked
        self.assertTrue(C.is_applicant_in_pool)
        # D is indirectly linked through phone to B who is directly linked
        self.assertTrue(D.is_applicant_in_pool)
        # E is indirectly linked through linkedin to B who is directly linked
        self.assertTrue(E.is_applicant_in_pool)
        # F is not linked to a Pool
        self.assertFalse(F.is_applicant_in_pool)
        # G is directly linked to Cool Pool through talent_pool_ids
        self.assertTrue(G.is_applicant_in_pool)
        # H is directly linked to Cool Pool through pool_applicant_id
        self.assertTrue(H.is_applicant_in_pool)

        # Testing the search
        # Note: For some reason testing the search does not work if the compute
        #       is not tested first which is why these two tests are in one test.
        applicant = self.env["hr.applicant"]
        in_pool_domain = applicant._search_is_applicant_in_pool("in", [True])
        in_pool_applicants = applicant.search(Domain.AND([in_pool_domain, [("company_id", "=", self.env.company.id)]]))
        out_of_pool_applicants = applicant.search(Domain.AND([~Domain(in_pool_domain), [("company_id", "=", self.env.company.id)]]))
        self.assertCountEqual(in_pool_applicants, A | B | C | D | E | G | H)
        self.assertCountEqual(out_of_pool_applicants, F)

    def test_application_no_partner_duplicate(self):
        """ Test that when applying, the existing partner
            doesn't get duplicated.
        """
        applicant_data = {
            'partner_name': 'Test',
            'email_from': 'test@thisisatest.com',
        }
        # First application, a partner should be created
        self.env['hr.applicant'].create(applicant_data)
        partner_count = self.env['res.partner'].search_count([('email', '=', 'test@thisisatest.com')])
        self.assertEqual(partner_count, 1)
        # Second application, no partner should be created
        self.env['hr.applicant'].create(applicant_data)
        partner_count = self.env['res.partner'].search_count([('email', '=', 'test@thisisatest.com')])
        self.assertEqual(partner_count, 1)

    def test_target_on_application_hiring(self):
        """
        Test that the target is updated when hiring an applicant
        """
        job = self.env['hr.job'].create({
            'name': 'Test Job',
            'no_of_recruitment': 1,
        })
        applicant = self.env['hr.applicant'].create({
            'partner_name': 'Test Applicant',
            'job_id': job.id,
        })
        stage_new = self.env['hr.recruitment.stage'].create({
            'name': 'New',
            'sequence': 0,
            'hired_stage': False,
        })
        stage_hired = self.env['hr.recruitment.stage'].create({
            'name': 'Hired',
            'sequence': 1,
            'hired_stage': True,
        })
        self.assertEqual(job.no_of_recruitment, 1)
        applicant.stage_id = stage_hired
        self.assertEqual(job.no_of_recruitment, 0)

        applicant.stage_id = stage_new
        self.assertEqual(job.no_of_recruitment, 1)

    def test_open_refuse_applicant_wizard_without_partner_name(self):
        """Test opening the refuse wizard when the applicant has no partner_name."""
        applicant = self.env['hr.applicant'].create({
            'partner_phone': '123',
        })
        wizard = Form(self.env['applicant.get.refuse.reason'].with_context(
            default_applicant_ids=[applicant.id], active_test=False))

        wizard_applicant = wizard.applicant_ids[0]
        self.assertFalse(wizard_applicant.partner_name)

    def test_applicant_refuse_reason(self):

        refuse_reason = self.env['hr.applicant.refuse.reason'].create([{'name': 'Fired'}])

        app_1, app_2 = self.env['hr.applicant'].create([
            {
                'partner_name': 'Laurie Poiret',
                'email_from': 'laurie.poiret@aol.ru',
            },
            {
                'partner_name': 'Mitchell Admin',
                'email_from': 'mitchell_admin@example.com',
            },
        ])

        applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create([{
            'refuse_reason_id': refuse_reason.id,
            'applicant_ids': [app_1.id],
            'duplicates': True
        }])
        applicant_get_refuse_reason.action_refuse_reason_apply()
        self.assertFalse(self.env['hr.applicant'].search([('email_from', 'ilike', 'laurie.poiret@aol.ru')]))
        self.assertEqual(
            self.env['hr.applicant'].search([('email_from', 'ilike', 'mitchell_admin@example.com')]),
            app_2
        )

    def test_applicant_refuse_mail_from_template(self):
        mail_template = self.env['mail.template'].create({
            'name': 'Test template',
            'model_id': self.env['ir.model']._get('hr.applicant').id,
            'email_from': 'test@test.test',
        })
        refuse_reason = self.env['hr.applicant.refuse.reason'].create({
            'name': 'Not good',
        })
        applicant = self.env['hr.applicant'].create({
            'partner_name': 'Laurie Poiret',
            'email_from': 'laurie.poiret@aol.ru',
        })
        applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create([{
            'refuse_reason_id': refuse_reason.id,
            'applicant_ids': applicant.ids,
            'duplicates': True,
        }])
        mail_values = applicant_get_refuse_reason._prepare_mail_values(applicant)
        self.assertEqual(mail_values['email_from'], self.env.user.email_formatted)

        refuse_reason_template = self.env['hr.applicant.refuse.reason'].create({
            'name': 'Fired',
            'template_id': mail_template.id,
        })
        applicant_get_refuse_reason.refuse_reason_id = refuse_reason_template
        mail_values = applicant_get_refuse_reason._prepare_mail_values(applicant)
        self.assertEqual(mail_values['email_from'], 'test@test.test')

    def test_copy_attachments_while_creating_employee(self):
        """
        Test that attachments are copied when creating an employee from an applicant
        """
        applicant_1 = self.env['hr.applicant'].create({
            'partner_name': 'Applicant 1',
            'email_from': 'test_applicant@example.com'
        })
        applicant_attachment = self.Attachment.create({
            'datas': self.TEXT,
            'name': 'textFile.txt',
            'mimetype': 'text/plain',
            'res_model': applicant_1._name,
            'res_id': applicant_1.id
        })

        employee_applicant = applicant_1.create_employee_from_applicant()
        self.assertTrue(employee_applicant['res_id'])
        attachment_employee_applicant = self.Attachment.search([
            ('res_model', '=', employee_applicant['res_model']),
            ('res_id', '=', employee_applicant['res_id']),
        ])
        self.assertEqual(applicant_attachment['datas'], attachment_employee_applicant['datas'])

    def test_other_applications_count(self):
        """
        Test that the application_count field does not change
        when archiving or refusing a linked application.
        """

        A1, A2, A3 = self.env["hr.applicant"].create(
            [
                {"partner_name": "test", "email_from": "test@example.com"},
                {"partner_name": "test", "email_from": "test@example.com"},
                {"partner_name": "test", "email_from": "test@example.com"},
            ]
        )

        self.assertEqual(A1.application_count, 3)

        # Archive A2
        A2.action_archive()
        self.assertEqual(
            A1.application_count,
            3,
            "Application_count should not change when archiving a linked application",
        )
        # Refuse A3
        refuse_reason = self.env["hr.applicant.refuse.reason"].create([{"name": "Fired"}])
        applicant_get_refuse_reason = self.env["applicant.get.refuse.reason"].create(
            [
                {
                    "refuse_reason_id": refuse_reason.id,
                    "applicant_ids": [A3.id],
                }
            ]
        )
        applicant_get_refuse_reason.action_refuse_reason_apply()
        self.assertEqual(
            A1.application_count,
            3,
            "The other_applications_count should not change when refusing an application",
        )

    def test_open_other_applications_count(self):
        """
        The smart button labeled 'Other Applications N' (where N represents the number of
        other job applications linked to the same applicant) should, when clicked, open a list view
        displaying all related applications.

        This list should include both the N other applications and the current one,
        resulting in a total of N + 1 records.
        """

        A1, _, _ = self.env["hr.applicant"].create(
            [
                {"partner_name": "test", "email_from": "test@example.com"},
                {"partner_name": "test", "email_from": "test@example.com"},
                {"partner_name": "test", "email_from": "test@example.com"},
            ]
        )

        res = A1.action_open_applications()
        self.assertEqual(len(res['domain'][0][2]), 3, "The list view should display 3 applications")

    def test_applicant_modify_email_number(self):
        applicant = self.env['hr.applicant'].create({
            'partner_name': 'Mary Applicant',
            'email_from': 'applicant@example.com',
            'partner_phone': '123456789',
        })
        self.assertEqual(applicant.partner_id.email, 'applicant@example.com', "Email should have been set on the partner.")
        self.assertEqual(applicant.partner_id.phone, '123456789', "Phone should have been set on the partner.")

        applicant.email_from = 'applicant_diff@example.com'
        self.assertEqual(applicant.partner_id.email, 'applicant_diff@example.com', "Email should have been updated on the partner.")
        applicant.partner_phone = '987654321'
        self.assertEqual(applicant.partner_id.phone, '987654321', "Phone should have been updated on the partner.")

    def test_send_mail_when_refuse_applicant(self):
        mail_template = self.env['mail.template'].create({
            'name': 'Test template',
            'model_id': self.env['ir.model']._get('hr.applicant').id,
            'subject': 'Application refused: {{ object.partner_name }}',
        })

        refuse_reason = self.env['hr.applicant.refuse.reason'].create([{
            'name': 'Not good',
            'template_id': mail_template.id,
        }])

        app_1 = self.env['hr.applicant'].create({
            'partner_name': 'Mario',
            'email_from': 'super@mario.bros',
        })

        applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create({
            'refuse_reason_id': refuse_reason.id,
            'send_mail': True,
            'applicant_ids': [(6, 0, [app_1.id])],
        })
        applicant_get_refuse_reason._prepare_send_refusal_mails()
        mail = self.env['mail.mail'].search([('subject', '=', 'Application refused: Mario')], limit=1)
        self.assertEqual(mail.partner_ids, app_1.partner_id)
