"""HTTP controller for classrooms (homerooms).

Routes:
- GET    /api/classrooms                       list (entity-scoped)
- GET    /api/classrooms/<id>                  fetch with full nested data
- POST   /api/classrooms                       create
- PATCH  /api/classrooms/<id>                  partial update
- DELETE /api/classrooms/<id>                  delete

- GET    /api/classrooms/<id>/students         list roster
- POST   /api/classrooms/<id>/students         set/add students  (body: {student_ids:[..], mode:'set'|'add'})
- DELETE /api/classrooms/<id>/students         remove students   (body: {student_ids:[..]})

- GET    /api/classrooms/<id>/teachers         list homeroom teachers
- POST   /api/classrooms/<id>/teachers         set/add teachers  (body: {teacher_ids:[..], mode:'set'|'add'})

- GET    /api/classrooms/<id>/courses          list courses
- POST   /api/classrooms/<id>/assign-course    cascade course → batch + enroll students
- DELETE /api/classrooms/<id>/courses/<cid>    detach course (does NOT delete batches/enrollments)

- GET    /api/classrooms/<id>/batches          list classroom batches

The legacy ``/api/groups`` aliases on op.classroom remain for backward
compatibility but new clients should target ``/api/classrooms``.
"""
import logging

from odoo import http
from odoo.http import request
from odoo.addons.encoach_api.controllers.base import (
    jwt_required, _json_response, _error_response, _get_json_body, _paginate,
)

_logger = logging.getLogger(__name__)


# ----------------------------------------------------------------------
# Entity scoping helpers (same shape used in branches.py / lms_core.py)
# ----------------------------------------------------------------------
def _entity_scope():
    """Same convention as ``encoach_lms_api.controllers.lms_core._entity_scope``."""
    user = request.env.user.sudo()
    ids = user.entity_ids.ids if hasattr(user, 'entity_ids') else []
    if ids:
        return ids, False
    is_super = bool(
        user.id == 1 or user.has_group('base.group_system')
    )
    return ids, is_super


def _ensure_entity_access(entity_id):
    if not entity_id:
        raise PermissionError('entity_id is required')
    ids, is_super = _entity_scope()
    if is_super:
        return int(entity_id)
    if not ids:
        raise PermissionError('User is not linked to any entity')
    if int(entity_id) not in ids:
        raise PermissionError('Entity access denied')
    return int(entity_id)


def _default_entity_id_from_scope():
    ids, is_super = _entity_scope()
    if ids:
        return ids[0]
    if is_super:
        return False
    raise PermissionError('User is not linked to any entity')


def _scoped_entity_domain(base_domain=None):
    domain = list(base_domain or [])
    ids, is_super = _entity_scope()
    if is_super:
        return domain
    if not ids:
        return domain + [('id', '=', 0)]
    return domain + [('entity_id', 'in', ids)]


def _ensure_branch_access(branch_id, *, entity_id=None):
    """Validate that the branch exists, belongs to ``entity_id`` (when set)
    and is accessible by the current user."""
    if not branch_id:
        return None
    Branch = request.env['encoach.lms.branch'].sudo()
    branch = Branch.browse(int(branch_id))
    if not branch.exists():
        raise ValueError('Branch not found')
    _ensure_entity_access(branch.entity_id.id)
    if entity_id and branch.entity_id.id != int(entity_id):
        raise PermissionError('Branch does not belong to the requested entity.')
    return branch.id


def _filter_ids_in_entity(model, ids, entity_id, *, label='record'):
    """Return ids that exist *and* belong to ``entity_id``.

    When ``entity_id`` is falsy (e.g. legacy classroom without an entity), we
    accept any record that has no ``entity_id`` set yet. This keeps the API
    safe by default while still allowing administrators to operate on
    pre-LMS-isolation data without surprises.
    """
    if not ids:
        return []
    Model = request.env[model].sudo()
    recs = Model.browse([int(x) for x in ids])
    out = []
    for rec in recs:
        if not rec.exists():
            continue
        rec_entity = rec.entity_id.id if rec.entity_id else None
        if entity_id and rec_entity and rec_entity != int(entity_id):
            raise PermissionError(
                f"{label} {rec.id} belongs to a different entity"
            )
        out.append(rec.id)
    return out


# ----------------------------------------------------------------------
# Serializers
# ----------------------------------------------------------------------
def _ser_student_short(s):
    return {
        'id': s.id,
        'name': s.name or '',
        'email': s.email or '',
        'gr_no': getattr(s, 'gr_no', '') or '',
    }


def _ser_teacher_short(f):
    return {
        'id': f.id,
        'name': f.name or '',
        'email': getattr(f, 'email', '') or '',
    }


def _ser_course_short(c):
    return {
        'id': c.id,
        'name': c.name or '',
        'code': c.code or '',
    }


def _ser_section_short(s):
    return {
        'id': s.id,
        'name': s.name or '',
        'code': s.code or '',
        'sequence': s.sequence or 0,
        'course_id': s.course_id.id if s.course_id else None,
        'course_name': s.course_id.name if s.course_id else '',
    }


def _ser_batch_short(b):
    return {
        'id': b.id,
        'name': b.name or '',
        'code': b.code or '',
        'course_id': b.course_id.id if b.course_id else None,
        'course_name': b.course_id.name if b.course_id else '',
        'course_section_id': (
            b.course_section_id.id
            if hasattr(b, 'course_section_id') and b.course_section_id
            else None
        ),
        'course_section_name': (
            b.course_section_id.name
            if hasattr(b, 'course_section_id') and b.course_section_id
            else ''
        ),
        'course_section_code': (
            b.course_section_id.code
            if hasattr(b, 'course_section_id') and b.course_section_id
            else ''
        ),
        'term_key': getattr(b, 'term_key', '') or '',
        'start_date': b.start_date and str(b.start_date) or '',
        'end_date': b.end_date and str(b.end_date) or '',
        'student_count': getattr(b, 'student_count', 0) or 0,
        'teacher_ids': b.teacher_ids.ids if hasattr(b, 'teacher_ids') else [],
    }


def _ser_classroom(r, *, full=False):
    data = {
        'id': r.id,
        'name': r.name or '',
        'code': r.code or '',
        'capacity': r.capacity or 0,
        'active': bool(r.active),
        'notes': getattr(r, 'notes', '') or '',
        'entity_id': r.entity_id.id if r.entity_id else None,
        'entity_name': r.entity_id.name if r.entity_id else '',
        'branch_id': r.branch_id.id if r.branch_id else None,
        'branch_name': r.branch_id.name if r.branch_id else '',
        'student_count': r.student_count or 0,
        'teacher_count': r.teacher_count or 0,
        'course_count': r.course_count or 0,
        'batch_count': r.batch_count or 0,
        'student_ids': r.student_ids.ids,
        'teacher_ids': r.teacher_ids.ids,
        'course_ids': r.course_ids.ids,
    }
    if full:
        data['students'] = [_ser_student_short(s) for s in r.student_ids]
        data['teachers'] = [_ser_teacher_short(t) for t in r.teacher_ids]
        data['courses'] = [_ser_course_short(c) for c in r.course_ids]
        data['sections'] = [
            _ser_section_short(s)
            for s in request.env['encoach.course.section'].sudo().search([
                ('course_id', 'in', r.course_ids.ids)
            ], order='course_id, sequence, id')
        ]
        data['batches'] = [_ser_batch_short(b) for b in r.batch_ids]
    return data


def _classroom_or_404(cid):
    rec = request.env['op.classroom'].sudo().browse(int(cid))
    if not rec.exists():
        return None
    return rec


def _scoped_classroom_domain(base_domain=None):
    """Same as ``_scoped_entity_domain`` but tolerates legacy classrooms
    without ``entity_id`` for backward-compatibility (so existing rooms
    keep showing up to admins)."""
    domain = list(base_domain or [])
    ids, is_super = _entity_scope()
    if is_super:
        return domain
    if not ids:
        return domain + [('id', '=', 0)]
    return domain + ['|', ('entity_id', 'in', ids), ('entity_id', '=', False)]


# ----------------------------------------------------------------------
# Controller
# ----------------------------------------------------------------------
class ClassroomsController(http.Controller):

    # ---- CRUD --------------------------------------------------------
    @http.route('/api/classrooms', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classrooms(self, **kw):
        try:
            Classroom = request.env['op.classroom'].sudo()
            domain = _scoped_classroom_domain([])
            if kw.get('search'):
                q = kw['search']
                domain += ['|', ('name', 'ilike', q), ('code', 'ilike', q)]
            if kw.get('active') in ('true', 'false', '1', '0'):
                domain.append(('active', '=', kw.get('active') in ('true', '1')))
            if kw.get('branch_id'):
                domain.append(('branch_id', '=', int(kw['branch_id'])))
            if kw.get('entity_id'):
                domain.append(('entity_id', '=', _ensure_entity_access(int(kw['entity_id']))))
            offset, limit, page = _paginate(kw)
            total = Classroom.search_count(domain)
            recs = Classroom.search(domain, offset=offset, limit=limit, order='name asc, id asc')
            items = [_ser_classroom(r) for r in recs]
            return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit})
        except Exception as exc:
            _logger.exception('list_classrooms failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_classroom(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('get_classroom failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_classroom(self, **kw):
        try:
            body = _get_json_body()
            name = (body.get('name') or '').strip()
            if not name:
                return _error_response('name is required', 400)
            entity_id = (
                _ensure_entity_access(int(body['entity_id']))
                if body.get('entity_id') else _default_entity_id_from_scope()
            )
            branch_id = _ensure_branch_access(body.get('branch_id'), entity_id=entity_id) if body.get('branch_id') else None

            code = (body.get('code') or '').strip() or name.upper().replace(' ', '_')[:16]
            vals = {
                'name': name,
                'code': code,
                'capacity': int(body.get('capacity') or 30),
                'notes': (body.get('notes') or '').strip(),
                'entity_id': entity_id,
                'branch_id': branch_id or False,
            }
            if 'active' in body:
                vals['active'] = bool(body.get('active'))
            if body.get('student_ids'):
                vals['student_ids'] = [(6, 0, [int(x) for x in body['student_ids']])]
            if body.get('teacher_ids'):
                vals['teacher_ids'] = [(6, 0, [int(x) for x in body['teacher_ids']])]
            rec = request.env['op.classroom'].sudo().create(vals)
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('create_classroom failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_classroom(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            body = _get_json_body()
            vals = {}
            if 'name' in body:
                vals['name'] = (body.get('name') or '').strip()
            if 'code' in body:
                vals['code'] = (body.get('code') or '').strip() or rec.code
            if 'capacity' in body:
                vals['capacity'] = int(body.get('capacity') or rec.capacity)
            if 'notes' in body:
                vals['notes'] = (body.get('notes') or '').strip()
            if 'active' in body:
                vals['active'] = bool(body.get('active'))
            target_entity = rec.entity_id.id
            if 'entity_id' in body and body.get('entity_id'):
                target_entity = _ensure_entity_access(int(body['entity_id']))
                vals['entity_id'] = target_entity
            if 'branch_id' in body:
                vals['branch_id'] = (
                    _ensure_branch_access(int(body['branch_id']), entity_id=target_entity)
                    if body.get('branch_id') else False
                )
            if vals:
                rec.write(vals)
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('update_classroom failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_classroom(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if rec:
                if rec.entity_id:
                    _ensure_entity_access(rec.entity_id.id)
                rec.unlink()
            return _json_response({'success': True})
        except Exception as exc:
            _logger.exception('delete_classroom failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    # ---- Roster (students) ------------------------------------------
    @http.route('/api/classrooms/<int:cid>/students', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classroom_students(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            items = [_ser_student_short(s) for s in rec.student_ids]
            return _json_response({'items': items, 'data': items, 'total': len(items)})
        except Exception as exc:
            _logger.exception('list_classroom_students failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/students', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def set_classroom_students(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            body = _get_json_body()
            raw_ids = [int(x) for x in (body.get('student_ids') or [])]
            ids = _filter_ids_in_entity(
                'op.student', raw_ids,
                rec.entity_id.id if rec.entity_id else None,
                label='Student',
            )
            mode = (body.get('mode') or 'set').lower()
            if mode == 'set':
                rec.write({'student_ids': [(6, 0, ids)]})
            else:
                rec.write({'student_ids': [(4, x) for x in ids]})
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('set_classroom_students failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/students', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def remove_classroom_students(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            body = _get_json_body()
            ids = [int(x) for x in (body.get('student_ids') or [])]
            rec.write({'student_ids': [(3, x) for x in ids]})
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('remove_classroom_students failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    # ---- Homeroom teachers ------------------------------------------
    @http.route('/api/classrooms/<int:cid>/teachers', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classroom_teachers(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            items = [_ser_teacher_short(t) for t in rec.teacher_ids]
            return _json_response({'items': items, 'data': items, 'total': len(items)})
        except Exception as exc:
            _logger.exception('list_classroom_teachers failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/teachers', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def set_classroom_teachers(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            body = _get_json_body()
            raw_ids = [int(x) for x in (body.get('teacher_ids') or [])]
            ids = _filter_ids_in_entity(
                'op.faculty', raw_ids,
                rec.entity_id.id if rec.entity_id else None,
                label='Teacher',
            )
            mode = (body.get('mode') or 'set').lower()
            if mode == 'set':
                rec.write({'teacher_ids': [(6, 0, ids)]})
            else:
                rec.write({'teacher_ids': [(4, x) for x in ids]})
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('set_classroom_teachers failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    # ---- Courses + cascade ------------------------------------------
    @http.route('/api/classrooms/<int:cid>/courses', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classroom_courses(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            items = [_ser_course_short(c) for c in rec.course_ids]
            return _json_response({'items': items, 'data': items, 'total': len(items)})
        except Exception as exc:
            _logger.exception('list_classroom_courses failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/sections', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classroom_sections(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            domain = [('course_id', 'in', rec.course_ids.ids)]
            if kw.get('course_id'):
                domain.append(('course_id', '=', int(kw['course_id'])))
            if kw.get('active') in ('true', 'false', '1', '0'):
                domain.append(('active', '=', kw.get('active') in ('true', '1')))
            items = [
                _ser_section_short(s)
                for s in request.env['encoach.course.section'].sudo().search(
                    domain, order='course_id, sequence, id'
                )
            ]
            return _json_response({'items': items, 'data': items, 'total': len(items)})
        except Exception as exc:
            _logger.exception('list_classroom_sections failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/assign-course', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def assign_classroom_course(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            body = _get_json_body()
            course_id = int(body.get('course_id') or 0)
            if not course_id:
                return _error_response('course_id is required', 400)
            classroom_entity_id = rec.entity_id.id if rec.entity_id else None
            _filter_ids_in_entity('op.course', [course_id], classroom_entity_id, label='Course')

            section_id = int(body.get('section_id') or 0) or None
            if section_id:
                _filter_ids_in_entity(
                    'encoach.course.section', [section_id],
                    classroom_entity_id, label='Course section',
                )

            teacher_ids = body.get('teacher_ids')
            if teacher_ids is not None:
                teacher_ids = _filter_ids_in_entity(
                    'op.faculty', [int(x) for x in teacher_ids],
                    classroom_entity_id, label='Teacher',
                )
            term_name = body.get('term_name') or None
            term_key = (body.get('term_key') or '').strip() or None
            start_date = body.get('start_date') or None
            end_date = body.get('end_date') or None
            batch = rec.assign_course(
                course_id,
                term_name=term_name,
                start_date=start_date,
                end_date=end_date,
                teacher_ids=teacher_ids,
                section_id=section_id,
                term_key=term_key,
            )
            return _json_response({
                'data': _ser_classroom(rec, full=True),
                'batch': _ser_batch_short(batch),
            })
        except Exception as exc:
            _logger.exception('assign_classroom_course failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    @http.route('/api/classrooms/<int:cid>/assign-section', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def assign_classroom_section(self, cid, **kw):
        """Alias to make the classroom×section workflow explicit for clients."""
        return self.assign_classroom_course(cid, **kw)

    @http.route('/api/classrooms/<int:cid>/courses/<int:course_id>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def detach_classroom_course(self, cid, course_id, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            rec.write({'course_ids': [(3, int(course_id))]})
            return _json_response({'data': _ser_classroom(rec, full=True)})
        except Exception as exc:
            _logger.exception('detach_classroom_course failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    # ---- Batches -----------------------------------------------------
    @http.route('/api/classrooms/<int:cid>/batches', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_classroom_batches(self, cid, **kw):
        try:
            rec = _classroom_or_404(cid)
            if not rec:
                return _error_response('Not found', 404)
            if rec.entity_id:
                _ensure_entity_access(rec.entity_id.id)
            items = [_ser_batch_short(b) for b in rec.batch_ids]
            return _json_response({'items': items, 'data': items, 'total': len(items)})
        except Exception as exc:
            _logger.exception('list_classroom_batches failed')
            return _error_response(str(exc), 403 if isinstance(exc, PermissionError) else 500)

    # ---- Legacy /api/groups aliases (back-compat) -------------------
    @http.route('/api/groups', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_groups(self, **kw):
        return self.list_classrooms(**kw)

    @http.route('/api/groups/<int:gid>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_group(self, gid, **kw):
        return self.get_classroom(gid, **kw)

    @http.route('/api/groups', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_group(self, **kw):
        return self.create_classroom(**kw)

    @http.route('/api/groups/<int:gid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_group(self, gid, **kw):
        return self.delete_classroom(gid, **kw)
