import os
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.files.storage import default_storage
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework_simplejwt.tokens import RefreshToken

from apps.clients.models import Client
from apps.jobs.models import JobDescription, JDRecruiterAssignment

User = get_user_model()


class JobDescriptionCopyTests(APITestCase):
    def setUp(self):
        # Create user
        self.user = User.objects.create_user(
            username="testadmin",
            email="testuser@ta-ats.local",
            password="testpassword",
            first_name="Test",
            last_name="User"
        )
        self.user.role = "ADMIN"
        self.user.is_superuser = True
        self.user.is_staff = True
        self.user.save()

        # Recruiter user
        self.recruiter = User.objects.create_user(
            username="testrecruiter",
            email="recruiter@ta-ats.local",
            password="testpassword",
            first_name="Recruiter",
            last_name="One"
        )
        self.recruiter.role = "RECRUITER"
        self.recruiter.save()

        # Generate JWT and apply to client
        token = RefreshToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token.access_token}")

        # Create Client
        self.company = Client.objects.create(name="Acme Corp")

        # Create JobDescription
        self.job = JobDescription.objects.create(
            title="Next.js Developer",
            department="Engineering",
            location="Remote",
            experience_band="2-4 years",
            ctc_band="10-15 LPA",
            notice_period="30 days",
            shift="Day",
            must_have_skills="Next.js, React, TypeScript",
            good_to_have_skills="Tailwind CSS, Node.js",
            qualifications="B.Tech/MCA",
            working_days="5 days (Mon-Fri)",
            num_positions=2,
            certification="AWS Certified Developer",
            questions=["What is ISR in Next.js?", "Do you know Server Actions?"],
            rank_weights={"skills": 40, "experience": 30},
            work_details="We are looking for a Next.js developer...",
            status="Draft",
            priority="High",
            created_by=self.user,
            client=self.company
        )

    def test_published_jd_cannot_revert_to_draft(self):
        self.job.status = "Published"
        self.job.approval_status = "APPROVED"
        self.job.save()

        url = reverse("job-detail", args=[self.job.id])
        response = self.client.patch(url, {"status": "Draft"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("Published Job Description can only be changed to Closed", str(response.data))
        self.job.refresh_from_db()
        self.assertEqual(self.job.status, "Published")

    def test_published_jd_can_be_closed(self):
        self.job.status = "Published"
        self.job.approval_status = "APPROVED"
        self.job.save()

        url = reverse("job-detail", args=[self.job.id])
        response = self.client.patch(url, {"status": "Closed"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.job.refresh_from_db()
        self.assertEqual(self.job.status, "Closed")

    def test_copy_jd_success(self):
        # Assign recruiter to the original job
        JDRecruiterAssignment.objects.create(
            jd=self.job,
            recruiter=self.recruiter,
            assigned_by=self.user
        )

        url = reverse("job-copy-jd", args=[self.job.id])
        response = self.client.post(url, {}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(response.data["success"])
        
        new_jd_data = response.data["data"]
        self.assertEqual(new_jd_data["title"], "Next.js Developer Copy")
        self.assertEqual(new_jd_data["department"], "Engineering")
        self.assertEqual(new_jd_data["location"], "Remote")
        self.assertEqual(new_jd_data["status"], "Draft")
        self.assertEqual(new_jd_data["priority"], "High")
        self.assertEqual(new_jd_data["client"], self.company.id)

        # Check DB count
        self.assertEqual(JobDescription.objects.count(), 2)

        # Verify recruiter assignment was duplicated
        new_jd = JobDescription.objects.get(id=new_jd_data["id"])
        self.assertEqual(new_jd.assigned_recruiters.count(), 1)
        self.assertEqual(new_jd.assigned_recruiters.first(), self.recruiter)

    def test_copy_jd_title_increment(self):
        # Create a pre-existing "Next.js Developer Copy" JD for same client & location
        JobDescription.objects.create(
            title="Next.js Developer Copy",
            location="Remote",
            work_details="Details...",
            created_by=self.user,
            client=self.company
        )

        # Run copy again, it should increment to "Next.js Developer Copy 2"
        url = reverse("job-copy-jd", args=[self.job.id])
        response = self.client.post(url, {}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data["data"]["title"], "Next.js Developer Copy 2")

        # Create another one so "Next.js Developer Copy 2" exists too
        JobDescription.objects.create(
            title="Next.js Developer Copy 2",
            location="Remote",
            work_details="Details...",
            created_by=self.user,
            client=self.company
        )

        # Copy again, it should increment to "Next.js Developer Copy 3"
        response = self.client.post(url, {}, format="json")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data["data"]["title"], "Next.js Developer Copy 3")

    def test_copy_jd_attachment_duplication(self):
        # Create an attachment on the job
        file_content = b"Mock PDF/text content for JD"
        test_file = SimpleUploadedFile("sample_jd.txt", file_content, content_type="text/plain")
        self.job.attachment = test_file
        self.job.save()

        self.assertTrue(bool(self.job.attachment))
        original_file_path = self.job.attachment.name

        # Perform copy
        url = reverse("job-copy-jd", args=[self.job.id])
        response = self.client.post(url, {}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        new_jd_id = response.data["data"]["id"]
        new_jd = JobDescription.objects.get(id=new_jd_id)

        self.assertTrue(bool(new_jd.attachment))
        # Ensure the copied file has a different path (named after the new ID)
        self.assertNotEqual(new_jd.attachment.name, original_file_path)
        self.assertTrue(default_storage.exists(new_jd.attachment.name))

        # Clean up storage
        if default_storage.exists(self.job.attachment.name):
            default_storage.delete(self.job.attachment.name)
        if default_storage.exists(new_jd.attachment.name):
            default_storage.delete(new_jd.attachment.name)

    def test_sync_attachment_title_text(self):
        file_content = (
            b"Job Title: Original Title\n"
            b"Department: Eng\n"
            b"Location: Pune\n"
            b"2. WORK DETAILS & REQUIREMENTS\n"
            b"Old details line 1\n"
            b"3. SCREENING QUESTIONS & ANSWERS\n"
            b"Q: Old question?\n"
            b"A: Old answer.\n"
        )
        test_file = SimpleUploadedFile("sample_jd.txt", file_content, content_type="text/plain")
        self.job.attachment = test_file
        self.job.save()

        # Check that it synchronized all fields upon save
        self.job.attachment.open("r")
        synced_text = self.job.attachment.read()
        self.job.attachment.close()
        
        self.assertIn("Job Title: Next.js Developer", synced_text)
        self.assertIn("Department: Engineering", synced_text)
        self.assertIn("Location: Remote", synced_text)
        self.assertIn("We are looking for a Next.js developer...", synced_text)
        self.assertIn("Q: What is ISR in Next.js?", synced_text)
        self.assertIn("Q: Do you know Server Actions?", synced_text)

        # Update fields manually and save
        self.job.title = "Senior Next.js Architect"
        self.job.location = "Mumbai"
        self.job.work_details = "<p>Fully new description paragraph</p>"
        self.job.questions = [{"question": "What is hydration?", "answer": "Hydration is..."}]
        self.job.save()

        self.job.attachment.open("r")
        updated_text = self.job.attachment.read()
        self.job.attachment.close()
        
        self.assertIn("Job Title: Senior Next.js Architect", updated_text)
        self.assertIn("Location: Mumbai", updated_text)
        self.assertIn("Fully new description paragraph", updated_text)
        self.assertNotIn("We are looking for a Next.js developer...", updated_text)
        self.assertIn("Q: What is hydration?", updated_text)
        self.assertIn("A: Hydration is...", updated_text)

        # Clean up storage
        if default_storage.exists(self.job.attachment.name):
            default_storage.delete(self.job.attachment.name)

    def test_sync_attachment_title_docx(self):
        try:
            from docx import Document
        except ImportError:
            self.skipTest("python-docx not installed")
        import io
        
        doc = Document()
        doc.add_paragraph("Job Title: Original Docx Title")
        doc.add_paragraph("Department: Eng")
        
        # Table 0: details
        tbl = doc.add_table(rows=2, cols=2)
        tbl.rows[0].cells[0].text = "Job Title"
        tbl.rows[0].cells[1].text = "Original Docx Title"
        tbl.rows[1].cells[0].text = "Location"
        tbl.rows[1].cells[1].text = "Original Location"
        
        # Work details heading
        doc.add_paragraph("2. WORK DETAILS & REQUIREMENTS")
        doc.add_paragraph("Old details block content")
        
        # Questions heading
        doc.add_paragraph("3. SCREENING QUESTIONS & ANSWERS")
        
        # Table 1: Q&A
        tbl_qa = doc.add_table(rows=2, cols=1)
        tbl_qa.rows[0].cells[0].text = "Q: Old question"
        tbl_qa.rows[1].cells[0].text = "A: Old answer"
        
        out_io = io.BytesIO()
        doc.save(out_io)
        out_io.seek(0)
        
        test_file = SimpleUploadedFile("sample_jd.docx", out_io.read(), content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document")
        self.job.attachment = test_file
        self.job.save()

        # Check that it synchronized upon save
        self.job.attachment.open("rb")
        synced_docx_content = self.job.attachment.read()
        self.job.attachment.close()
        
        doc_read = Document(io.BytesIO(synced_docx_content))
        self.assertEqual(doc_read.paragraphs[0].text, "Job Title: Next.js Developer")
        self.assertEqual(doc_read.paragraphs[1].text, "Department: Engineering")
        self.assertEqual(doc_read.tables[0].rows[0].cells[1].text, "Next.js Developer")
        self.assertEqual(doc_read.tables[0].rows[1].cells[1].text, "Remote")
        self.assertEqual(doc_read.paragraphs[3].text, "We are looking for a Next.js developer...")
        self.assertEqual(doc_read.tables[1].rows[0].cells[0].text, "Q: What is ISR in Next.js?")
        self.assertEqual(doc_read.tables[1].rows[1].cells[0].text, "A: ")
        self.assertEqual(doc_read.tables[1].rows[2].cells[0].text, "Q: Do you know Server Actions?")
        self.assertEqual(doc_read.tables[1].rows[3].cells[0].text, "A: ")

        # Update and save again
        self.job.title = "Senior Next.js Architect"
        self.job.location = "Mumbai"
        self.job.work_details = "<p>Fully new description paragraph</p>"
        self.job.questions = [{"question": "What is hydration?", "answer": "Hydration is..."}]
        self.job.save()

        self.job.attachment.open("rb")
        updated_docx_content = self.job.attachment.read()
        self.job.attachment.close()

        doc_read2 = Document(io.BytesIO(updated_docx_content))
        self.assertEqual(doc_read2.paragraphs[0].text, "Job Title: Senior Next.js Architect")
        self.assertEqual(doc_read2.tables[0].rows[0].cells[1].text, "Senior Next.js Architect")
        self.assertEqual(doc_read2.tables[0].rows[1].cells[1].text, "Mumbai")
        self.assertEqual(doc_read2.paragraphs[3].text, "Fully new description paragraph")
        self.assertEqual(doc_read2.tables[1].rows[0].cells[0].text, "Q: What is hydration?")
        self.assertEqual(doc_read2.tables[1].rows[1].cells[0].text, "A: Hydration is...")

        # Clean up storage
        if default_storage.exists(self.job.attachment.name):
            default_storage.delete(self.job.attachment.name)


class JobDescriptionCandidateFilteringTests(APITestCase):
    def setUp(self):
        # Create candidate user
        self.candidate = User.objects.create_user(
            username="candidate@ta-ats.local",
            email="candidate@ta-ats.local",
            password="testpassword",
            first_name="Candidate",
            last_name="User"
        )
        self.candidate.role = "CANDIDATE"
        self.candidate.save()

        # Creator user
        self.creator = User.objects.create_user(
            username="creator@ta-ats.local",
            email="creator@ta-ats.local",
            password="testpassword",
        )
        self.creator.role = "ADMIN"
        self.creator.save()

        # Create different JDs
        self.published_job = JobDescription.objects.create(
            title="Published Next.js Developer",
            location="Remote",
            work_details="Work details...",
            status="Published",
            created_by=self.creator,
        )
        self.draft_job = JobDescription.objects.create(
            title="Draft React Developer",
            location="Remote",
            work_details="Work details...",
            status="Draft",
            created_by=self.creator,
        )


        token = RefreshToken.for_user(self.candidate)
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token.access_token}")

    def test_candidate_only_sees_published_jds(self):
        url = reverse("job-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # Candidate should only see the published job description
        data = response.data["data"]
        results = data["results"] if isinstance(data, dict) and "results" in data else data
        titles = [item["title"] for item in results]
        self.assertIn("Published Next.js Developer", titles)
        self.assertNotIn("Draft React Developer", titles)


from unittest.mock import patch
from django.core import mail

class RecruiterJDAssignmentEmailTests(APITestCase):
    def setUp(self):
        self.pm_user = User.objects.create_user(
            username="pmuser",
            email="pm.doe@example.com",
            password="testpassword",
            full_name="John Doe",
            role="PROJECT_MANAGER",
        )
        self.recruiter1 = User.objects.create_user(
            username="recruiter1",
            email="recruiter.a@example.com",
            password="testpassword",
            full_name="Alice Smith",
            role="RECRUITER",
        )
        self.recruiter2 = User.objects.create_user(
            username="recruiter2",
            email="recruiter.b@example.com",
            password="testpassword",
            full_name="Bob Jones",
            role="RECRUITER",
        )
        self.company = Client.objects.create(name="Employee Management System")
        self.job = JobDescription.objects.create(
            title="Senior React Developer",
            department="Engineering",
            location="Remote",
            work_details="Detailed JD responsibilities...",
            status="Draft",
            created_by=self.pm_user,
            client=self.company,
        )
        token = RefreshToken.for_user(self.pm_user)
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token.access_token}")

    def test_single_recruiter_assignment_sends_email(self):
        mail.outbox = []
        url = reverse("job-assign-recruiters", args=[self.job.id])
        response = self.client.post(url, {"recruiter_ids": [self.recruiter1.id]}, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # Check DB assignment exists
        self.assertTrue(JDRecruiterAssignment.objects.filter(jd=self.job, recruiter=self.recruiter1).exists())

        # Check outbox email
        self.assertEqual(len(mail.outbox), 1)
        email = mail.outbox[0]
        self.assertEqual(email.to, ["recruiter.a@example.com"])
        self.assertEqual(email.subject, "New Job Description Assigned to You")
        self.assertIn("Hello Alice Smith,", email.body)
        self.assertIn("Project Manager: John Doe", email.body)
        self.assertIn("Email: pm.doe@example.com", email.body)
        self.assertIn("JD Title: Senior React Developer", email.body)
        self.assertIn(f"JD ID: JD-{self.job.id:04d}", email.body)
        self.assertIn("Project: Employee Management System", email.body)
        self.assertIn("Department: Engineering", email.body)
        self.assertIn("Status: Assigned to Recruiter", email.body)

    def test_multiple_recruiters_assignment_sends_individual_emails(self):
        mail.outbox = []
        url = reverse("job-assign-recruiters", args=[self.job.id])
        response = self.client.post(
            url, {"recruiter_ids": [self.recruiter1.id, self.recruiter2.id]}, format="json"
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # Check outbox has 2 separate emails
        self.assertEqual(len(mail.outbox), 2)
        recipients = [m.to[0] for m in mail.outbox]
        self.assertIn("recruiter.a@example.com", recipients)
        self.assertIn("recruiter.b@example.com", recipients)

        # Each email has single recipient
        for m in mail.outbox:
            self.assertEqual(len(m.to), 1)

        # Check individual personalization
        r1_email = next(m for m in mail.outbox if m.to == ["recruiter.a@example.com"])
        self.assertIn("Hello Alice Smith,", r1_email.body)
        self.assertNotIn("Bob Jones", r1_email.body)

        r2_email = next(m for m in mail.outbox if m.to == ["recruiter.b@example.com"])
        self.assertIn("Hello Bob Jones,", r2_email.body)
        self.assertNotIn("Alice Smith", r2_email.body)

    def test_email_failure_does_not_break_assignment_or_other_emails(self):
        mail.outbox = []
        url = reverse("job-assign-recruiters", args=[self.job.id])

        from apps.notifications import services
        original_send = services.send_email_notification

        def side_effect(*args, **kwargs):
            if kwargs.get("to_emails") == "recruiter.a@example.com":
                raise Exception("SMTP Connection Failed")
            return original_send(*args, **kwargs)

        with patch("apps.notifications.services.send_email_notification", side_effect=side_effect):
            response = self.client.post(
                url, {"recruiter_ids": [self.recruiter1.id, self.recruiter2.id]}, format="json"
            )
            self.assertEqual(response.status_code, status.HTTP_200_OK)

        # Assignments should both exist in DB
        self.assertTrue(JDRecruiterAssignment.objects.filter(jd=self.job, recruiter=self.recruiter1).exists())
        self.assertTrue(JDRecruiterAssignment.objects.filter(jd=self.job, recruiter=self.recruiter2).exists())

        # Recruiter 2 received email
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, ["recruiter.b@example.com"])



