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__)


def _ser_year(r):
    terms = []
    if hasattr(r, 'academic_term_ids'):
        for t in r.academic_term_ids:
            terms.append(_ser_term(t))
    return {
        'id': r.id,
        'name': r.name or '',
        'start_date': str(r.start_date) if r.start_date else '',
        'end_date': str(r.end_date) if r.end_date else '',
        'term_structure': getattr(r, 'term_structure', 'others') or 'others',
        'terms': terms,
    }


def _ser_term(t):
    return {
        'id': t.id,
        'name': t.name or '',
        'term_start_date': str(t.term_start_date) if hasattr(t, 'term_start_date') and t.term_start_date else str(t.start_date) if hasattr(t, 'start_date') and t.start_date else '',
        'term_end_date': str(t.term_end_date) if hasattr(t, 'term_end_date') and t.term_end_date else str(t.end_date) if hasattr(t, 'end_date') and t.end_date else '',
        'academic_year_id': t.academic_year_id.id if hasattr(t, 'academic_year_id') and t.academic_year_id else 0,
        'academic_year_name': t.academic_year_id.name if hasattr(t, 'academic_year_id') and t.academic_year_id else '',
        'parent_term_id': t.parent_id.id if hasattr(t, 'parent_id') and t.parent_id else None,
        'parent_term_name': t.parent_id.name if hasattr(t, 'parent_id') and t.parent_id else None,
    }


def _ser_dept(d):
    env = d.env
    course_count = 0
    faculty_count = 0
    try:
        if 'op.course' in env:
            Course = env['op.course'].sudo()
            if 'department_id' in Course._fields:
                course_count = Course.search_count([('department_id', '=', d.id)])
        if 'op.faculty' in env:
            Faculty = env['op.faculty'].sudo()
            if 'department_id' in Faculty._fields:
                faculty_count = Faculty.search_count([('department_id', '=', d.id)])
            elif 'main_department_id' in Faculty._fields:
                faculty_count = Faculty.search_count([('main_department_id', '=', d.id)])
    except Exception:
        pass
    return {
        'id': d.id,
        'name': d.name or '',
        'code': d.code or '' if hasattr(d, 'code') else '',
        'parent_id': d.parent_id.id if hasattr(d, 'parent_id') and d.parent_id else None,
        'parent_name': d.parent_id.name if hasattr(d, 'parent_id') and d.parent_id else None,
        'course_count': course_count,
        'faculty_count': faculty_count,
    }


class AcademicController(http.Controller):

    # ── Academic Years ───────────────────────────────────────────────

    @http.route('/api/academic-years', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_years(self, **kw):
        try:
            M = request.env['op.academic.year'].sudo()
            offset, limit, page = _paginate(kw)
            total = M.search_count([])
            recs = M.search([], offset=offset, limit=limit, order='id desc')
            return _json_response({'items': [_ser_year(r) for r in recs], 'data': [_ser_year(r) for r in recs], 'total': total, 'page': page, 'size': limit})
        except Exception as e:
            _logger.exception('list_years')
            return _error_response(str(e), 500)

    @http.route('/api/academic-years/<int:yid>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_year(self, yid, **kw):
        try:
            rec = request.env['op.academic.year'].sudo().browse(yid)
            if not rec.exists():
                return _error_response('Not found', 404)
            return _json_response(_ser_year(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-years', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_year(self, **kw):
        try:
            body = _get_json_body()
            Year = request.env['op.academic.year'].sudo()
            vals = {
                'name': body.get('name', ''),
                'start_date': body.get('start_date'),
                'end_date': body.get('end_date'),
            }
            if body.get('term_structure') and 'term_structure' in Year._fields:
                vals['term_structure'] = body['term_structure']
            rec = Year.create(vals)
            return _json_response(_ser_year(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-years/<int:yid>', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_year(self, yid, **kw):
        try:
            rec = request.env['op.academic.year'].sudo().browse(yid)
            if not rec.exists():
                return _error_response('Not found', 404)
            body = _get_json_body()
            vals = {}
            for k in ('name', 'start_date', 'end_date'):
                if k in body:
                    vals[k] = body[k]
            if 'term_structure' in body and 'term_structure' in rec._fields:
                vals['term_structure'] = body['term_structure']
            if vals:
                rec.write(vals)
            return _json_response(_ser_year(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-years/<int:yid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_year(self, yid, **kw):
        try:
            rec = request.env['op.academic.year'].sudo().browse(yid)
            if rec.exists():
                # Unlink dependent terms first to avoid RESTRICT FK violation.
                if hasattr(rec, 'academic_term_ids') and rec.academic_term_ids:
                    rec.academic_term_ids.unlink()
                rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            _logger.exception('delete_year')
            return _error_response(str(e), 500)

    @http.route('/api/academic-years/<int:yid>/generate-terms', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def generate_terms(self, yid, **kw):
        """Create a set of op.academic.term records spanning the year.
        The number of terms is dictated by the year's `term_structure`:

            two_sem       → 2 semesters
            two_sem_qua   → 2 semesters + 4 quarters (nested under each sem)
            three_sem     → 3 semesters
            four_Quarter  → 4 quarters
            final_year    → 1 single term
            others        → 2 semesters (default fallback)
        """
        try:
            from datetime import timedelta
            year = request.env['op.academic.year'].sudo().browse(yid)
            if not year.exists():
                return _error_response('Not found', 404)
            start = year.start_date
            end = year.end_date
            if not start or not end:
                return _error_response('Year has no dates', 400)
            total_days = max(1, (end - start).days)
            structure = getattr(year, 'term_structure', 'two_sem') or 'two_sem'
            body = _get_json_body() or {}
            if body.get('replace'):
                for existing in year.academic_term_ids:
                    existing.sudo().unlink()

            Term = request.env['op.academic.term'].sudo()

            def _split(n, label):
                chunk = total_days // n
                out = []
                for i in range(n):
                    s = start + timedelta(days=i * chunk)
                    e = end if i == n - 1 else start + timedelta(days=(i + 1) * chunk - 1)
                    out.append(Term.create({
                        'name': f'{year.name} - {label} {i + 1}',
                        'term_start_date': str(s),
                        'term_end_date': str(e),
                        'academic_year_id': year.id,
                    }))
                return out

            created = []
            if structure == 'two_sem':
                created = _split(2, 'Semester')
            elif structure == 'three_sem':
                created = _split(3, 'Semester')
            elif structure == 'four_Quarter':
                created = _split(4, 'Quarter')
            elif structure == 'final_year':
                created = [Term.create({
                    'name': f'{year.name} - Final Year',
                    'term_start_date': str(start),
                    'term_end_date': str(end),
                    'academic_year_id': year.id,
                })]
            elif structure == 'two_sem_qua':
                sems = _split(2, 'Semester')
                quarters = []
                for parent_idx, parent in enumerate(sems):
                    s = parent.term_start_date
                    e = parent.term_end_date
                    span = max(1, (e - s).days)
                    half = span // 2
                    q1 = Term.create({
                        'name': f'{year.name} - Q{parent_idx * 2 + 1}',
                        'term_start_date': str(s),
                        'term_end_date': str(s + timedelta(days=half)),
                        'academic_year_id': year.id,
                    })
                    q2 = Term.create({
                        'name': f'{year.name} - Q{parent_idx * 2 + 2}',
                        'term_start_date': str(s + timedelta(days=half + 1)),
                        'term_end_date': str(e),
                        'academic_year_id': year.id,
                    })
                    if 'parent_id' in Term._fields:
                        q1.write({'parent_id': parent.id})
                        q2.write({'parent_id': parent.id})
                    quarters += [q1, q2]
                created = sems + quarters
            else:
                created = _split(2, 'Semester')

            year.invalidate_recordset()
            return _json_response([_ser_term(t) for t in created])
        except Exception as e:
            _logger.exception('generate_terms')
            return _error_response(str(e), 500)

    # ── Academic Terms ───────────────────────────────────────────────

    @http.route('/api/academic-terms', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_terms(self, **kw):
        try:
            M = request.env['op.academic.term'].sudo()
            domain = []
            if kw.get('year_id'):
                domain.append(('academic_year_id', '=', int(kw['year_id'])))
            offset, limit, page = _paginate(kw)
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            return _json_response({'items': [_ser_term(r) for r in recs], 'data': [_ser_term(r) for r in recs], 'total': total, 'page': page, 'size': limit})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-terms', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_term(self, **kw):
        try:
            body = _get_json_body()
            vals = {
                'name': body.get('name', ''),
                'term_start_date': body.get('term_start_date'),
                'term_end_date': body.get('term_end_date'),
            }
            if body.get('academic_year_id'):
                vals['academic_year_id'] = int(body['academic_year_id'])
            rec = request.env['op.academic.term'].sudo().create(vals)
            return _json_response(_ser_term(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-terms/<int:tid>', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_term(self, tid, **kw):
        try:
            rec = request.env['op.academic.term'].sudo().browse(tid)
            if not rec.exists():
                return _error_response('Not found', 404)
            body = _get_json_body()
            vals = {}
            for k in ('name', 'term_start_date', 'term_end_date'):
                if k in body:
                    vals[k] = body[k]
            if 'academic_year_id' in body:
                vals['academic_year_id'] = int(body['academic_year_id'])
            if vals:
                rec.write(vals)
            return _json_response(_ser_term(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/academic-terms/<int:tid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_term(self, tid, **kw):
        try:
            rec = request.env['op.academic.term'].sudo().browse(tid)
            if rec.exists():
                rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            return _error_response(str(e), 500)

    # ── Departments ──────────────────────────────────────────────────

    @http.route('/api/departments', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_depts(self, **kw):
        try:
            M = request.env['op.department'].sudo()
            offset, limit, page = _paginate(kw)
            total = M.search_count([])
            recs = M.search([], offset=offset, limit=limit, order='id desc')
            return _json_response({'items': [_ser_dept(r) for r in recs], 'data': [_ser_dept(r) for r in recs], 'total': total, 'page': page, 'size': limit})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/departments', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_dept(self, **kw):
        try:
            body = _get_json_body()
            vals = {'name': body.get('name', '')}
            if body.get('code'):
                vals['code'] = body['code']
            if body.get('parent_id'):
                vals['parent_id'] = int(body['parent_id'])
            rec = request.env['op.department'].sudo().create(vals)
            return _json_response(_ser_dept(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/departments/<int:did>', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_dept(self, did, **kw):
        try:
            rec = request.env['op.department'].sudo().browse(did)
            if not rec.exists():
                return _error_response('Not found', 404)
            body = _get_json_body()
            vals = {}
            for k in ('name', 'code'):
                if k in body:
                    vals[k] = body[k]
            if 'parent_id' in body:
                vals['parent_id'] = int(body['parent_id']) if body['parent_id'] else False
            if vals:
                rec.write(vals)
            return _json_response(_ser_dept(rec))
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/departments/<int:did>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_dept(self, did, **kw):
        try:
            rec = request.env['op.department'].sudo().browse(did)
            if rec.exists():
                rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            return _error_response(str(e), 500)
