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

_logger = logging.getLogger(__name__)


def _question_to_student_dict(q):
    return {
        'id': q.id,
        'skill': q.skill or '',
        'question_type': q.question_type or '',
        'stem': q.stem or '',
        'options': json.loads(q.options) if q.options else [],
        'marks': q.marks,
        'difficulty': q.difficulty or '',
        'source_type': q.source_type or '',
        'source_id': q.source_id or 0,
    }


class ExamSessionController(http.Controller):

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

            uid = request.env.user.id
            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.search([
                ('student_id', '=', uid),
                ('exam_id', '=', exam.id),
                ('status', 'in', ['in_progress', 'scoring']),
            ], limit=1, order='id desc')

            if not attempt:
                attempt = Attempt.create({
                    'student_id': uid,
                    'exam_id': exam.id,
                    'status': 'in_progress',
                    'entity_id': exam.entity_id.id if exam.entity_id else False,
                })

            Answer = request.env['encoach.student.answer'].sudo()
            saved_answers = {}
            for ans in Answer.search([('attempt_id', '=', attempt.id)]):
                saved_answers[ans.question_id.id] = ans.answer or ''

            sections = []
            for sec in exam.section_ids.sorted('sequence'):
                questions = []
                for q in sec.question_ids:
                    q_dict = _question_to_student_dict(q)
                    q_dict['saved_answer'] = saved_answers.get(q.id, '')
                    questions.append(q_dict)

                sec_dict = {
                    'id': sec.id,
                    'title': sec.title,
                    'skill': sec.skill or '',
                    'difficulty': sec.difficulty or '',
                    'time_limit_min': sec.time_limit_min or 0,
                    'total_marks': sec.total_marks or 0,
                    'scoring_method': sec.scoring_method or 'auto',
                    'sequence': sec.sequence,
                    'questions': questions,
                }
                if sec.passage_text:
                    sec_dict['passage_text'] = sec.passage_text
                if sec.instructions_text:
                    sec_dict['instructions_text'] = sec.instructions_text
                if sec.content_json:
                    try:
                        sec_dict['content'] = json.loads(sec.content_json)
                    except (json.JSONDecodeError, TypeError):
                        pass
                sections.append(sec_dict)

            return _json_response({
                'attempt_id': attempt.id,
                'exam_id': exam.id,
                'exam_title': exam.title,
                'exam_mode': exam.exam_mode or 'official',
                'total_time_min': exam.total_time_min or 0,
                'total_marks': exam.total_marks or 0,
                'grading_system': exam.grading_system or 'ielts',
                'access_type': exam.access_type or 'private',
                'randomize_questions': exam.randomize_questions or False,
                'status': attempt.status,
                'started_at': str(attempt.started_at) if attempt.started_at else None,
                'sections': sections,
            })
        except Exception as e:
            _logger.exception('get_session failed')
            return _error_response(str(e), 500)

    @http.route('/api/exam/<int:exam_id>/autosave', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def autosave(self, exam_id, **kw):
        try:
            body = _get_json_body()
            attempt_id = body.get('attempt_id')
            raw_answers = body.get('answers', [])

            if not attempt_id:
                return _error_response('attempt_id is required', 400)

            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.browse(int(attempt_id))
            if not attempt.exists() or attempt.student_id.id != request.env.user.id:
                return _error_response('Invalid attempt', 403)

            answer_pairs = self._normalize_answers(raw_answers)

            Answer = request.env['encoach.student.answer'].sudo()
            saved = 0
            for q_id, answer_val in answer_pairs:
                existing = Answer.search([
                    ('attempt_id', '=', attempt.id),
                    ('question_id', '=', q_id),
                ], limit=1)
                if existing:
                    existing.write({'answer': str(answer_val)})
                else:
                    Answer.create({
                        'attempt_id': attempt.id,
                        'question_id': q_id,
                        'answer': str(answer_val),
                    })
                saved += 1

            return _json_response({'saved': saved})
        except Exception as e:
            _logger.exception('autosave failed')
            return _error_response(str(e), 500)

    @http.route('/api/exam/<int:exam_id>/submit', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def submit_exam(self, exam_id, **kw):
        try:
            body = _get_json_body()
            attempt_id = body.get('attempt_id')
            raw_answers = body.get('answers', [])

            if not attempt_id:
                return _error_response('attempt_id is required', 400)

            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.browse(int(attempt_id))
            if not attempt.exists() or attempt.student_id.id != request.env.user.id:
                return _error_response('Invalid attempt', 403)

            answer_pairs = self._normalize_answers(raw_answers)

            Answer = request.env['encoach.student.answer'].sudo()
            Question = request.env['encoach.question'].sudo()

            total_score = 0.0
            max_score = 0.0

            for q_id, answer_val in answer_pairs:
                q = Question.browse(q_id)
                if not q.exists():
                    continue

                is_correct = False
                score = 0.0
                correct = (q.correct_answer or '').strip().lower()
                given = str(answer_val).strip().lower()

                if correct and given:
                    is_correct = correct == given
                    score = q.marks if is_correct else 0.0

                existing = Answer.search([
                    ('attempt_id', '=', attempt.id),
                    ('question_id', '=', q_id),
                ], limit=1)
                vals = {
                    'answer': str(answer_val),
                    'score': score,
                    'is_correct': is_correct,
                }
                if existing:
                    existing.write(vals)
                else:
                    vals.update({
                        'attempt_id': attempt.id,
                        'question_id': q_id,
                    })
                    Answer.create(vals)

                total_score += score
                max_score += q.marks

            from odoo.fields import Datetime
            attempt.write({
                'status': 'completed',
                'finished_at': Datetime.now(),
                'total_score': total_score,
                'max_score': max_score,
            })

            return _json_response({
                'attempt_id': attempt.id,
                'status': 'completed',
                'total_score': total_score,
                'max_score': max_score,
                'percentage': round(total_score / max_score * 100, 1) if max_score else 0,
                'results_available': True,
            })
        except Exception as e:
            _logger.exception('submit_exam failed')
            return _error_response(str(e), 500)

    @http.route('/api/exam/<int:exam_id>/status', type='http', auth='none',
                methods=['GET'], csrf=False)
    @jwt_required
    def get_status(self, exam_id, **kw):
        try:
            uid = request.env.user.id
            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.search([
                ('student_id', '=', uid),
                ('exam_id', '=', exam_id),
            ], limit=1, order='id desc')

            if not attempt:
                return _error_response('No attempt found', 404)

            scores_available = attempt.status == 'completed' and attempt.max_score > 0
            return _json_response({
                'status': attempt.status,
                'scores_available': scores_available,
                'total_score': attempt.total_score,
                'max_score': attempt.max_score,
                'percentage': round(attempt.total_score / attempt.max_score * 100, 1) if attempt.max_score else 0,
            })
        except Exception as e:
            _logger.exception('get_status failed')
            return _error_response(str(e), 500)

    @http.route('/api/exam/<int:exam_id>/results', type='http', auth='none',
                methods=['GET'], csrf=False)
    @jwt_required
    def get_results(self, exam_id, **kw):
        try:
            uid = request.env.user.id
            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.search([
                ('student_id', '=', uid),
                ('exam_id', '=', exam_id),
                ('status', '=', 'completed'),
            ], limit=1, order='id desc')

            if not attempt:
                return _error_response('No completed attempt found', 404)

            Answer = request.env['encoach.student.answer'].sudo()
            answers = []
            for ans in Answer.search([('attempt_id', '=', attempt.id)]):
                answers.append({
                    'question_id': ans.question_id.id,
                    'answer': ans.answer or '',
                    'score': ans.score,
                    'is_correct': ans.is_correct,
                    'feedback': ans.feedback or '',
                })

            Exam = request.env['encoach.exam.custom'].sudo()
            exam = Exam.browse(exam_id)

            return _json_response({
                'attempt_id': attempt.id,
                'exam_id': exam_id,
                'exam_title': exam.title if exam.exists() else '',
                'status': attempt.status,
                'total_score': attempt.total_score,
                'max_score': attempt.max_score,
                'percentage': round(attempt.total_score / attempt.max_score * 100, 1) if attempt.max_score else 0,
                'started_at': str(attempt.started_at) if attempt.started_at else None,
                'finished_at': str(attempt.finished_at) if attempt.finished_at else None,
                'answers': answers,
            })
        except Exception as e:
            _logger.exception('get_results failed')
            return _error_response(str(e), 500)

    @staticmethod
    def _normalize_answers(raw):
        """Accept answers as list of {question_id, answer} or dict {qid: answer}."""
        if isinstance(raw, list):
            pairs = []
            for item in raw:
                if isinstance(item, dict):
                    qid = item.get('question_id') or item.get('qid')
                    ans = item.get('answer', '')
                    if qid:
                        pairs.append((int(qid), ans))
            return pairs
        if isinstance(raw, dict):
            return [(int(k), v) for k, v in raw.items()]
        return []
