from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils import timezone
from django.core.files.uploadedfile import SimpleUploadedFile
from datetime import datetime, time, timedelta
import json
from unittest.mock import patch, MagicMock

User = get_user_model()

from .models import (
    JobPosting, Application, Person, ZoomMeeting, FormTemplate, FormStage, FormField,
    FormSubmission, FieldResponse, InterviewSchedule, ScheduledInterview,
    TrainingMaterial, Source, HiringAgency, MeetingComment
)
from .forms import (
    JobPostingForm, CandidateForm, ZoomMeetingForm, MeetingCommentForm,
    CandidateStageForm, InterviewScheduleForm, CandidateSignupForm
)
from .views import (
    ZoomMeetingListView, ZoomMeetingCreateView, job_detail, candidate_screening_view,
    candidate_exam_view, candidate_interview_view, api_schedule_candidate_meeting
)
from .views_frontend import CandidateListView, JobListView
from .utils import create_zoom_meeting, get_candidates_from_request


class BaseTestCase(TestCase):
    """Base test case setup with common test data"""

    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123',
            is_staff=True
        )

        # Create test data
        self.job = JobPosting.objects.create(
            title='Software Engineer',
            department='IT',
            job_type='FULL_TIME',
            workplace_type='REMOTE',
            location_country='Saudi Arabia',
            description='Job description',
            qualifications='Job qualifications',
            application_deadline=timezone.now() + timedelta(days=30),
            created_by=self.user
        )

        # Create a person first
        person = Person.objects.create(
            first_name='John',
            last_name='Doe',
            email='john@example.com',
            phone='1234567890'
        )

        self.candidate = Application.objects.create(
            person=person,
            resume=SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf'),
            job=self.job,
            stage='Applied'
        )

        self.zoom_meeting = ZoomMeeting.objects.create(
            topic='Interview with John Doe',
            start_time=timezone.now() + timedelta(hours=1),
            duration=60,
            timezone='UTC',
            join_url='https://zoom.us/j/123456789',
            meeting_id='123456789'
        )


class ModelTests(BaseTestCase):
    """Test cases for models"""

    def test_job_posting_creation(self):
        """Test JobPosting model creation"""
        self.assertEqual(self.job.title, 'Software Engineer')
        self.assertEqual(self.job.department, 'IT')
        self.assertIsNotNone(self.job.slug)
        self.assertEqual(self.job.status, 'DRAFT')

    def test_job_posting_unique_id_generation(self):
        """Test unique internal job ID generation"""
        self.assertTrue(self.job.internal_job_id.startswith('KAAUH'))
        self.assertIn(str(timezone.now().year), self.job.internal_job_id)

    def test_job_posting_methods(self):
        """Test JobPosting model methods"""
        # Test is_expired method
        self.assertFalse(self.job.is_expired())

        # Test location display
        self.assertIn('Saudi Arabia', self.job.get_location_display())

    def test_candidate_creation(self):
        """Test Candidate model creation"""
        self.assertEqual(self.candidate.first_name, 'John')
        self.assertEqual(self.candidate.stage, 'Applied')
        self.assertEqual(self.candidate.job, self.job)

    def test_candidate_stage_transitions(self):
        """Test candidate stage transition logic"""
        # Test current available stages
        available_stages = self.candidate.get_available_stages()
        self.assertIn('Exam', available_stages)
        self.assertIn('Interview', available_stages)

    def test_zoom_meeting_creation(self):
        """Test ZoomMeeting model creation"""
        self.assertEqual(self.zoom_meeting.topic, 'Interview with John Doe')
        self.assertEqual(self.zoom_meeting.duration, 60)
        self.assertIsNotNone(self.zoom_meeting.meeting_id)

    def test_template_creation(self):
        """Test FormTemplate model creation"""
        template = FormTemplate.objects.create(
            name='Test Template',
            job=self.job,
            created_by=self.user
        )
        self.assertEqual(template.name, 'Test Template')
        self.assertEqual(template.job, self.job)

    def test_scheduled_interview_creation(self):
        """Test ScheduledInterview model creation"""
        scheduled = ScheduledInterview.objects.create(
            candidate=self.candidate,
            job=self.job,
            zoom_meeting=self.zoom_meeting,
            interview_date=timezone.now().date(),
            interview_time=time(10, 0),
            status='scheduled'
        )
        self.assertEqual(scheduled.candidate, self.candidate)
        self.assertEqual(scheduled.status, 'scheduled')


class ViewTests(BaseTestCase):
    """Test cases for views"""

    def test_job_list_view(self):
        """Test JobListView"""
        response = self.client.get(reverse('job_list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Software Engineer')

    def test_job_list_search(self):
        """Test JobListView search functionality"""
        response = self.client.get(reverse('job_list'), {'search': 'Software'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Software Engineer')

    def test_job_detail_view(self):
        """Test job_detail view"""
        response = self.client.get(reverse('job_detail', kwargs={'slug': self.job.slug}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Software Engineer')
        self.assertContains(response, 'John Doe')

    def test_zoom_meeting_list_view(self):
        """Test ZoomMeetingListView"""
        response = self.client.get(reverse('list_meetings'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Interview with John Doe')

    def test_zoom_meeting_list_search(self):
        """Test ZoomMeetingListView search functionality"""
        response = self.client.get(reverse('list_meetings'), {'q': 'Interview'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Interview with John Doe')

    def test_zoom_meeting_list_filter_status(self):
        """Test ZoomMeetingListView status filter"""
        response = self.client.get(reverse('list_meetings'), {'status': 'waiting'})
        self.assertEqual(response.status_code, 200)

    def test_zoom_meeting_create_view(self):
        """Test ZoomMeetingCreateView"""
        self.client.login(username='testuser', password='testpass123')
        response = self.client.get(reverse('create_meeting'))
        self.assertEqual(response.status_code, 200)

    def test_candidate_screening_view(self):
        """Test candidate_screening_view"""
        response = self.client.get(reverse('candidate_screening_view', kwargs={'slug': self.job.slug}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'John Doe')

    def test_candidate_screening_view_filters(self):
        """Test candidate_screening_view with filters"""
        response = self.client.get(
            reverse('candidate_screening_view', kwargs={'slug': self.job.slug}),
            {'min_ai_score': '50', 'tier1_count': '5'}
        )
        self.assertEqual(response.status_code, 200)

    def test_candidate_exam_view(self):
        """Test candidate_exam_view"""
        response = self.client.get(reverse('candidate_exam_view', kwargs={'slug': self.job.slug}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'John Doe')

    def test_candidate_interview_view(self):
        """Test candidate_interview_view"""
        response = self.client.get(reverse('candidate_interview_view', kwargs={'slug': self.job.slug}))
        self.assertEqual(response.status_code, 200)

    @patch('recruitment.views.create_zoom_meeting')
    def test_schedule_candidate_meeting(self, mock_create_zoom):
        """Test api_schedule_candidate_meeting view"""
        mock_create_zoom.return_value = {
            'status': 'success',
            'meeting_details': {
                'meeting_id': '987654321',
                'join_url': 'https://zoom.us/j/987654321',
                'password': '123456'
            },
            'zoom_gateway_response': {'status': 'waiting'}
        }

        self.client.login(username='testuser', password='testpass123')
        data = {
            'start_time': (timezone.now() + timedelta(hours=2)).isoformat(),
            'duration': 60
        }
        response = self.client.post(
            reverse('api_schedule_candidate_meeting',
                   kwargs={'job_slug': self.job.slug, 'candidate_pk': self.candidate.pk}),
            data
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'success')



class FormTests(BaseTestCase):
    """Test cases for forms"""

    def test_job_posting_form(self):
        """Test JobPostingForm"""
        form_data = {
            'title': 'New Job Title',
            'department': 'New Department',
            'job_type': 'FULL_TIME',
            'workplace_type': 'REMOTE',
            'location_city': 'Riyadh',
            'location_state': 'Riyadh',
            'location_country': 'Saudi Arabia',
            'description': 'Job description with at least 20 characters to meet validation requirements',
            'qualifications': 'Job qualifications',
            'salary_range': '5000-7000',
            'application_deadline': '2025-12-31',
            'max_applications': '100',
            'open_positions': '2',
            'hash_tags': '#hiring,#jobopening'
        }
        form = JobPostingForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_candidate_form(self):
        """Test CandidateForm"""
        form_data = {
            'job': self.job.id,
            'first_name': 'Jane',
            'last_name': 'Smith',
            'phone': '9876543210',
            'email': 'jane@example.com',
            'resume': SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf')
        }
        form = CandidateForm(data=form_data, files=form_data)
        self.assertTrue(form.is_valid())

    def test_zoom_meeting_form(self):
        """Test ZoomMeetingForm"""
        form_data = {
            'topic': 'Test Meeting',
            'start_time': (timezone.now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M'),
            'duration': 60
        }
        form = ZoomMeetingForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_meeting_comment_form(self):
        """Test MeetingCommentForm"""
        form_data = {
            'content': 'This is a test comment'
        }
        form = MeetingCommentForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_candidate_stage_form(self):
        """Test CandidateStageForm with valid transition"""
        form_data = {
            'stage': 'Exam'
        }
        form = CandidateStageForm(data=form_data, instance=self.candidate)
        self.assertTrue(form.is_valid())

    def test_interview_schedule_form(self):
        """Test InterviewScheduleForm"""
        # Update candidate to Interview stage first
        self.candidate.stage = 'Interview'
        self.candidate.save()

        form_data = {
            'candidates': [self.candidate.id],
            'start_date': (timezone.now() + timedelta(days=1)).date(),
            'end_date': (timezone.now() + timedelta(days=7)).date(),
            'working_days': [0, 1, 2, 3, 4],  # Monday to Friday
        }
        form = InterviewScheduleForm(slug=self.job.slug, data=form_data)
        self.assertTrue(form.is_valid())

    def test_candidate_signup_form_valid(self):
        """Test CandidateSignupForm with valid data"""
        form_data = {
            'first_name': 'John',
            'last_name': 'Doe',
            'email': 'john.doe@example.com',
            'phone': '+1234567890',
            'password': 'SecurePass123',
            'confirm_password': 'SecurePass123'
        }
        form = CandidateSignupForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_candidate_signup_form_password_mismatch(self):
        """Test CandidateSignupForm with password mismatch"""
        form_data = {
            'first_name': 'John',
            'last_name': 'Doe',
            'email': 'john.doe@example.com',
            'phone': '+1234567890',
            'password': 'SecurePass123',
            'confirm_password': 'DifferentPass123'
        }
        form = CandidateSignupForm(data=form_data)
        self.assertFalse(form.is_valid())
        self.assertIn('Passwords do not match', str(form.errors))


class IntegrationTests(BaseTestCase):
    """Integration tests for multiple components"""

    def test_candidate_journey(self):
        """Test the complete candidate journey from application to interview"""
        # 1. Create candidate
        person = Person.objects.create(
            first_name='Jane',
            last_name='Smith',
            email='jane@example.com',
            phone='9876543210'
        )
        candidate = Application.objects.create(
            person=person,
            resume=SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf'),
            job=self.job,
            stage='Applied'
        )

        # 2. Move to Exam stage
        candidate.stage = 'Exam'
        candidate.save()

        # 3. Move to Interview stage
        candidate.stage = 'Interview'
        candidate.save()

        # 4. Create interview schedule
        scheduled_interview = ScheduledInterview.objects.create(
            candidate=candidate,
            job=self.job,
            zoom_meeting=self.zoom_meeting,
            interview_date=timezone.now().date(),
            interview_time=time(10, 0),
            status='scheduled'
        )

        # 5. Verify all stages and relationships
        self.assertEqual(Application.objects.count(), 2)
        self.assertEqual(ScheduledInterview.objects.count(), 1)
        self.assertEqual(candidate.stage, 'Interview')
        self.assertEqual(scheduled_interview.candidate, candidate)

    def test_meeting_candidate_association(self):
        """Test the association between meetings and candidates"""
        # Create a scheduled interview
        scheduled_interview = ScheduledInterview.objects.create(
            candidate=self.candidate,
            job=self.job,
            zoom_meeting=self.zoom_meeting,
            interview_date=timezone.now().date(),
            interview_time=time(10, 0),
            status='scheduled'
        )

        # Verify the relationship
        self.assertEqual(self.zoom_meeting.interview, scheduled_interview)
        self.assertEqual(self.candidate.get_meetings().count(), 1)

    def test_form_submission_candidate_creation(self):
        """Test creating a candidate through form submission"""
        # Create a form template
        template = FormTemplate.objects.create(
            job=self.job,
            name='Application Form',
            created_by=self.user,
            is_active=True
        )

        # Create form stages and fields
        stage = FormStage.objects.create(
            template=template,
            name='Personal Information',
            order=0
        )

        FormField.objects.create(
            stage=stage,
            label='First Name',
            field_type='text',
            order=0
        )
        FormField.objects.create(
            stage=stage,
            label='Last Name',
            field_type='text',
            order=1
        )
        FormField.objects.create(
            stage=stage,
            label='Email',
            field_type='email',
            order=2
        )

        # Submit form data
        form_data = {
            'field_1': 'New',
            'field_2': 'Candidate',
            'field_3': 'new@example.com'
        }

        response = self.client.post(
            reverse('application_submit', kwargs={'template_id': template.id}),
            form_data
        )

        # Verify candidate was created
        self.assertEqual(Application.objects.filter(person__email='new@example.com').count(), 1)


class PerformanceTests(BaseTestCase):
    """Basic performance tests"""

    def test_large_dataset_pagination(self):
        """Test pagination with large datasets"""
        # Create many candidates
        for i in range(100):
            person = Person.objects.create(
                first_name=f'Candidate{i}',
                last_name=f'Test{i}',
                email=f'candidate{i}@example.com',
                phone=f'123456789{i}'
            )
            Application.objects.create(
                person=person,
                resume=SimpleUploadedFile(f'resume{i}.pdf', b'file_content', content_type='application/pdf'),
                job=self.job,
                stage='Applied'
            )

        # Test pagination
        response = self.client.get(reverse('candidate_list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Candidate')


class AuthenticationTests(BaseTestCase):
    """Authentication and permission tests"""

    def test_unauthorized_access(self):
        """Test that unauthorized users cannot access protected views"""
        # Create a non-staff user
        regular_user = User.objects.create_user(
            username='regularuser',
            email='regular@example.com',
            password='testpass123'
        )

        # Try to access a view that requires staff privileges
        self.client.login(username='regularuser', password='testpass123')
        response = self.client.get(reverse('job_list'))
        # Should still be accessible for now (can be adjusted based on actual requirements)
        self.assertEqual(response.status_code, 200)

    def test_login_required(self):
        """Test that login is required for certain operations"""
        # Test form submission without login
        template = FormTemplate.objects.create(
            job=self.job,
            name='Test Template',
            created_by=self.user,
            is_active=True
        )

        response = self.client.post(
            reverse('application_submit', kwargs={'template_id': template.id}),
            {}
        )
        # Should redirect to login page
        self.assertEqual(response.status_code, 302)


class EdgeCaseTests(BaseTestCase):
    """Tests for edge cases and error handling"""

    def test_invalid_job_id(self):
        """Test handling of invalid job slug"""
        response = self.client.get(reverse('job_detail', kwargs={'slug': 'invalid-slug'}))
        self.assertEqual(response.status_code, 404)

    def test_meeting_past_time(self):
        """Test handling of meeting with past time"""
        # This would be tested in the view that validates meeting time
        pass

    def test_duplicate_submission(self):
        """Test handling of duplicate form submissions"""
        # Create form template
        template = FormTemplate.objects.create(
            job=self.job,
            name='Test Template',
            created_by=self.user,
            is_active=True
        )

        # Submit form twice
        response1 = self.client.post(
            reverse('application_submit', kwargs={'template_id': template.id}),
            {'field_1': 'John', 'field_2': 'Doe'}
        )

        # This should be handled by the view logic
        # Currently, it would create a duplicate candidate
        # We can add validation to prevent this if needed

    def test_invalid_stage_transition(self):
        """Test invalid candidate stage transitions"""
        # Try to transition from Interview back to Applied (should be invalid)
        self.candidate.stage = 'Interview'
        self.candidate.save()

        # The model should prevent this through validation
        # This would be tested in the model's clean method or view logic
        pass


class UtilityFunctionTests(BaseTestCase):
    """Tests for utility functions"""

    @patch('recruitment.views.create_zoom_meeting')
    def test_create_zoom_meeting_utility(self, mock_create):
        """Test the create_zoom_meeting utility function"""
        mock_create.return_value = {
            'status': 'success',
            'meeting_details': {
                'meeting_id': '123456789',
                'join_url': 'https://zoom.us/j/123456789'
            }
        }

        result = create_zoom_meeting(
            topic='Test Meeting',
            start_time=timezone.now() + timedelta(hours=1),
            duration=60
        )

        self.assertEqual(result['status'], 'success')
        self.assertIn('meeting_id', result['meeting_details'])

    def test_get_candidates_from_request(self):
        """Test the get_candidates_from_request utility function"""
        # This would be tested with a request that has candidate_ids
        pass


# Factory classes for test data (can be expanded with factory_boy)
class TestFactories:
    """Factory methods for creating test data"""

    @staticmethod
    def create_job_posting(**kwargs):
        defaults = {
            'title': 'Test Job',
            'department': 'Test Department',
            'job_type': 'FULL_TIME',
            'workplace_type': 'ON_SITE',
            'location_country': 'Saudi Arabia',
            'description': 'Test description',
            'created_by': User.objects.create_user('factoryuser', 'factory@example.com', 'password')
        }
        defaults.update(kwargs)
        return JobPosting.objects.create(**defaults)

    @staticmethod
    def create_candidate(**kwargs):
        job = TestFactories.create_job_posting()
        person = Person.objects.create(
            first_name='Test',
            last_name='Candidate',
            email='test@example.com',
            phone='1234567890'
        )
        defaults = {
            'person': person,
            'job': job,
            'stage': 'Applied',
            'resume': SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf')
        }
        defaults.update(kwargs)
        return Application.objects.create(**defaults)

    @staticmethod
    def create_zoom_meeting(**kwargs):
        defaults = {
            'topic': 'Test Meeting',
            'start_time': timezone.now() + timedelta(hours=1),
            'duration': 60,
            'timezone': 'UTC',
            'join_url': 'https://zoom.us/j/test123',
            'meeting_id': 'test123'
        }
        defaults.update(kwargs)
        return ZoomMeeting.objects.create(**defaults)


# Test runner configuration (can be added to settings)
"""
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
TEST_DISCOVER_TOPS = ['recruitment']
"""
