from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError


class OpClassroomExt(models.Model):
    """Extend the OpenEduCat classroom into a homeroom (class group).

    Original ``op.classroom`` represents a physical room (with capacity and
    facilities). We keep that semantic and add organisational fields so the
    same record also acts as the home of a group of students:
      * ``student_ids``  — homeroom roster (M2M).
      * ``teacher_ids``  — homeroom teachers (M2M).
      * ``course_ids``   — courses taught to this classroom (M2M).
      * ``batch_ids``    — auto-created batches (one per classroom × course).

    ``assign_course`` is idempotent: re-assigning the same course reuses the
    existing batch and only enrolls students that aren't already enrolled.
    """

    _inherit = "op.classroom"

    entity_id = fields.Many2one(
        "encoach.entity",
        string="Entity",
        ondelete="set null",
        index=True,
        help="Owning entity/organization for LMS isolation.",
    )
    branch_id = fields.Many2one(
        "encoach.lms.branch",
        string="Branch",
        ondelete="set null",
        index=True,
        help="Optional branch/campus inside the owning entity.",
    )

    student_ids = fields.Many2many(
        "op.student",
        "op_classroom_student_rel",
        "classroom_id",
        "student_id",
        string="Students",
    )
    teacher_ids = fields.Many2many(
        "op.faculty",
        "op_classroom_faculty_rel",
        "classroom_id",
        "faculty_id",
        string="Homeroom Teachers",
    )
    course_ids = fields.Many2many(
        "op.course",
        "op_classroom_course_rel",
        "classroom_id",
        "course_id",
        string="Courses",
    )

    batch_ids = fields.One2many(
        "op.batch",
        "classroom_id",
        string="Batches",
    )

    student_count = fields.Integer(compute="_compute_counts")
    teacher_count = fields.Integer(compute="_compute_counts")
    course_count = fields.Integer(compute="_compute_counts")
    batch_count = fields.Integer(compute="_compute_counts")

    notes = fields.Text(string="Notes")

    @api.depends("student_ids", "teacher_ids", "course_ids", "batch_ids")
    def _compute_counts(self):
        for rec in self:
            rec.student_count = len(rec.student_ids)
            rec.teacher_count = len(rec.teacher_ids)
            rec.course_count = len(rec.course_ids)
            rec.batch_count = len(rec.batch_ids)

    @api.constrains("branch_id", "entity_id")
    def _check_branch_entity(self):
        for rec in self:
            if rec.branch_id and rec.entity_id and rec.branch_id.entity_id != rec.entity_id:
                raise ValidationError(
                    "Classroom branch must belong to the same entity as the classroom."
                )

    # ------------------------------------------------------------------
    # Cascade: assign a course to this classroom
    # ------------------------------------------------------------------
    def _ensure_unique_batch_code(self, base_code):
        """Return a unique batch code derived from ``base_code``.

        ``op.batch`` has a global ``unique(code)`` constraint, so we suffix
        with a counter when needed.
        """
        Batch = self.env["op.batch"].sudo()
        candidate = base_code
        idx = 1
        while Batch.search_count([("code", "=", candidate)]):
            idx += 1
            candidate = f"{base_code}-{idx}"
        return candidate

    def assign_course(
        self,
        course,
        term_name=None,
        start_date=None,
        end_date=None,
        teacher_ids=None,
        section_id=None,
        term_key=None,
    ):
        """Assign ``course`` to this classroom and cascade to a batch.

        - Creates (or reuses) the canonical batch for
          ``(classroom, course, section, term_key)``.
        - Enrolls every classroom student into the batch via ``op.student.course``.
        - Optionally sets ``teacher_ids`` (M2M) on the batch; if none is
          provided we default to the classroom's homeroom teachers.

        Returns the ``op.batch`` record.
        """
        self.ensure_one()
        Course = self.env["op.course"].sudo()
        Section = self.env["encoach.course.section"].sudo()
        Batch = self.env["op.batch"].sudo()
        Enroll = self.env["op.student.course"].sudo()

        if isinstance(course, int):
            course = Course.browse(course)
        if not course or not course.exists():
            raise UserError("Course not found.")

        if (
            self.entity_id
            and course.entity_id
            and self.entity_id.id != course.entity_id.id
        ):
            raise ValidationError(
                "Course belongs to a different entity than the classroom."
            )

        section = None
        if section_id:
            section = Section.browse(int(section_id))
            if not section.exists():
                raise UserError("Course section not found.")
            if section.course_id.id != course.id:
                raise ValidationError("Selected section does not belong to the selected course.")
            if (
                self.entity_id
                and section.entity_id
                and self.entity_id.id != section.entity_id.id
            ):
                raise ValidationError(
                    "Course section belongs to a different entity than the classroom."
                )

        if teacher_ids is not None and self.entity_id:
            teachers = self.env["op.faculty"].sudo().browse(list(teacher_ids))
            for t in teachers:
                if (
                    t.exists()
                    and t.entity_id
                    and t.entity_id.id != self.entity_id.id
                ):
                    raise ValidationError(
                        f"Teacher {t.partner_id.name or t.id} belongs to a "
                        "different entity than the classroom."
                    )

        # Add to classroom course list (idempotent)
        if course.id not in self.course_ids.ids:
            self.write({"course_ids": [(4, course.id)]})

        # Find or create the canonical batch (classroom × course × section × term)
        domain = [
            ("classroom_id", "=", self.id),
            ("course_id", "=", course.id),
            ("course_section_id", "=", section.id if section else False),
            ("term_key", "=", term_key or False),
        ]
        batch = Batch.search(domain, limit=1)
        if not batch:
            section_label = f" — Section {section.code}" if section else ""
            term_label = f" ({term_name})" if term_name else ""
            base_name = f"{self.name} — {course.name or course.code or 'Course'}{section_label}{term_label}"
            base_code = (self.code or self.name or "BATCH").upper().replace(" ", "_")[:12]
            section_code = (section.code if section else "").upper().replace(" ", "")
            term_code = (term_key or "").upper().replace(" ", "")
            base_code = f"{base_code}-{(course.code or '').upper()[:8] or course.id}"
            if section_code:
                base_code = f"{base_code}-{section_code[:5]}"
            if term_code:
                base_code = f"{base_code}-{term_code[:4]}"
            today = fields.Date.context_today(self)
            vals = {
                "name": base_name,
                "code": self._ensure_unique_batch_code(base_code[:16]),
                "course_id": course.id,
                "classroom_id": self.id,
                "course_section_id": section.id if section else False,
                "term_key": term_key or False,
                "start_date": start_date or today,
                "end_date": end_date or today.replace(month=12, day=31),
                "entity_id": self.entity_id.id if self.entity_id else False,
                "branch_id": self.branch_id.id if self.branch_id else False,
            }
            batch = Batch.create(vals)

        # Teacher assignment (M2M) — explicit overrides homeroom defaults
        target_teachers = teacher_ids if teacher_ids is not None else self.teacher_ids.ids
        if target_teachers:
            batch.write({"teacher_ids": [(6, 0, list(target_teachers))]})

        # Enroll classroom roster into the batch (idempotent)
        for student in self.student_ids:
            existing = Enroll.search([
                ("student_id", "=", student.id),
                ("course_id", "=", course.id),
                ("batch_id", "=", batch.id),
            ], limit=1)
            if not existing:
                Enroll.create({
                    "student_id": student.id,
                    "course_id": course.id,
                    "batch_id": batch.id,
                    "state": "running",
                })

        return batch

    @api.model
    def _add_students_to_open_batches(self, classroom_id, student_ids):
        """When new students join the classroom, enroll them in every existing
        course batch under that classroom.
        """
        Batch = self.env["op.batch"].sudo()
        Enroll = self.env["op.student.course"].sudo()
        classroom = self.browse(classroom_id)
        if not classroom.exists():
            return
        batches = Batch.search([("classroom_id", "=", classroom.id)])
        for batch in batches:
            for sid in student_ids:
                exists = Enroll.search([
                    ("student_id", "=", sid),
                    ("course_id", "=", batch.course_id.id),
                    ("batch_id", "=", batch.id),
                ], limit=1)
                if not exists:
                    Enroll.create({
                        "student_id": sid,
                        "course_id": batch.course_id.id,
                        "batch_id": batch.id,
                        "state": "running",
                    })

    def write(self, vals):
        """Cascade roster changes into existing classroom batches."""
        old_students = {rec.id: set(rec.student_ids.ids) for rec in self}
        res = super().write(vals)
        if "student_ids" in vals:
            for rec in self:
                added = set(rec.student_ids.ids) - old_students.get(rec.id, set())
                if added:
                    self._add_students_to_open_batches(rec.id, list(added))
        return res
