# exams/models.py

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
from academics.models import (
    AcademicYear,
    AcademicClass,
    Subject,
    StudentEnrollment,
    SubjectTeacher,
)
class ExamType(models.Model):
    """
    Types of exams (e.g., Quarterly, Half-Yearly, Annual, Unit Test, etc.)
    """

    name = models.CharField(max_length=100)
    code = models.CharField(max_length=20, unique=True)

    # Exam characteristics
    is_terminal = models.BooleanField(
        default=False, help_text="Whether this is a terminal exam (like final exam)"
    )
    is_mid_term = models.BooleanField(
        default=False, help_text="Whether this is a mid-term exam"
    )
    is_continuous_assessment = models.BooleanField(
        default=False, help_text="Whether this is part of continuous assessment"
    )

    # Weightage in overall grade
    default_weightage = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=100.00,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
    )

    # Grading system
    grading_system = models.CharField(
        max_length=20,
        choices=[
            ("marks", "Marks Based"),
            ("grade", "Grade Based"),
            ("cgpa", "CGPA Based"),
        ],
        default="marks",
    )

    max_marks = models.PositiveIntegerField(default=100)
    passing_marks = models.PositiveIntegerField(default=35)

    # Academic term association (optional)
    academic_term = models.ForeignKey(
        "academics.AcademicTerm",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="exam_types",
    )

    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return f"{self.name} ({self.code})"


class Exam(models.Model):
    """
    Main exam instance (e.g., Quarterly Exam 2024, Unit Test 1)
    """

    # Exam identification
    name = models.CharField(max_length=200)
    code = models.CharField(max_length=50)

    # Relationships
    exam_type = models.ForeignKey(
        ExamType, on_delete=models.CASCADE, related_name="exams"
    )

    academic_year = models.ForeignKey(
        AcademicYear, on_delete=models.CASCADE, related_name="exams"
    )

    academic_class = models.ForeignKey(
        AcademicClass, on_delete=models.CASCADE, related_name="exams"
    )

    # Exam dates
    start_date = models.DateField()
    end_date = models.DateField()

    # Exam settings
    weightage = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=100.00,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
    )

    # Status
    status = models.CharField(
        max_length=20,
        choices=[
            ("scheduled", "Scheduled"),
            ("ongoing", "Ongoing"),
            ("completed", "Completed"),
            ("cancelled", "Cancelled"),
            ("results_published", "Results Published"),
        ],
        default="scheduled",
    )

    # Results publishing
    results_published_date = models.DateTimeField(null=True, blank=True)

    # Additional info
    instructions = models.TextField(blank=True)

    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-start_date"]
        unique_together = [
            ["academic_year", "academic_class", "name"],
            ["academic_year", "academic_class", "code"],
        ]

    def __str__(self):
        return f"{self.name} - {self.academic_class} ({self.academic_year})"

    @property
    def is_results_published(self):
        return self.status == "results_published"

    @property
    def duration_days(self):
        if self.start_date and self.end_date:
            return (self.end_date - self.start_date).days + 1
        return 0

    @property
    def total_registered_students(self):
        """Get count of registered students for this exam"""
        return self.student_registrations.filter(is_registered=True).count()

    @property
    def total_eligible_students(self):
        """Get count of eligible students in the class"""
        return self.academic_class.enrollments.filter(is_active=True).count()

    @property
    def marks_entry_status(self):
        """
        Whether every registered student has a marks entry (a score, or an
        absence) recorded for every subject of this exam. Used to block
        result calculation while entries are still pending.
        """
        subjects = self.exam_subjects.filter(is_active=True)
        registrations = self.student_registrations.filter(is_registered=True)
        total_expected = subjects.count() * registrations.count()

        if total_expected == 0:
            return {"total_expected": 0, "entered": 0, "pending": 0, "complete": False}

        entered = StudentMarks.objects.filter(
            exam_subject__in=subjects,
            student_enrollment_id__in=registrations.values_list(
                "student_enrollment_id", flat=True
            ),
        ).filter(
            models.Q(obtained_marks__isnull=False) | models.Q(is_absent=True)
        ).count()

        pending = max(total_expected - entered, 0)
        return {
            "total_expected": total_expected,
            "entered": entered,
            "pending": pending,
            "complete": pending == 0,
        }


class ExamClassRegistration(models.Model):
    """
    Class-wise exam registration - register all students in a class at once
    """

    exam = models.ForeignKey(
        Exam, on_delete=models.CASCADE, related_name="class_registrations"
    )

    academic_class = models.ForeignKey(
        AcademicClass, on_delete=models.CASCADE, related_name="exam_class_registrations"
    )

    # Registration details
    registration_date = models.DateTimeField(auto_now_add=True)
    registered_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        related_name="registered_exams",
    )

    # Hall ticket prefix for auto-generation
    hall_ticket_prefix = models.CharField(max_length=20, blank=True)
    seat_number_prefix = models.CharField(max_length=20, blank=True)

    # Registration status
    is_registered = models.BooleanField(default=True)

    # Bulk operations
    auto_generate_hall_tickets = models.BooleanField(default=True)
    auto_generate_seat_numbers = models.BooleanField(default=False)

    # Additional info
    notes = models.TextField(blank=True)

    class Meta:
        unique_together = ["exam", "academic_class"]

    def __str__(self):
        return f"{self.exam} - {self.academic_class} - Class Registration"

    def register_all_students(self):
        """
        Register all active students in the class for this exam
        """
        enrollments = self.academic_class.enrollments.filter(is_active=True)
        created_count = 0
        updated_count = 0

        for enrollment in enrollments:
            registration, created = StudentExamRegistration.objects.get_or_create(
                student_enrollment=enrollment,
                exam=self.exam,
                defaults={
                    "is_registered": True,
                    "registration_type": "class_based",
                    "class_registration": self,
                },
            )

            if created:
                created_count += 1
            elif not registration.is_registered:
                registration.is_registered = True
                registration.save()
                updated_count += 1

            # Hall tickets / seat numbers are issued here because there is no
            # separate admin registrations page — registration happens
            # automatically when the exam is scheduled.
            changed = False
            if self.auto_generate_hall_tickets and not registration.hall_ticket_number:
                registration.generate_hall_ticket(self.hall_ticket_prefix or None)
                changed = True
            if self.auto_generate_seat_numbers and not registration.seat_number:
                registration.generate_seat_number(self.seat_number_prefix or None)
                changed = True
            if changed:
                registration.save()

        return created_count, updated_count

    def unregister_all_students(self):
        """
        Unregister all students in the class from this exam
        """
        registrations = StudentExamRegistration.objects.filter(
            student_enrollment__academic_class=self.academic_class, exam=self.exam
        )

        count = registrations.update(is_registered=False)
        return count


class StudentExamRegistration(models.Model):
    """
    Individual student registration for exams
    """

    REGISTRATION_TYPES = [
        ("individual", "Individual Registration"),
        ("class_based", "Class Based Registration"),
        ("bulk_upload", "Bulk Upload Registration"),
    ]

    student_enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="exam_registrations"
    )

    exam = models.ForeignKey(
        Exam, on_delete=models.CASCADE, related_name="student_registrations"
    )

    class_registration = models.ForeignKey(
        ExamClassRegistration,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="student_registrations",
        help_text="If this registration came from class-wise registration",
    )

    registration_date = models.DateTimeField(auto_now_add=True)
    registration_type = models.CharField(
        max_length=20, choices=REGISTRATION_TYPES, default="individual"
    )
    is_registered = models.BooleanField(default=True)

    # Hall ticket / admit card
    hall_ticket_number = models.CharField(max_length=50, blank=True)
    seat_number = models.CharField(max_length=50, blank=True)

    # Special accommodations
    needs_special_accommodation = models.BooleanField(default=False)
    accommodation_details = models.TextField(blank=True)

    # Registration metadata
    registered_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="registered_students",
    )

    notes = models.TextField(blank=True)

    class Meta:
        unique_together = ["student_enrollment", "exam"]

    def __str__(self):
        return f"{self.student_enrollment} - {self.exam} - {self.get_registration_type_display()}"

    def generate_hall_ticket(self, prefix=None):
        """
        Auto-generate hall ticket number
        """
        if not prefix:
            year = self.exam.academic_year.code or self.exam.academic_year.name
            prefix = f"HT-{year}-{self.exam.code}"

        self.hall_ticket_number = f"{prefix}-{self.student_enrollment.roll_number}"
        return self.hall_ticket_number

    def generate_seat_number(self, prefix=None):
        """
        Auto-generate seat number
        """
        if not prefix:
            year = self.exam.academic_year.code or self.exam.academic_year.name
            prefix = f"SEAT-{year}"

        self.seat_number = f"{prefix}-{self.student_enrollment.roll_number}"
        return self.seat_number


class ExamSubject(models.Model):
    """
    Subjects for a specific exam with subject-specific settings
    """

    exam = models.ForeignKey(
        Exam, on_delete=models.CASCADE, related_name="exam_subjects"
    )

    subject = models.ForeignKey(
        Subject, on_delete=models.CASCADE, related_name="exam_subjects"
    )

    # Subject-specific exam settings
    max_marks = models.PositiveIntegerField(default=100)
    passing_marks = models.PositiveIntegerField(default=35)

    # Exam schedule — left blank until assigned via the Exam Timetable page
    exam_date = models.DateField(null=True, blank=True)
    start_time = models.TimeField(null=True, blank=True)
    duration_minutes = models.PositiveIntegerField(
        default=180, help_text="Duration in minutes"  # 3 hours default
    )

    # Room allocation
    room_number = models.CharField(max_length=50, blank=True)

    # Invigilators (many-to-many)
    invigilators = models.ManyToManyField(
        "people.Teacher", blank=True, related_name="invigilated_exams"
    )

    # Teacher in charge
    teacher_in_charge = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="exam_subjects_charged",
    )

    # Additional settings
    extra_instructions = models.TextField(blank=True)

    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["exam_date", "start_time"]
        unique_together = ["exam", "subject"]

    def __str__(self):
        return f"{self.exam} - {self.subject}"

    @property
    def end_time(self):
        """Calculate end time based on start time and duration"""
        from datetime import datetime, timedelta

        if self.start_time and self.duration_minutes:
            dt = datetime.combine(datetime.today(), self.start_time)
            end_dt = dt + timedelta(minutes=self.duration_minutes)
            return end_dt.time()
        return None


class StudentMarks(models.Model):
    """
    Individual student marks for each exam subject
    """

    student_enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="exam_marks"
    )

    exam_subject = models.ForeignKey(
        ExamSubject, on_delete=models.CASCADE, related_name="student_marks"
    )

    # Marks obtained
    obtained_marks = models.DecimalField(
        max_digits=6,
        decimal_places=2,
        null=True,
        blank=True,
        validators=[MinValueValidator(0)],
    )

    # Grade details (if using grade system)
    grade = models.CharField(max_length=10, blank=True)
    grade_point = models.DecimalField(
        max_digits=4,
        decimal_places=2,
        null=True,
        blank=True,
        validators=[MinValueValidator(0), MaxValueValidator(10)],
    )

    # Status
    is_absent = models.BooleanField(default=False)
    is_withheld = models.BooleanField(
        default=False,
        help_text="Whether marks are withheld due to disciplinary reasons",
    )
    is_revaluation_applied = models.BooleanField(default=False)

    # Marks entry details
    entered_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        related_name="entered_marks",
    )

    entry_date = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)

    # Revaluation details (if any)
    revaluation_marks = models.DecimalField(
        max_digits=6, decimal_places=2, null=True, blank=True
    )
    revaluation_reason = models.TextField(blank=True)
    revaluation_date = models.DateTimeField(null=True, blank=True)

    # Remarks
    remarks = models.TextField(blank=True)

    class Meta:
        unique_together = ["student_enrollment", "exam_subject"]

    def __str__(self):
        return f"{self.student_enrollment} - {self.exam_subject}"

    @property
    def is_passed(self):
        if self.obtained_marks is not None:
            return self.obtained_marks >= self.exam_subject.passing_marks
        return False

    @property
    def percentage(self):
        if self.obtained_marks is not None and self.exam_subject.max_marks > 0:
            return (self.obtained_marks / self.exam_subject.max_marks) * 100
        return None


class ExamResult(models.Model):
    """
    Consolidated result for a student in an exam
    """

    student_enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="exam_results"
    )

    exam = models.ForeignKey(
        Exam, on_delete=models.CASCADE, related_name="student_results"
    )

    # Aggregated results
    total_marks = models.DecimalField(
        max_digits=8, decimal_places=2, null=True, blank=True
    )
    total_max_marks = models.DecimalField(
        max_digits=8, decimal_places=2, null=True, blank=True
    )
    percentage = models.DecimalField(
        max_digits=5, decimal_places=2, null=True, blank=True
    )

    # Overall grade
    overall_grade = models.CharField(max_length=10, blank=True)
    overall_grade_point = models.DecimalField(
        max_digits=4, decimal_places=2, null=True, blank=True
    )

    # Result status
    result_status = models.CharField(
        max_length=20,
        choices=[
            ("pass", "Pass"),
            ("fail", "Fail"),
            ("absent", "Absent"),
            ("withheld", "Withheld"),
            ("promoted", "Promoted"),
            ("detained", "Detained"),
        ],
        default="pass",
    )

    # Rank
    rank = models.PositiveIntegerField(null=True, blank=True)

    # Additional info
    remarks = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ["student_enrollment", "exam"]

    def __str__(self):
        return f"{self.student_enrollment} - {self.exam} - {self.result_status}"


class ExamAttendance(models.Model):
    """
    Track attendance for each exam subject
    """

    student_enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="exam_attendance"
    )

    exam_subject = models.ForeignKey(
        ExamSubject, on_delete=models.CASCADE, related_name="attendance"
    )

    is_present = models.BooleanField(default=True)
    arrival_time = models.TimeField(null=True, blank=True)
    departure_time = models.TimeField(null=True, blank=True)

    # Late arrival / early departure
    is_late = models.BooleanField(default=False)
    late_minutes = models.PositiveIntegerField(default=0)
    early_departure_minutes = models.PositiveIntegerField(default=0)

    remarks = models.TextField(blank=True)

    marked_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        related_name="marked_attendance",
    )

    marked_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ["student_enrollment", "exam_subject"]

    def __str__(self):
        return f"{self.student_enrollment} - {self.exam_subject} - {'Present' if self.is_present else 'Absent'}"


class ExamGradeSystem(models.Model):
    """
    Grade system configuration for marks to grade conversion
    """

    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)

    # Grade ranges
    grade_ranges = models.JSONField(
        help_text="JSON field storing grade ranges e.g., {'A+': [90,100], 'A': [80,89]}"
    )

    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class ExamConfiguration(models.Model):
    """
    School-level exam configuration settings
    """

    academic_year = models.ForeignKey(
        AcademicYear, on_delete=models.CASCADE, related_name="exam_configurations"
    )

    # Default grade system
    default_grade_system = models.ForeignKey(
        ExamGradeSystem,
        on_delete=models.SET_NULL,
        null=True,
        related_name="configurations",
    )

    # Promotion criteria
    promotion_min_percentage = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=35.00,
        help_text="Minimum percentage required for promotion",
    )

    max_subjects_fail_allowed = models.PositiveIntegerField(
        default=2,
        help_text="Maximum number of subjects a student can fail and still be promoted",
    )

    # Mark entry settings
    allow_mark_entry_before_exam = models.BooleanField(default=False)
    allow_mark_entry_after_results_published = models.BooleanField(default=False)

    # Grading settings
    enable_grade_points = models.BooleanField(default=True)
    enable_rank_calculation = models.BooleanField(default=True)

    # Registration settings
    auto_register_students_on_exam_creation = models.BooleanField(
        default=True,
        help_text="Automatically register all students when exam is created",
    )
    allow_student_self_registration = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ["academic_year"]

    def __str__(self):
        return f"Exam Configuration - {self.academic_year}"


# =====================================================
# CLASS TEST MODELS (Teacher-initiated mini/daily tests)
# =====================================================


class ClassTest(models.Model):
    """
    Mini class tests and daily tests created by subject/class teachers
    tied to their timetable periods.
    """

    TEST_TYPE_CHOICES = [
        ("MINI_TEST", "Mini Class Test"),
        ("DAILY_TEST", "Daily Test"),
    ]

    STATUS_CHOICES = [
        ("SCHEDULED", "Scheduled"),
        ("COMPLETED", "Completed"),
        ("MARKS_ENTERED", "Marks Entered"),
    ]

    title = models.CharField(max_length=255)
    test_type = models.CharField(max_length=20, choices=TEST_TYPE_CHOICES)

    academic_year = models.ForeignKey(
        AcademicYear,
        on_delete=models.CASCADE,
        related_name="class_tests",
    )
    academic_class = models.ForeignKey(
        AcademicClass,
        on_delete=models.CASCADE,
        related_name="class_tests",
    )
    # nullable — class teacher tests may not have a specific subject
    subject = models.ForeignKey(
        Subject,
        on_delete=models.CASCADE,
        related_name="class_tests",
        null=True,
        blank=True,
    )
    # populated when a subject teacher creates the test
    subject_teacher = models.ForeignKey(
        SubjectTeacher,
        on_delete=models.SET_NULL,
        related_name="class_tests",
        null=True,
        blank=True,
    )
    # optional link to the timetable slot this test is held in
    timetable_period = models.ForeignKey(
        "schedules.TimeTable",
        on_delete=models.SET_NULL,
        related_name="class_tests",
        null=True,
        blank=True,
    )

    test_date = models.DateField()
    period_number = models.PositiveIntegerField()
    start_time = models.TimeField(null=True, blank=True)
    end_time = models.TimeField(null=True, blank=True)

    max_marks = models.PositiveIntegerField()
    passing_marks = models.PositiveIntegerField(null=True, blank=True)
    duration_minutes = models.PositiveIntegerField(null=True, blank=True)

    description = models.TextField(blank=True)

    status = models.CharField(
        max_length=20, choices=STATUS_CHOICES, default="SCHEDULED"
    )

    created_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.CASCADE,
        related_name="created_class_tests",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-test_date", "-created_at"]
        indexes = [
            models.Index(fields=["academic_class", "test_date"]),
            models.Index(fields=["created_by", "status"]),
            models.Index(fields=["subject_teacher"]),
        ]

    def __str__(self):
        return f"{self.title} - {self.academic_class} ({self.test_date})"


class ClassTestStudent(models.Model):
    """
    Per-student result record for a ClassTest.
    Created automatically when a test is created (one row per enrolled student).
    """

    class_test = models.ForeignKey(
        ClassTest,
        on_delete=models.CASCADE,
        related_name="student_results",
    )
    student_enrollment = models.ForeignKey(
        StudentEnrollment,
        on_delete=models.CASCADE,
        related_name="class_test_results",
    )

    is_absent = models.BooleanField(default=False)
    marks_obtained = models.DecimalField(
        max_digits=6,
        decimal_places=2,
        null=True,
        blank=True,
        validators=[MinValueValidator(0)],
    )
    remarks = models.CharField(max_length=255, blank=True)

    marks_entered_by = models.ForeignKey(
        "people.Teacher",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="entered_class_test_marks",
    )
    marks_entered_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        unique_together = ["class_test", "student_enrollment"]

    def __str__(self):
        return f"{self.class_test} - {self.student_enrollment}"

    @property
    def percentage(self):
        if self.marks_obtained is None or self.class_test.max_marks == 0:
            return None
        return round(float(self.marks_obtained) / self.class_test.max_marks * 100, 2)

    @property
    def is_passed(self):
        if self.is_absent or self.marks_obtained is None:
            return None
        if self.class_test.passing_marks:
            return float(self.marks_obtained) >= self.class_test.passing_marks
        return None
