import json
import logging

from odoo import http, fields
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__)

IELTS_SKILLS = ['listening', 'reading', 'writing', 'speaking']


def _section_to_dict(sec):
    return {
        'id': sec.id,
        'title': sec.title,
        'skill': sec.skill or '',
        'question_count': sec.question_count or 0,
        'time_limit_min': sec.time_limit_min or 0,
        'scoring_method': sec.scoring_method or 'auto',
        'sequence': sec.sequence,
        'question_ids': sec.question_ids.ids,
    }


def _exam_to_dict(exam):
    return {
        'id': exam.id,
        'title': exam.title,
        'template_id': exam.template_id.id if exam.template_id else None,
        'subject_id': exam.subject_id.id if exam.subject_id else None,
        'entity_id': exam.entity_id.id if exam.entity_id else None,
        'teacher_id': exam.teacher_id.id if exam.teacher_id else None,
        'description': exam.description or '',
        'total_time_min': exam.total_time_min or 0,
        'pass_threshold': exam.pass_threshold or 0.0,
        'results_release_mode': exam.results_release_mode or 'auto',
        'randomize_questions': exam.randomize_questions,
        'status': exam.status,
        'sections': [_section_to_dict(s) for s in exam.section_ids],
    }


def _question_to_dict(q):
    options = None
    if q.options:
        try:
            options = json.loads(q.options)
        except (json.JSONDecodeError, TypeError):
            options = q.options
    return {
        'id': q.id,
        'skill': q.skill,
        'question_type': q.question_type,
        'stem': q.stem,
        'options': options,
        'marks': q.marks,
        'difficulty': q.difficulty,
        'status': q.status,
        'ielts_certified': q.ielts_certified,
    }


class EncoachIeltsExamController(http.Controller):

    # ------------------------------------------------------------------
    # POST /api/exam/ielts/create
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/create', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def create(self, **kw):
        try:
            body = _get_json_body()
            template_id = body.get('template_id')
            title = (body.get('title') or '').strip()
            entity_id = body.get('entity_id')

            if not template_id or not title:
                return _error_response('template_id and title are required', 400)

            template = request.env['encoach.exam.template'].sudo().browse(
                int(template_id))
            if not template.exists():
                return _error_response('Template not found', 404)

            exam = request.env['encoach.exam.custom'].sudo().create({
                'title': title,
                'template_id': template.id,
                'subject_id': template.subject_id.id if template.subject_id else False,
                'entity_id': int(entity_id) if entity_id else False,
                'teacher_id': request.env.user.id,
                'total_time_min': template.total_time_min or 0,
                'pass_threshold': template.pass_threshold or 0.0,
                'results_release_mode': template.results_release_mode or 'auto',
                'status': 'draft',
            })

            structure = None
            if template.structure:
                try:
                    structure = json.loads(template.structure)
                except (json.JSONDecodeError, TypeError):
                    structure = None

            Section = request.env['encoach.exam.custom.section'].sudo()
            if structure and isinstance(structure, list):
                for idx, sec_def in enumerate(structure):
                    Section.create({
                        'exam_id': exam.id,
                        'title': sec_def.get('title', f'Section {idx + 1}'),
                        'skill': sec_def.get('skill', ''),
                        'question_count': sec_def.get('question_count', 0),
                        'time_limit_min': sec_def.get('time_limit_min', 0),
                        'scoring_method': sec_def.get('scoring_method', 'auto'),
                        'sequence': sec_def.get('sequence', (idx + 1) * 10),
                    })
            else:
                for idx, skill in enumerate(IELTS_SKILLS):
                    Section.create({
                        'exam_id': exam.id,
                        'title': skill.capitalize(),
                        'skill': skill,
                        'question_count': 40 if skill in ('listening', 'reading') else 0,
                        'time_limit_min': {'listening': 30, 'reading': 60,
                                           'writing': 60, 'speaking': 15}.get(skill, 30),
                        'scoring_method': 'auto' if skill in ('listening', 'reading') else 'rubric',
                        'sequence': (idx + 1) * 10,
                    })

            exam.invalidate_recordset()
            return _json_response(_exam_to_dict(exam), 201)

        except Exception as e:
            _logger.exception('ielts create failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # GET /api/exam/ielts/<int:exam_id>
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>', type='http', auth='none',
                methods=['GET'], csrf=False)
    @jwt_required
    def get(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            return _json_response(_exam_to_dict(exam))

        except Exception as e:
            _logger.exception('ielts get failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # PUT /api/exam/ielts/<int:exam_id>
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>', type='http', auth='none',
                methods=['PUT'], csrf=False)
    @jwt_required
    def update(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)
            if exam.status != 'draft':
                return _error_response('Only draft exams can be updated', 400)

            body = _get_json_body()
            allowed = {'title', 'description', 'total_time_min', 'pass_threshold',
                       'results_release_mode', 'randomize_questions'}
            vals = {k: v for k, v in body.items() if k in allowed}

            if vals:
                exam.write(vals)

            return _json_response(_exam_to_dict(exam))

        except Exception as e:
            _logger.exception('ielts update failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # GET /api/exam/ielts/<int:exam_id>/content-pool-count
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/content-pool-count',
                type='http', auth='none', methods=['GET'], csrf=False)
    @jwt_required
    def content_pool_count(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            Question = request.env['encoach.question'].sudo()
            base_domain = [('status', '=', 'active')]
            if exam.subject_id:
                base_domain.append(('subject_id', '=', exam.subject_id.id))

            counts = {}
            for skill in IELTS_SKILLS:
                domain = base_domain + [('skill', '=', skill)]
                counts[skill] = Question.search_count(domain)

            return _json_response(counts)

        except Exception as e:
            _logger.exception('content_pool_count failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # GET /api/exam/ielts/<int:exam_id>/content-pool
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/content-pool',
                type='http', auth='none', methods=['GET'], csrf=False)
    @jwt_required
    def content_pool(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            domain = [('status', '=', 'active')]
            if exam.subject_id:
                domain.append(('subject_id', '=', exam.subject_id.id))

            skill = kw.get('skill')
            if skill:
                domain.append(('skill', '=', skill))

            difficulty = kw.get('difficulty')
            if difficulty:
                domain.append(('difficulty', '=', difficulty))

            page = int(kw.get('page', 1))
            size = int(kw.get('size', 20))

            Question = request.env['encoach.question'].sudo()
            records, total = _paginate(Question, domain, page=page, size=size)

            return _json_response({
                'items': [_question_to_dict(q) for q in records],
                'total': total,
                'page': page,
                'size': size,
            })

        except Exception as e:
            _logger.exception('content_pool failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # POST /api/exam/ielts/<int:exam_id>/auto-assemble
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/auto-assemble',
                type='http', auth='none', methods=['POST'], csrf=False)
    @jwt_required
    def auto_assemble(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)
            if exam.status != 'draft':
                return _error_response('Only draft exams can be auto-assembled', 400)

            Question = request.env['encoach.question'].sudo()
            base_domain = [('status', '=', 'active')]
            if exam.subject_id:
                base_domain.append(('subject_id', '=', exam.subject_id.id))

            assembled = []
            for section in exam.section_ids:
                needed = section.question_count or 10
                domain = base_domain + [('skill', '=', section.skill)] if section.skill else base_domain

                candidates = Question.search(domain, order='difficulty asc, id asc')

                easy = candidates.filtered(lambda q: q.difficulty == 'easy')
                medium = candidates.filtered(lambda q: q.difficulty == 'medium')
                hard = candidates.filtered(lambda q: q.difficulty == 'hard')

                easy_count = max(1, needed // 3)
                hard_count = max(1, needed // 3)
                medium_count = needed - easy_count - hard_count

                selected_ids = []
                selected_ids.extend(easy[:easy_count].ids)
                selected_ids.extend(medium[:medium_count].ids)
                selected_ids.extend(hard[:hard_count].ids)

                if len(selected_ids) < needed:
                    remaining = candidates.filtered(lambda q: q.id not in selected_ids)
                    selected_ids.extend(remaining[:needed - len(selected_ids)].ids)

                section.write({'question_ids': [(6, 0, selected_ids)]})
                assembled.append({
                    'section_id': section.id,
                    'title': section.title,
                    'skill': section.skill or '',
                    'questions_assigned': len(selected_ids),
                    'question_ids': selected_ids,
                })

            return _json_response({'sections': assembled})

        except Exception as e:
            _logger.exception('auto_assemble failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # POST /api/exam/ielts/<int:exam_id>/suggest
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/suggest',
                type='http', auth='none', methods=['POST'], csrf=False)
    @jwt_required
    def suggest(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            body = _get_json_body()
            section_id = body.get('section_id')
            criteria = body.get('criteria', {})

            section = request.env['encoach.exam.custom.section'].sudo().browse(
                int(section_id)) if section_id else None
            if section_id and (not section or not section.exists()):
                return _error_response('Section not found', 404)

            domain = [('status', '=', 'active')]
            if exam.subject_id:
                domain.append(('subject_id', '=', exam.subject_id.id))

            if section and section.skill:
                domain.append(('skill', '=', section.skill))

            if criteria.get('difficulty'):
                domain.append(('difficulty', '=', criteria['difficulty']))
            if criteria.get('question_type'):
                domain.append(('question_type', '=', criteria['question_type']))

            if section:
                current_ids = section.question_ids.ids
                if current_ids:
                    domain.append(('id', 'not in', current_ids))

            suggestions = request.env['encoach.question'].sudo().search(
                domain, limit=int(criteria.get('limit', 10)))

            return _json_response({
                'suggestions': [_question_to_dict(q) for q in suggestions],
            })

        except Exception as e:
            _logger.exception('suggest failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # PUT /api/exam/ielts/<int:exam_id>/sections/<int:section_id>/questions
    # ------------------------------------------------------------------
    @http.route(
        '/api/exam/ielts/<int:exam_id>/sections/<int:section_id>/questions',
        type='http', auth='none', methods=['PUT'], csrf=False)
    @jwt_required
    def update_section_questions(self, exam_id, section_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            section = request.env['encoach.exam.custom.section'].sudo().browse(
                section_id)
            if not section.exists() or section.exam_id.id != exam.id:
                return _error_response('Section not found in this exam', 404)

            body = _get_json_body()
            question_ids = body.get('question_ids', [])
            if not isinstance(question_ids, list):
                return _error_response('question_ids must be a list', 400)

            valid = request.env['encoach.question'].sudo().browse(question_ids)
            valid_ids = valid.filtered(lambda q: q.exists()).ids

            section.write({'question_ids': [(6, 0, valid_ids)]})

            return _json_response(_section_to_dict(section))

        except Exception as e:
            _logger.exception('update_section_questions failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # GET /api/exam/ielts/<int:exam_id>/validate
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/validate',
                type='http', auth='none', methods=['GET'], csrf=False)
    @jwt_required
    def validate(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            errors = []
            warnings = []

            if not exam.section_ids:
                errors.append('Exam has no sections')

            for section in exam.section_ids:
                actual = len(section.question_ids)
                expected = section.question_count or 0

                if actual == 0:
                    errors.append(
                        f'Section "{section.title}" has no questions assigned')
                elif expected > 0 and actual < expected:
                    warnings.append(
                        f'Section "{section.title}" has {actual}/{expected} questions')

                if not section.time_limit_min:
                    warnings.append(
                        f'Section "{section.title}" has no time limit set')

            if not exam.total_time_min:
                warnings.append('Exam has no total time limit set')

            has_all_skills = {s.skill for s in exam.section_ids if s.skill}
            for skill in IELTS_SKILLS:
                if skill not in has_all_skills:
                    warnings.append(f'Missing IELTS section: {skill}')

            return _json_response({
                'valid': len(errors) == 0,
                'errors': errors,
                'warnings': warnings,
            })

        except Exception as e:
            _logger.exception('validate failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # POST /api/exam/ielts/<int:exam_id>/assign
    # ------------------------------------------------------------------
    @http.route('/api/exam/ielts/<int:exam_id>/assign',
                type='http', auth='none', methods=['POST'], csrf=False)
    @jwt_required
    def assign(self, exam_id, **kw):
        try:
            exam = request.env['encoach.exam.custom'].sudo().browse(exam_id)
            if not exam.exists():
                return _error_response('Exam not found', 404)

            body = _get_json_body()
            student_ids = body.get('student_ids', [])
            batch_id = body.get('batch_id')
            access_start = body.get('access_start')
            access_end = body.get('access_end')

            Assignment = request.env['encoach.exam.assignment'].sudo()
            created = 0

            if student_ids:
                for sid in student_ids:
                    Assignment.create({
                        'exam_id': exam.id,
                        'student_id': int(sid),
                        'access_start': access_start,
                        'access_end': access_end,
                        'status': 'assigned',
                    })
                    created += 1

            if batch_id:
                try:
                    batch = request.env['op.batch'].sudo().browse(int(batch_id))
                    if batch.exists():
                        batch_students = request.env['op.student'].sudo().search(
                            [('batch_id', '=', batch.id)])
                        for student in batch_students:
                            user = student.user_id if hasattr(student, 'user_id') else None
                            if user:
                                Assignment.create({
                                    'exam_id': exam.id,
                                    'student_id': user.id,
                                    'batch_id': batch.id,
                                    'access_start': access_start,
                                    'access_end': access_end,
                                    'status': 'assigned',
                                })
                                created += 1
                except Exception:
                    _logger.warning('Batch lookup failed for batch_id=%s', batch_id)

            return _json_response({'assignments_created': created})

        except Exception as e:
            _logger.exception('assign failed')
            return _error_response(str(e), 500)
