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

SUBJECTIVE_SKILLS = {'writing', 'speaking'}

BAND_TO_CEFR = [
    (8.5, 'c2'), (7.0, 'c1'), (5.5, 'b2'),
    (4.5, 'b1'), (3.5, 'a2'), (2.5, 'a1'), (0.0, 'pre_a1'),
]


def _band_to_cefr(band):
    for threshold, level in BAND_TO_CEFR:
        if band >= threshold:
            return level
    return 'pre_a1'


def _recompute_bands(attempt):
    """Recompute skill and overall bands after grading updates."""
    Answer = request.env['encoach.student.answer'].sudo()
    Score = request.env['encoach.score'].sudo()

    skill_totals = {}
    skill_max = {}

    for ans in Answer.search([('attempt_id', '=', attempt.id)]):
        q = ans.question_id
        skill = q.skill or 'general'
        skill_totals.setdefault(skill, 0.0)
        skill_max.setdefault(skill, 0.0)
        skill_totals[skill] += ans.score or 0.0
        skill_max[skill] += q.marks or 1.0

    band_map = {}
    skill_field_map = {
        'listening': 'listening_band',
        'reading': 'reading_band',
        'writing': 'writing_band',
        'speaking': 'speaking_band',
    }

    for skill, total in skill_totals.items():
        mx = skill_max.get(skill, 1.0) or 1.0
        band = round((total / mx) * 9.0 * 2) / 2
        band = min(band, 9.0)
        band_map[skill] = band

        existing = Score.search([
            ('attempt_id', '=', attempt.id), ('skill', '=', skill),
        ], limit=1)
        vals = {
            'band_score': band,
            'raw_score': total,
            'max_score': mx,
            'cefr_level': _band_to_cefr(band),
            'entity_id': attempt.entity_id.id if attempt.entity_id else False,
        }
        if existing:
            existing.write(vals)
        else:
            vals.update({'attempt_id': attempt.id, 'skill': skill})
            Score.create(vals)

    write_vals = {}
    for skill, field in skill_field_map.items():
        if skill in band_map:
            write_vals[field] = band_map[skill]

    counted = [b for s, b in band_map.items() if s in skill_field_map]
    overall = round(sum(counted) / len(counted) * 2) / 2 if counted else 0.0
    overall = min(overall, 9.0)
    write_vals['overall_band'] = overall
    write_vals['cefr_level'] = _band_to_cefr(overall)

    existing_overall = Score.search([
        ('attempt_id', '=', attempt.id), ('skill', '=', 'overall'),
    ], limit=1)
    overall_score_vals = {
        'band_score': overall,
        'raw_score': sum(skill_totals.values()),
        'max_score': sum(skill_max.values()),
        'cefr_level': _band_to_cefr(overall),
        'entity_id': attempt.entity_id.id if attempt.entity_id else False,
    }
    if existing_overall:
        existing_overall.write(overall_score_vals)
    else:
        overall_score_vals.update({'attempt_id': attempt.id, 'skill': 'overall'})
        Score.create(overall_score_vals)

    attempt.write(write_vals)
    return band_map, overall


class EncoachGradingController(http.Controller):

    # ------------------------------------------------------------------
    # GET /api/grading/queue
    # ------------------------------------------------------------------
    @http.route('/api/grading/queue', type='http', auth='none',
                methods=['GET'], csrf=False)
    @jwt_required
    def get_queue(self, **kw):
        try:
            user = request.env.user.sudo()
            domain = [('status', '=', 'scoring')]

            if user.entity_ids:
                domain.append(('entity_id', 'in', user.entity_ids.ids))

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

            Attempt = request.env['encoach.student.attempt'].sudo()
            attempts, total = _paginate(Attempt, domain, page, size, order='completed_at desc')

            items = []
            for att in attempts:
                skills_to_grade = set()
                for ans in att.answer_ids:
                    q = ans.question_id
                    if q.skill in SUBJECTIVE_SKILLS and not ans.score:
                        skills_to_grade.add(q.skill)

                items.append({
                    'attempt_id': att.id,
                    'student_name': att.student_id.name or '',
                    'student_id': att.student_id.id,
                    'exam_id': att.exam_id.id if att.exam_id else None,
                    'exam_name': att.exam_id.title if att.exam_id else '',
                    'submitted_at': att.completed_at,
                    'entity_id': att.entity_id.id if att.entity_id else None,
                    'skills_to_grade': list(skills_to_grade),
                })

            return _json_response({
                'items': items,
                'total': total,
                'page': page,
                'size': size,
            })

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

    # ------------------------------------------------------------------
    # GET /api/grading/<int:attempt_id>
    # ------------------------------------------------------------------
    @http.route('/api/grading/<int:attempt_id>', type='http', auth='none',
                methods=['GET'], csrf=False)
    @jwt_required
    def get_attempt(self, attempt_id, **kw):
        try:
            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.browse(attempt_id)
            if not attempt.exists():
                return _error_response('Attempt not found', 404)

            answers = []
            for ans in attempt.answer_ids:
                q = ans.question_id
                fb = request.env['encoach.feedback'].sudo().search([
                    ('attempt_id', '=', attempt.id),
                    ('question_id', '=', q.id),
                ], limit=1)
                answers.append({
                    'answer_id': ans.id,
                    'question_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 [],
                    'correct_answer': q.correct_answer or '',
                    'marks': q.marks,
                    'student_answer': ans.answer or '',
                    'is_correct': ans.is_correct,
                    'current_score': ans.score or 0.0,
                    'existing_feedback': {
                        'feedback_text': fb.feedback_text or '',
                        'rubric_scores': json.loads(fb.rubric_scores) if fb.rubric_scores else {},
                        'source': fb.source,
                        'graded_by': fb.graded_by.id if fb.graded_by else None,
                    } if fb else None,
                })

            return _json_response({
                'attempt_id': attempt.id,
                'student_id': attempt.student_id.id,
                'student_name': attempt.student_id.name or '',
                'exam_id': attempt.exam_id.id if attempt.exam_id else None,
                'exam_name': attempt.exam_id.title if attempt.exam_id else '',
                'status': attempt.status,
                'started_at': attempt.started_at,
                'completed_at': attempt.completed_at,
                'listening_band': attempt.listening_band,
                'reading_band': attempt.reading_band,
                'writing_band': attempt.writing_band,
                'speaking_band': attempt.speaking_band,
                'overall_band': attempt.overall_band,
                'answers': answers,
            })

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

    # ------------------------------------------------------------------
    # POST /api/grading/<int:attempt_id>/submit
    # ------------------------------------------------------------------
    @http.route('/api/grading/<int:attempt_id>/submit', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def submit_grade(self, attempt_id, **kw):
        try:
            body = _get_json_body()
            grades = body.get('grades', [])
            if not grades:
                return _error_response('grades array is required', 400)

            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.browse(attempt_id)
            if not attempt.exists():
                return _error_response('Attempt not found', 404)
            if attempt.status not in ('scoring', 'scored'):
                return _error_response('Attempt is not awaiting grading', 400)

            Answer = request.env['encoach.student.answer'].sudo()
            Feedback = request.env['encoach.feedback'].sudo()
            grader_id = request.env.user.id

            for grade in grades:
                q_id = grade.get('question_id')
                if not q_id:
                    continue
                q_id = int(q_id)
                score_val = float(grade.get('score', 0))
                feedback_text = grade.get('feedback_text', '')
                rubric_scores = grade.get('rubric_scores', {})

                ans = Answer.search([
                    ('attempt_id', '=', attempt.id),
                    ('question_id', '=', q_id),
                ], limit=1)
                if ans:
                    ans.write({'score': score_val, 'is_correct': score_val > 0})

                existing_fb = Feedback.search([
                    ('attempt_id', '=', attempt.id),
                    ('question_id', '=', q_id),
                    ('source', '=', 'teacher'),
                ], limit=1)
                fb_vals = {
                    'feedback_text': feedback_text,
                    'rubric_scores': json.dumps(rubric_scores) if rubric_scores else '',
                    'graded_by': grader_id,
                }
                if existing_fb:
                    existing_fb.write(fb_vals)
                else:
                    fb_vals.update({
                        'attempt_id': attempt.id,
                        'question_id': q_id,
                        'source': 'teacher',
                    })
                    Feedback.create(fb_vals)

            band_map, overall = _recompute_bands(attempt)

            exam = attempt.exam_id
            release_mode = exam.results_release_mode if exam else 'auto'

            from odoo.fields import Datetime as DT
            if release_mode == 'auto':
                attempt.write({'status': 'released', 'released_at': DT.now(),
                               'released_by': grader_id})
            elif release_mode == 'manual_approval':
                attempt.write({'status': 'pending_approval'})
            else:
                attempt.write({'status': 'scored'})

            scores = {}
            for s in attempt.score_ids:
                scores[s.skill] = {
                    'band_score': s.band_score,
                    'raw_score': s.raw_score,
                    'max_score': s.max_score,
                    'cefr_level': s.cefr_level or '',
                }

            return _json_response({
                'attempt_id': attempt.id,
                'new_status': attempt.status,
                'updated_scores': scores,
            })

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

    # ------------------------------------------------------------------
    # POST /api/grading/ai-suggest
    # ------------------------------------------------------------------
    @http.route('/api/grading/ai-suggest', type='http', auth='none',
                methods=['POST'], csrf=False)
    @jwt_required
    def ai_suggest(self, **kw):
        try:
            body = _get_json_body()
            attempt_id = body.get('attempt_id')
            question_id = body.get('question_id')
            if not attempt_id or not question_id:
                return _error_response('attempt_id and question_id are required', 400)

            Attempt = request.env['encoach.student.attempt'].sudo()
            attempt = Attempt.browse(int(attempt_id))
            if not attempt.exists():
                return _error_response('Attempt not found', 404)

            Question = request.env['encoach.question'].sudo()
            question = Question.browse(int(question_id))
            if not question.exists():
                return _error_response('Question not found', 404)

            Answer = request.env['encoach.student.answer'].sudo()
            ans = Answer.search([
                ('attempt_id', '=', attempt.id),
                ('question_id', '=', question.id),
            ], limit=1)

            student_response = ans.answer if ans else ''

            suggested_score = question.marks * 0.5
            suggested_feedback = (
                f"AI suggestion for {question.skill} {question.question_type} question. "
                f"Student provided a response of {len(student_response)} characters. "
                f"Suggested mid-range score based on rubric criteria."
            )
            confidence = 0.6

            if not student_response:
                suggested_score = 0.0
                suggested_feedback = "No response provided by student."
                confidence = 0.95

            rubric = None
            if attempt.exam_id and attempt.exam_id.template_id:
                Rubric = request.env['encoach.rubric'].sudo()
                rubric = Rubric.search([
                    ('skill', '=', question.skill),
                ], limit=1)

            if rubric:
                suggested_feedback += f" Rubric '{rubric.name}' criteria should be applied."

            return _json_response({
                'suggested_score': round(suggested_score, 1),
                'suggested_feedback': suggested_feedback,
                'confidence': confidence,
            })

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