"""
Course exam-taking views (Part 5).
One question per screen on base_exam.html; soft anti-cheat (flag, don't auto-fail).
"""
from __future__ import annotations

import json

from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views import View
from django.views.generic import DetailView

from apps.courses.exam_engine import (
    ExamEngineError,
    apply_answers,
    begin_timed_exam,
    format_time_taken,
    get_or_start_attempt,
    log_attempt_event,
    submit_course_exam_attempt,
)
from apps.courses.models import (
    ATTEMPT_STARTED,
    Course,
    CourseExamAttempt,
    EVENT_COPY_ATTEMPT,
    EVENT_FULLSCREEN_ENTER,
    EVENT_FULLSCREEN_EXIT,
    EVENT_NAV_BACK,
    EVENT_PASTE_ATTEMPT,
    EVENT_TAB_SWITCH,
    EVENT_WARNING_SHOWN,
    EVENT_WINDOW_BLUR,
    TAB_SWITCH_FLAG_THRESHOLD,
)
from utils.permissions import StudentRequiredMixin


ALLOWED_EVENT_TYPES = {
    EVENT_TAB_SWITCH,
    EVENT_WINDOW_BLUR,
    EVENT_FULLSCREEN_EXIT,
    EVENT_FULLSCREEN_ENTER,
    EVENT_COPY_ATTEMPT,
    EVENT_PASTE_ATTEMPT,
    EVENT_NAV_BACK,
    EVENT_WARNING_SHOWN,
}


def _parse_answers_from_post(post) -> dict:
    answers = {}
    for key, val in post.items():
        if key.startswith('aq_'):
            answers[key[3:]] = val
    return answers


class StartCourseExamView(StudentRequiredMixin, View):
    """GET confirm / POST draw attempt → launch page."""
    template_name = 'courses/exam_start.html'

    def get_course(self, slug):
        return get_object_or_404(
            Course.objects.filter(status='published', is_active=True),
            slug=slug,
        )

    def get(self, request, slug):
        course = self.get_course(slug)
        open_attempt = CourseExamAttempt.objects.filter(
            user=request.user,
            course=course,
            status=ATTEMPT_STARTED,
            submitted_at__isnull=True,
        ).order_by('-started_at').first()
        if open_attempt and not (
            open_attempt.timer_started_at and open_attempt.time_expired()
        ):
            return redirect('courses:exam_launch', attempt_id=open_attempt.pk)
        return render(request, self.template_name, {'course': course})

    def post(self, request, slug):
        course = self.get_course(slug)
        try:
            attempt = get_or_start_attempt(request.user, course)
        except ExamEngineError as exc:
            messages.error(request, str(exc))
            return redirect('courses:course_detail', slug=slug)
        return redirect('courses:exam_launch', attempt_id=attempt.pk)


class CourseExamLaunchView(StudentRequiredMixin, View):
    template_name = 'courses/exam_launch.html'

    def get(self, request, attempt_id):
        attempt = get_object_or_404(
            CourseExamAttempt.objects.select_related('course'),
            pk=attempt_id,
            user=request.user,
        )
        if attempt.submitted_at:
            return redirect('courses:exam_result', attempt_id=attempt.pk)
        if attempt.timer_started_at and attempt.time_expired():
            submit_course_exam_attempt(attempt, {}, auto_submitted=True)
            return redirect('courses:exam_result', attempt_id=attempt.pk)
        return render(request, self.template_name, {
            'attempt': attempt,
            'course': attempt.course,
            'take_exam_url': request.build_absolute_uri(
                reverse('courses:take_exam', args=[attempt.pk])
            ),
            'flag_threshold': TAB_SWITCH_FLAG_THRESHOLD,
        })


class TakeCourseExamView(StudentRequiredMixin, View):
    template_name = 'courses/take_exam.html'

    def get_attempt(self, request, attempt_id):
        return get_object_or_404(
            CourseExamAttempt.objects.select_related('course'),
            pk=attempt_id,
            user=request.user,
        )

    def get(self, request, attempt_id):
        attempt = self.get_attempt(request, attempt_id)
        if attempt.submitted_at:
            return redirect('courses:exam_result', attempt_id=attempt.pk)
        if attempt.timer_started_at and attempt.time_expired():
            submit_course_exam_attempt(attempt, {}, auto_submitted=True)
            messages.warning(request, 'Time expired — your exam was submitted automatically.')
            return redirect('courses:exam_result', attempt_id=attempt.pk)

        questions = list(
            attempt.attempt_questions.select_related('question').order_by('order_index')
        )
        return render(request, self.template_name, {
            'attempt': attempt,
            'course': attempt.course,
            'questions': questions,
            'is_practice': attempt.is_practice_phase,
            'seconds_remaining': attempt.seconds_remaining,
            'deadline_iso': (
                attempt.deadline_at.isoformat() if attempt.timer_started_at else ''
            ),
            'time_limit_minutes': attempt.course.duration_minutes or 120,
            'flag_threshold': TAB_SWITCH_FLAG_THRESHOLD,
            'event_url': reverse('courses:exam_event', args=[attempt.pk]),
            'save_answer_url': reverse('courses:exam_save_answer', args=[attempt.pk]),
        })

    def post(self, request, attempt_id):
        attempt = self.get_attempt(request, attempt_id)
        if attempt.submitted_at:
            return redirect('courses:exam_result', attempt_id=attempt.pk)
        if attempt.is_practice_phase:
            messages.warning(
                request,
                'Complete the practice question first, then start the real exam.',
            )
            return redirect('courses:take_exam', attempt_id=attempt.pk)

        answers = _parse_answers_from_post(request.POST)
        auto = request.POST.get('auto_submit') == '1'
        try:
            submit_course_exam_attempt(attempt, answers, auto_submitted=auto)
        except ExamEngineError as exc:
            messages.error(request, str(exc))
            return redirect('courses:course_detail', slug=attempt.course.slug)
        return redirect('courses:exam_result', attempt_id=attempt.pk)


class BeginCourseTimedExamView(StudentRequiredMixin, View):
    def post(self, request, attempt_id):
        attempt = get_object_or_404(
            CourseExamAttempt.objects.select_related('course'),
            pk=attempt_id,
            user=request.user,
        )
        if attempt.submitted_at:
            return redirect('courses:exam_result', attempt_id=attempt.pk)
        try:
            begin_timed_exam(attempt)
        except ExamEngineError as exc:
            messages.error(request, str(exc))
            return redirect('courses:course_detail', slug=attempt.course.slug)
        return redirect('courses:take_exam', attempt_id=attempt.pk)


class CourseExamEventView(StudentRequiredMixin, View):
    """AJAX / beacon endpoint for anti-cheat telemetry."""

    def post(self, request, attempt_id):
        attempt = get_object_or_404(
            CourseExamAttempt,
            pk=attempt_id,
            user=request.user,
        )
        event_type = (request.POST.get('event_type') or '').strip()
        detail = (request.POST.get('detail') or '')[:255]

        if not event_type and request.body:
            try:
                body = json.loads(request.body.decode('utf-8') or '{}')
                event_type = (body.get('event_type') or '').strip()
                detail = (body.get('detail') or detail or '')[:255]
            except (json.JSONDecodeError, UnicodeDecodeError):
                return JsonResponse({'ok': False, 'error': 'bad json'}, status=400)

        if event_type not in ALLOWED_EVENT_TYPES:
            return JsonResponse({'ok': False, 'error': 'invalid event'}, status=400)

        event = log_attempt_event(attempt, event_type, detail=detail)
        attempt.refresh_from_db()
        return JsonResponse({
            'ok': True,
            'event_id': event.pk,
            'tab_switch_count': attempt.tab_switch_count,
            'is_flagged_for_review': attempt.is_flagged_for_review,
            'flag_threshold': TAB_SWITCH_FLAG_THRESHOLD,
        })


class CourseExamSaveAnswerView(StudentRequiredMixin, View):
    """Persist a single answer mid-exam (crash resilience)."""

    def post(self, request, attempt_id):
        attempt = get_object_or_404(
            CourseExamAttempt,
            pk=attempt_id,
            user=request.user,
            submitted_at__isnull=True,
        )
        if attempt.is_practice_phase:
            return JsonResponse({'ok': False, 'error': 'practice'}, status=400)
        answers = _parse_answers_from_post(request.POST)
        if not answers and request.POST.get('aq_id'):
            answers = {request.POST.get('aq_id'): request.POST.get('selected')}
        apply_answers(attempt, answers)
        return JsonResponse({'ok': True})


class CourseExamResultView(StudentRequiredMixin, DetailView):
    template_name = 'courses/exam_result.html'
    context_object_name = 'attempt'
    pk_url_kwarg = 'attempt_id'

    def get_queryset(self):
        return CourseExamAttempt.objects.filter(
            user=self.request.user,
            submitted_at__isnull=False,
        ).select_related('course')

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        attempt = ctx['attempt']
        ctx['course'] = attempt.course
        ctx['time_taken_display'] = format_time_taken(attempt.time_taken_seconds)
        ctx['topic_breakdown'] = attempt.topic_breakdown or []
        ctx['pass_mark'] = attempt.course.pass_percentage or 70
        cert = getattr(attempt, 'certificate', None)
        if cert is None:
            from apps.courses.models import CourseCertificate
            cert = CourseCertificate.objects.filter(attempt=attempt).first()
        ctx['certificate'] = cert

        needs_practical = bool(attempt.passed and attempt.course.requires_practical)
        ctx['needs_practical'] = needs_practical
        practical = None
        if needs_practical:
            from apps.courses.practical_service import get_practical_for_attempt
            practical = get_practical_for_attempt(attempt)
        ctx['practical'] = practical
        ctx['practical_blocks_certificate'] = needs_practical and not (
            practical and practical.is_approved
        )
        if cert and cert.is_valid:
            from apps.courses.growth_service import freelancer_share_blurb
            verify_url = self.request.build_absolute_uri(cert.get_verify_path())
            ctx['freelancer_blurb'] = freelancer_share_blurb(cert, verify_url)
        else:
            ctx['freelancer_blurb'] = ''
        return ctx
