import json
import logging
from datetime import datetime

from odoo import http
from odoo.http import request

from odoo.addons.encoach_api.controllers.base import (
    validate_token, _json_response as _base_json_response, _error_response,
)

_logger = logging.getLogger(__name__)


def _json_response(data, status=200):
    return request.make_json_response(data, status=status)


def _require_jwt():
    """Validate JWT and set the user context. Return user or error response."""
    user = validate_token()
    if not user:
        return None, _error_response("Authentication required", status=401)
    request.update_env(user=user.id)
    return user, None


def _normalize_dt(val):
    """Convert ISO datetime strings (with T) to Odoo-compatible format."""
    if not val or not isinstance(val, str):
        return val
    return val.replace('T', ' ').replace('Z', '').split('+')[0]


def _schedule_to_dict(rec):
    return {
        'id': rec.id,
        'name': rec.name,
        'exam_id': rec.exam_id.id if rec.exam_id else None,
        'exam_title': rec.exam_id.title if rec.exam_id else '',
        'entity_id': rec.entity_id.id if rec.entity_id else None,
        'entity_name': rec.entity_id.name if rec.entity_id else '',
        'start_date': rec.start_date.isoformat() if rec.start_date else None,
        'end_date': rec.end_date.isoformat() if rec.end_date else None,
        'state': rec.state,
        'assign_mode': rec.assign_mode,
        'full_length': rec.full_length,
        'generate_different': rec.generate_different,
        'auto_release_results': rec.auto_release_results,
        'auto_start': rec.auto_start,
        'official_exam': rec.official_exam,
        'hide_assignee_details': rec.hide_assignee_details,
        'batch_ids': rec.batch_ids.ids,
        'batch_names': [b.name for b in rec.batch_ids],
        'student_ids': rec.student_ids.ids,
        'assignee_count': rec.assignee_count,
        'completed_count': rec.completed_count,
        'created': rec.create_date.strftime('%Y-%m-%d') if rec.create_date else '',
    }


class EncoachExamScheduleController(http.Controller):

    @http.route('/api/exam-schedules', type='http', auth='none',
                methods=['GET'], csrf=False)
    def list_schedules(self, **kw):
        try:
            user, err = _require_jwt()
            if err:
                return err
            Schedule = request.env['encoach.exam.schedule'].sudo()
            domain = []
            state_filter = kw.get('state')
            if state_filter:
                domain.append(('state', '=', state_filter))

            limit = int(kw.get('limit', 50))
            offset = int(kw.get('offset', 0))
            total = Schedule.search_count(domain)
            records = Schedule.search(domain, limit=limit, offset=offset,
                                      order='start_date desc')
            return _json_response({
                'items': [_schedule_to_dict(r) for r in records],
                'total': total,
            })
        except Exception as e:
            _logger.exception('exam-schedules list failed')
            return _json_response({'error': str(e)}, 500)

    @http.route('/api/exam-schedules', type='http', auth='none',
                methods=['POST'], csrf=False)
    def create_schedule(self, **kw):
        try:
            user, err = _require_jwt()
            if err:
                return err
            body = json.loads(request.httprequest.data or '{}')
            name = body.get('name', '').strip()
            exam_id = body.get('exam_id')
            if not name or not exam_id:
                return _json_response({'error': 'name and exam_id are required'}, 400)

            start_date = _normalize_dt(body.get('start_date'))
            end_date = _normalize_dt(body.get('end_date'))
            if not start_date or not end_date:
                return _json_response({'error': 'start_date and end_date are required'}, 400)

            now = datetime.now()
            try:
                sd = datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S')
            except ValueError:
                sd = datetime.strptime(start_date[:19], '%Y-%m-%d %H:%M:%S')
            try:
                ed = datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S')
            except ValueError:
                ed = datetime.strptime(end_date[:19], '%Y-%m-%d %H:%M:%S')

            if sd <= now and ed > now:
                initial_state = 'active'
            elif sd > now:
                initial_state = 'planned'
            else:
                initial_state = 'past'

            vals = {
                'name': name,
                'exam_id': int(exam_id),
                'entity_id': body.get('entity_id') and int(body['entity_id']) or False,
                'start_date': start_date,
                'end_date': end_date,
                'state': initial_state,
                'assign_mode': body.get('assign_mode', 'batch'),
                'full_length': body.get('full_length', True),
                'generate_different': body.get('generate_different', False),
                'auto_release_results': body.get('auto_release_results', False),
                'auto_start': body.get('auto_start', False),
                'official_exam': body.get('official_exam', False),
                'hide_assignee_details': body.get('hide_assignee_details', False),
            }

            batch_ids = body.get('batch_ids', [])
            if batch_ids:
                vals['batch_ids'] = [(6, 0, [int(b) for b in batch_ids])]

            student_ids = body.get('student_ids', [])
            if student_ids:
                vals['student_ids'] = [(6, 0, [int(s) for s in student_ids])]

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

            self._create_individual_assignments(rec)

            return _json_response(_schedule_to_dict(rec), 201)
        except Exception as e:
            _logger.exception('exam-schedule create failed')
            return _json_response({'error': str(e)}, 500)

    def _create_individual_assignments(self, schedule):
        """Create individual assignment records for each targeted student."""
        Assignment = request.env['encoach.exam.assignment'].sudo()
        created_user_ids = set()

        if schedule.student_ids:
            for user in schedule.student_ids:
                if user.id not in created_user_ids:
                    Assignment.create({
                        'exam_id': schedule.exam_id.id,
                        'schedule_id': schedule.id,
                        'student_id': user.id,
                        'access_start': schedule.start_date,
                        'access_end': schedule.end_date,
                        'status': 'assigned',
                    })
                    created_user_ids.add(user.id)

        for batch in schedule.batch_ids:
            try:
                students = request.env['op.student'].sudo().search([('batch_id', '=', batch.id)])
                for student in students:
                    user = student.user_id if hasattr(student, 'user_id') else None
                    if user and user.id not in created_user_ids:
                        Assignment.create({
                            'exam_id': schedule.exam_id.id,
                            'schedule_id': schedule.id,
                            'student_id': user.id,
                            'batch_id': batch.id,
                            'access_start': schedule.start_date,
                            'access_end': schedule.end_date,
                            'status': 'assigned',
                        })
                        created_user_ids.add(user.id)
            except Exception:
                _logger.warning('Batch student lookup failed for batch %s', batch.id)

        if schedule.assign_mode == 'entity' and schedule.entity_id:
            entity_users = schedule.entity_id.user_ids
            for user in entity_users:
                if user.id not in created_user_ids:
                    Assignment.create({
                        'exam_id': schedule.exam_id.id,
                        'schedule_id': schedule.id,
                        'student_id': user.id,
                        'access_start': schedule.start_date,
                        'access_end': schedule.end_date,
                        'status': 'assigned',
                    })
                    created_user_ids.add(user.id)

    @http.route('/api/exam-schedules/<int:schedule_id>', type='http', auth='none',
                methods=['PUT'], csrf=False)
    def update_schedule(self, schedule_id, **kw):
        try:
            user, err = _require_jwt()
            if err:
                return err
            rec = request.env['encoach.exam.schedule'].sudo().browse(schedule_id)
            if not rec.exists():
                return _json_response({'error': 'Not found'}, 404)

            body = json.loads(request.httprequest.data or '{}')
            vals = {}
            for f in ('name', 'assign_mode',
                      'full_length', 'generate_different', 'auto_release_results',
                      'auto_start', 'official_exam', 'hide_assignee_details'):
                if f in body:
                    vals[f] = body[f]
            if 'start_date' in body:
                vals['start_date'] = _normalize_dt(body['start_date'])
            if 'end_date' in body:
                vals['end_date'] = _normalize_dt(body['end_date'])
            if 'entity_id' in body:
                vals['entity_id'] = body['entity_id'] and int(body['entity_id']) or False
            if 'batch_ids' in body:
                vals['batch_ids'] = [(6, 0, [int(b) for b in body['batch_ids']])]
            if 'student_ids' in body:
                vals['student_ids'] = [(6, 0, [int(s) for s in body['student_ids']])]
            if 'state' in body:
                vals['state'] = body['state']
            if vals:
                rec.write(vals)
            return _json_response(_schedule_to_dict(rec))
        except Exception as e:
            _logger.exception('exam-schedule update failed')
            return _json_response({'error': str(e)}, 500)

    @http.route('/api/exam-schedules/<int:schedule_id>', type='http', auth='none',
                methods=['DELETE'], csrf=False)
    def delete_schedule(self, schedule_id, **kw):
        try:
            user, err = _require_jwt()
            if err:
                return err
            rec = request.env['encoach.exam.schedule'].sudo().browse(schedule_id)
            if not rec.exists():
                return _json_response({'error': 'Not found'}, 404)
            rec.assignment_ids.unlink()
            rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            _logger.exception('exam-schedule delete failed')
            return _json_response({'error': str(e)}, 500)

    @http.route('/api/exam-schedules/<int:schedule_id>/archive', type='http', auth='none',
                methods=['POST'], csrf=False)
    def archive_schedule(self, schedule_id, **kw):
        try:
            user, err = _require_jwt()
            if err:
                return err
            rec = request.env['encoach.exam.schedule'].sudo().browse(schedule_id)
            if not rec.exists():
                return _json_response({'error': 'Not found'}, 404)
            rec.write({'state': 'archived'})
            return _json_response(_schedule_to_dict(rec))
        except Exception as e:
            _logger.exception('exam-schedule archive failed')
            return _json_response({'error': str(e)}, 500)

    @http.route('/api/student/my-exams', type='http', auth='none',
                methods=['GET'], csrf=False)
    def student_my_exams(self, **kw):
        """Return exam assignments for the current student user."""
        try:
            user, err = _require_jwt()
            if err:
                return err
            Assignment = request.env['encoach.exam.assignment'].sudo()
            assignments = Assignment.search([
                ('student_id', '=', user.id),
                ('status', 'in', ['assigned', 'started']),
            ], order='access_start asc')

            result = []
            for a in assignments:
                schedule = a.schedule_id
                state = schedule.state if schedule else 'active'
                result.append({
                    'id': a.id,
                    'exam_id': a.exam_id.id,
                    'exam_title': a.exam_id.title if a.exam_id else '',
                    'schedule_name': schedule.name if schedule else '',
                    'start_date': a.access_start.isoformat() if a.access_start else None,
                    'end_date': a.access_end.isoformat() if a.access_end else None,
                    'status': a.status,
                    'schedule_state': state,
                    'auto_start': schedule.auto_start if schedule else False,
                    'can_start': state == 'active' and a.status == 'assigned',
                })
            return _json_response({'items': result})
        except Exception as e:
            _logger.exception('student my-exams failed')
            return _json_response({'error': str(e)}, 500)
