import json
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 _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],
    }


class EncoachCustomExamController(http.Controller):

    # ------------------------------------------------------------------
    # POST /api/exam/custom/create
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/create', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def create(self, **kw):
        try:
            body = _get_json_body()
            title = (body.get('title') or '').strip()
            if not title:
                return _error_response('title is required', 400)

            vals = {
                'title': title,
                'teacher_id': request.env.user.id,
                'subject_id': body.get('subject_id') or False,
                'template_id': body.get('template_id') or False,
                'entity_id': body.get('entity_id') or False,
                'description': body.get('description', ''),
                'total_time_min': body.get('total_time_min', 0),
                'pass_threshold': body.get('pass_threshold', 0.0),
                'results_release_mode': body.get('results_release_mode', 'auto'),
                'randomize_questions': body.get('randomize_questions', False),
                'status': 'draft',
            }

            exam = request.env['encoach.exam.custom'].sudo().create(vals)

            sections = body.get('sections', [])
            Section = request.env['encoach.exam.custom.section'].sudo()
            for idx, sec_data in enumerate(sections):
                sec_vals = {
                    'exam_id': exam.id,
                    'title': sec_data.get('title', f'Section {idx + 1}'),
                    'skill': sec_data.get('skill', ''),
                    'question_count': sec_data.get('question_count', 0),
                    'time_limit_min': sec_data.get('time_limit_min', 0),
                    'scoring_method': sec_data.get('scoring_method', 'auto'),
                    'sequence': sec_data.get('sequence', (idx + 1) * 10),
                }
                section = Section.create(sec_vals)

                q_ids = sec_data.get('question_ids', [])
                if q_ids:
                    section.write({'question_ids': [(6, 0, q_ids)]})

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

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

    # ------------------------------------------------------------------
    # GET /api/exam/custom/<int:exam_id>
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<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('custom exam get failed')
            return _error_response(str(e), 500)

    # ------------------------------------------------------------------
    # PUT /api/exam/custom/<int:exam_id>
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<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', 'subject_id'}
            vals = {k: v for k, v in body.items() if k in allowed}

            if vals:
                exam.write(vals)

            sections_data = body.get('sections')
            if sections_data and isinstance(sections_data, list):
                Section = request.env['encoach.exam.custom.section'].sudo()

                existing_ids = set(exam.section_ids.ids)
                incoming_ids = set()

                for idx, sec_data in enumerate(sections_data):
                    sec_id = sec_data.get('id')
                    sec_vals = {
                        'title': sec_data.get('title', f'Section {idx + 1}'),
                        'skill': sec_data.get('skill', ''),
                        'question_count': sec_data.get('question_count', 0),
                        'time_limit_min': sec_data.get('time_limit_min', 0),
                        'scoring_method': sec_data.get('scoring_method', 'auto'),
                        'sequence': sec_data.get('sequence', (idx + 1) * 10),
                    }

                    if sec_id and sec_id in existing_ids:
                        section = Section.browse(sec_id)
                        section.write(sec_vals)
                        incoming_ids.add(sec_id)
                    else:
                        sec_vals['exam_id'] = exam.id
                        section = Section.create(sec_vals)
                        incoming_ids.add(section.id)

                    q_ids = sec_data.get('question_ids')
                    if q_ids is not None:
                        section.write({'question_ids': [(6, 0, q_ids)]})

                to_delete = existing_ids - incoming_ids
                if to_delete:
                    Section.browse(list(to_delete)).unlink()

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

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

    # ------------------------------------------------------------------
    # DELETE /api/exam/custom/<int:exam_id>
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<int:exam_id>', type='http', auth='none',
                methods=['DELETE'], csrf=False)
    @jwt_required
    def delete(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 deleted', 400)

            exam.section_ids.unlink()
            exam.unlink()

            return _json_response({'deleted': True}, 204)

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

    # ------------------------------------------------------------------
    # POST /api/exam/custom/<int:exam_id>/save-template
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<int:exam_id>/save-template',
                type='http', auth='none', methods=['POST'], csrf=False)
    @jwt_required
    def save_template(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)

            structure = []
            for sec in exam.section_ids.sorted('sequence'):
                structure.append({
                    '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,
                })

            body = _get_json_body()
            template_name = body.get('name', f'Template from: {exam.title}')

            template = request.env['encoach.exam.template'].sudo().create({
                'name': template_name,
                'type': 'custom',
                'teacher_id': request.env.user.id,
                'subject_id': exam.subject_id.id if exam.subject_id else False,
                'entity_id': exam.entity_id.id if exam.entity_id else False,
                'structure': json.dumps(structure),
                '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,
            })

            return _json_response({
                'id': template.id,
                'name': template.name,
                'type': template.type,
                'structure': structure,
            }, 201)

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

    # ------------------------------------------------------------------
    # GET /api/exam/custom/<int:exam_id>/validate
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<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.scoring_method:
                    warnings.append(
                        f'Section "{section.title}" has no scoring method set')

                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')
            if not exam.pass_threshold:
                warnings.append('Exam has no pass threshold set')

            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/custom/<int:exam_id>/assign
    # ------------------------------------------------------------------
    @http.route('/api/exam/custom/<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)
