"""
Practical assessment gate for requires_practical courses (Part 7).

Certificate issuance is deferred until a PracticalSubmission is approved.
"""
from __future__ import annotations

import logging

from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone

from apps.courses.models import (
    PRACTICAL_APPROVED,
    PRACTICAL_FILE,
    PRACTICAL_FIGMA,
    PRACTICAL_GITHUB,
    PRACTICAL_NEEDS_REVISION,
    PRACTICAL_PENDING,
    PRACTICAL_REJECTED,
    CourseExamAttempt,
    PracticalSubmission,
)

logger = logging.getLogger(__name__)


def attempt_needs_practical(attempt: CourseExamAttempt) -> bool:
    return bool(attempt.passed and attempt.course.requires_practical)


def practical_approved_for_attempt(attempt: CourseExamAttempt) -> bool:
    return PracticalSubmission.objects.filter(
        attempt=attempt,
        status=PRACTICAL_APPROVED,
    ).exists()


def get_practical_for_attempt(attempt: CourseExamAttempt) -> PracticalSubmission | None:
    return (
        PracticalSubmission.objects.filter(attempt=attempt)
        .order_by('-submitted_at')
        .first()
    )


def validate_submission_payload(submission_type: str, url: str, file_obj) -> None:
    if submission_type in (PRACTICAL_GITHUB, PRACTICAL_FIGMA):
        if not (url or '').strip():
            raise ValidationError('A link is required for this submission type.')
        if submission_type == PRACTICAL_GITHUB and 'github.com' not in url.lower():
            raise ValidationError('Please provide a GitHub repository URL (github.com).')
        if submission_type == PRACTICAL_FIGMA and 'figma.com' not in url.lower():
            raise ValidationError('Please provide a Figma file URL (figma.com).')
    elif submission_type == PRACTICAL_FILE:
        if not file_obj:
            raise ValidationError('Please upload a ZIP or project file.')
    else:
        raise ValidationError('Invalid submission type.')


@transaction.atomic
def submit_or_update_practical(
    *,
    attempt: CourseExamAttempt,
    user,
    submission_type: str,
    submission_url: str = '',
    submission_file=None,
) -> PracticalSubmission:
    """Create or resubmit practical work after a passing MCQ attempt."""
    attempt = CourseExamAttempt.objects.select_related('course').get(pk=attempt.pk)
    if attempt.user_id != user.pk:
        raise ValidationError('This exam attempt does not belong to you.')
    if not attempt.passed:
        raise ValidationError('Pass the MCQ exam before submitting a practical.')
    if not attempt.course.requires_practical:
        raise ValidationError('This course does not require a practical submission.')

    existing = PracticalSubmission.objects.select_for_update().filter(attempt=attempt).first()
    if existing and existing.status == PRACTICAL_APPROVED:
        raise ValidationError('This practical is already approved.')
    if existing and existing.status == PRACTICAL_PENDING:
        raise ValidationError('Your practical is already awaiting review.')

    validate_submission_payload(submission_type, submission_url, submission_file)

    if existing:
        existing.submission_type = submission_type
        existing.submission_url = submission_url.strip() if submission_type != PRACTICAL_FILE else ''
        if submission_type == PRACTICAL_FILE and submission_file:
            existing.submission_file = submission_file
        elif submission_type != PRACTICAL_FILE:
            existing.submission_file = None
        existing.status = PRACTICAL_PENDING
        existing.reviewer_notes = ''
        existing.reviewed_by = None
        existing.reviewed_at = None
        existing.submitted_at = timezone.now()
        existing.save()
        return existing

    return PracticalSubmission.objects.create(
        user=user,
        course=attempt.course,
        attempt=attempt,
        submission_type=submission_type,
        submission_url=submission_url.strip() if submission_type != PRACTICAL_FILE else '',
        submission_file=submission_file if submission_type == PRACTICAL_FILE else None,
        status=PRACTICAL_PENDING,
    )


@transaction.atomic
def review_practical(
    submission: PracticalSubmission,
    *,
    reviewer,
    action: str,
    notes: str = '',
) -> PracticalSubmission:
    """
    action: approve | reject | needs_revision
    On approve → enqueue certificate for the linked attempt.
    """
    submission = PracticalSubmission.objects.select_for_update().select_related(
        'attempt', 'course',
    ).get(pk=submission.pk)

    if submission.status == PRACTICAL_APPROVED and action == 'approve':
        return submission

    notes = (notes or '').strip()
    if action in ('reject', 'needs_revision') and not notes:
        raise ValidationError('Reviewer notes are required when rejecting or requesting revision.')

    if action == 'approve':
        submission.status = PRACTICAL_APPROVED
    elif action == 'reject':
        submission.status = PRACTICAL_REJECTED
    elif action == 'needs_revision':
        submission.status = PRACTICAL_NEEDS_REVISION
    else:
        raise ValidationError('Unknown review action.')

    submission.reviewer_notes = notes
    submission.reviewed_by = reviewer
    submission.reviewed_at = timezone.now()
    submission.save()

    if submission.status == PRACTICAL_APPROVED:
        from apps.courses.certificate_service import enqueue_certificate_for_attempt

        attempt_pk = submission.attempt_id

        def _issue():
            enqueue_certificate_for_attempt(attempt_pk)

        transaction.on_commit(_issue)
        logger.info(
            'Practical %s approved — certificate queued for attempt %s',
            submission.pk,
            attempt_pk,
        )

    return submission
