import logging
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Request

logger = logging.getLogger(__name__)
from pydantic import BaseModel
from sqlalchemy.orm import Session
from sqlalchemy import func

from app.database import get_db
from app.models.quiz import Quiz
from app.models.question import Question
from app.models.attempt import QuizAttempt, AttemptAnswer
from app.models.pdf_document import PDFDocument
from app.models.user import User
from app.schemas.attempt import (
    AttemptSubmit,
    AttemptResponse,
    AttemptDetail,
    AnswerDetail,
    DashboardStats,
    QuizStats,
)
from app.utils.auth import get_current_user
from app.utils.quiz_questions import get_quiz_questions

router = APIRouter()


@router.post("/start", response_model=AttemptResponse)
def start_attempt(
    quiz_id: int,
    fresh: bool = False,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    quiz = db.query(Quiz).filter(Quiz.id == quiz_id).first()
    if not quiz:
        raise HTTPException(status_code=404, detail="Quiz not found")

    # Enforce max_attempts
    if quiz.max_attempts:
        completed_count = db.query(QuizAttempt).filter(
            QuizAttempt.quiz_id == quiz_id,
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.isnot(None),
        ).count()
        if completed_count >= quiz.max_attempts:
            raise HTTPException(status_code=403, detail="Maximum attempts reached for this quiz")

    # Reuse the most recent incomplete attempt unless fresh=true
    if not fresh:
        existing = db.query(QuizAttempt).filter(
            QuizAttempt.quiz_id == quiz_id,
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.is_(None),
        ).order_by(QuizAttempt.started_at.desc()).first()
        if existing:
            return AttemptResponse(
                id=existing.id,
                quiz_id=existing.quiz_id,
                score=0,
                total_questions=existing.total_questions,
                percentage=0.0,
                started_at=existing.started_at,
                completed_at=None,
            )

    # Question pool: randomly select N questions if questions_per_attempt is set
    import random
    selected_ids = None
    total_q = quiz.questions_count
    if quiz.questions_per_attempt and quiz.questions_per_attempt < quiz.questions_count:
        all_q_ids = [q.id for q in quiz.questions]
        selected_ids = sorted(random.sample(all_q_ids, quiz.questions_per_attempt))
        total_q = quiz.questions_per_attempt

    attempt = QuizAttempt(
        quiz_id=quiz_id,
        user_id=current_user.id,
        total_questions=total_q,
        selected_question_ids=selected_ids,
    )
    db.add(attempt)
    db.commit()
    db.refresh(attempt)
    return AttemptResponse(
        id=attempt.id,
        quiz_id=attempt.quiz_id,
        score=0,
        total_questions=attempt.total_questions,
        percentage=0.0,
        started_at=attempt.started_at,
        completed_at=None,
    )


@router.post("/{attempt_id}/submit", response_model=AttemptDetail)
def submit_attempt(
    attempt_id: int,
    submission: AttemptSubmit,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    attempt = db.query(QuizAttempt).filter(
        QuizAttempt.id == attempt_id,
        QuizAttempt.user_id == current_user.id,
    ).first()
    if not attempt:
        raise HTTPException(status_code=404, detail="Attempt not found")
    if attempt.completed_at:
        raise HTTPException(status_code=400, detail="Attempt already submitted")

    # Get all questions for this quiz via junction table
    questions = {q.id: q for q in get_quiz_questions(db, attempt.quiz_id)}

    score = 0
    submitted = {ans.question_id: ans.user_answer for ans in submission.answers}

    # Save submitted answers
    for ans in submission.answers:
        question = questions.get(ans.question_id)
        if not question:
            continue
        is_correct = bool(question.correct_answer) and ans.user_answer.strip().lower() == question.correct_answer.strip().lower()
        if is_correct:
            score += 1
        db.add(AttemptAnswer(
            attempt_id=attempt_id,
            question_id=ans.question_id,
            user_answer=ans.user_answer,
            is_correct=is_correct,
        ))

    # Check if review is allowed (course quizzes may disallow)
    quiz = db.query(Quiz).filter(Quiz.id == attempt.quiz_id).first()
    is_course_quiz = quiz and quiz.course_id is not None
    review_allowed = not is_course_quiz or (quiz.allow_review == 1)

    # Build full review — include ALL questions, unanswered marked as incorrect
    answer_details = []
    if review_allowed:
        for q in get_quiz_questions(db, attempt.quiz_id):
            user_answer = submitted.get(q.id, "")
            is_correct = bool(user_answer) and bool(q.correct_answer) and user_answer.strip().lower() == q.correct_answer.strip().lower()
            answer_details.append(AnswerDetail(
                question_id=q.id,
                question_text=q.question_text,
                question_type=q.question_type,
                options=q.options,
                user_answer=user_answer,
                correct_answer=q.correct_answer,
                is_correct=is_correct,
                explanation=q.explanation,
            ))

    attempt.score = score
    attempt.completed_at = datetime.utcnow()
    db.commit()

    # Clear saved progress from Redis when submitted
    try:
        import redis as redis_lib
        from app.config import settings
        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True)
        r.delete(f"quiz_progress:{current_user.id}:{attempt.quiz_id}")
    except Exception:
        logger.warning("Failed to clear quiz progress from Redis", exc_info=True)

    percentage = (score / attempt.total_questions * 100) if attempt.total_questions > 0 else 0

    # Update reminder schedule (skip course quizzes)
    if not is_course_quiz:
        try:
            from app.services.reminder_service import update_reminder_schedule
            update_reminder_schedule(db, current_user.id, attempt.quiz_id, percentage)
        except Exception:
            logger.warning("Failed to update reminder schedule for quiz %d", attempt.quiz_id, exc_info=True)

    return AttemptDetail(
        id=attempt.id,
        quiz_id=attempt.quiz_id,
        score=score,
        total_questions=attempt.total_questions,
        percentage=round(percentage, 1),
        started_at=attempt.started_at,
        completed_at=attempt.completed_at,
        answers=answer_details,
        course_id=quiz.course_id if quiz else None,
        allow_review=review_allowed,
    )


@router.get("/", response_model=list[AttemptResponse])
def list_attempts(
    quiz_id: int | None = None,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    query = db.query(QuizAttempt).filter(
        QuizAttempt.user_id == current_user.id,
        (QuizAttempt.expired == 0) | (QuizAttempt.expired.is_(None)),
    )
    if quiz_id:
        query = query.filter(QuizAttempt.quiz_id == quiz_id)
    attempts = query.order_by(QuizAttempt.started_at.desc()).all()

    return [
        AttemptResponse(
            id=a.id,
            quiz_id=a.quiz_id,
            score=a.score,
            total_questions=a.total_questions,
            percentage=round((a.score / a.total_questions * 100) if a.total_questions > 0 else 0, 1),
            started_at=a.started_at,
            completed_at=a.completed_at,
        )
        for a in attempts
    ]


class ProgressSave(BaseModel):
    quiz_id: int
    attempt_id: int
    answers: dict  # {question_id: answer}
    current_idx: int
    mode: str
    voice: str | None = None
    time_left: int | None = None  # remaining seconds for timed mode (authoritative when suspended=True)
    started_at: str | None = None  # ISO timestamp when quiz started (for timer calculation)
    total_time: int | None = None  # original time limit in seconds
    suspended: bool = False  # when True, timer is paused; time_left is preserved authoritatively


@router.post("/progress")
def save_progress(
    data: ProgressSave,
    request: Request = None,
    current_user: User = Depends(get_current_user),
):
    """Save in-progress quiz answers to Redis (survives logout/browser change).
    Each attempt gets its own saved progress (key includes attempt_id).
    Also refreshes the active session lock to prevent concurrent resume on another device."""
    try:
        import redis as redis_lib, json as _json
        from app.config import settings
        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True)

        # Session lock: identify this device by a session_id header
        session_id = request.headers.get("x-quiz-session", "") if request else ""
        lock_key = f"quiz_active:{current_user.id}:{data.attempt_id}"
        if session_id:
            r.setex(lock_key, 30, session_id)  # 30s TTL, refreshed every save

        key = f"quiz_progress:{current_user.id}:{data.attempt_id}"
        r.setex(key, 7 * 24 * 3600, _json.dumps({  # 7 days
            "quiz_id": data.quiz_id,
            "attempt_id": data.attempt_id,
            "answers": data.answers,
            "current_idx": data.current_idx,
            "mode": data.mode,
            "voice": data.voice,
            "time_left": data.time_left,
            "started_at": data.started_at,
            "total_time": data.total_time,
            "suspended": data.suspended,
        }))
    except Exception:
        logger.warning("Redis unavailable for progress save", exc_info=True)
    return {"saved": True}


@router.get("/progress")
def get_progress(
    quiz_id: int,
    request: Request = None,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Retrieve in-progress quiz answers from Redis.
    Finds the latest incomplete attempt for this quiz, then checks Redis.
    Rejects if another device is actively using this attempt.
    Auto-submits timed quizzes if timer has expired."""
    try:
        import redis as redis_lib, json as _json
        from app.config import settings
        from datetime import datetime, timezone
        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True)

        # Find latest incomplete attempt for this quiz
        attempt = db.query(QuizAttempt).filter(
            QuizAttempt.quiz_id == quiz_id,
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.is_(None),
        ).order_by(QuizAttempt.started_at.desc()).first()

        if not attempt:
            return None

        # Check if another device is actively using this attempt
        session_id = request.headers.get("x-quiz-session", "") if request else ""
        lock_key = f"quiz_active:{current_user.id}:{attempt.id}"
        active_session = r.get(lock_key)
        if active_session and session_id and active_session != session_id:
            raise HTTPException(
                status_code=409,
                detail="This quiz is active on another device. It will become available when the other session ends or times out (~30 seconds)."
            )

        key = f"quiz_progress:{current_user.id}:{attempt.id}"
        data = r.get(key)
        if not data:
            return None

        saved = _json.loads(data)

        # If the quiz was suspended, timer is paused — re-anchor on resume so
        # the held time_left becomes the new total_time starting now.
        if saved.get("suspended"):
            time_left = saved.get("time_left")
            if time_left is not None and time_left > 0:
                saved["started_at"] = datetime.now(timezone.utc).isoformat()
                saved["total_time"] = int(time_left)
                saved["suspended"] = False
                # Persist the re-anchored progress so timer continues correctly
                r.setex(key, 7 * 24 * 3600, _json.dumps(saved))
            return saved

        # Check if timer expired for timed quiz (auto-submit) — only for unsuspended attempts
        total_time = saved.get("total_time")
        started_at_str = saved.get("started_at")
        if total_time is not None and started_at_str:
            try:
                started_at = datetime.fromisoformat(started_at_str.replace('Z', '+00:00'))
                elapsed = (datetime.now(timezone.utc) - started_at).total_seconds()
                if elapsed >= total_time:
                    # Timer expired — auto-submit
                    questions = {q.id: q for q in get_quiz_questions(db, quiz_id)}
                    score = 0
                    submitted_answers = saved.get("answers", {})
                    for qid_str, user_ans in submitted_answers.items():
                        qid = int(qid_str)
                        question = questions.get(qid)
                        if question:
                            correct = (question.correct_answer or "").strip().lower()
                            if (user_ans or "").strip().lower() == correct:
                                score += 1
                            db.add(AttemptAnswer(
                                attempt_id=attempt.id,
                                question_id=qid,
                                user_answer=user_ans,
                                is_correct=(user_ans or "").strip().lower() == correct,
                            ))
                    attempt.score = score
                    attempt.completed_at = datetime.utcnow()
                    attempt.expired = 1  # mark as timer-expired; exclude from history
                    db.commit()
                    r.delete(key)
                    return None  # no progress to resume — already submitted
            except Exception:
                logger.warning("Failed to check quiz timer expiration", exc_info=True)

        return saved
    except Exception:
        logger.warning("Redis unavailable for progress retrieval", exc_info=True)
    return None


@router.delete("/progress/{attempt_id}", status_code=204)
def clear_progress(
    attempt_id: int,
    current_user: User = Depends(get_current_user),
):
    """Clear saved progress when quiz is submitted."""
    try:
        import redis as redis_lib
        from app.config import settings
        r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True)
        r.delete(f"quiz_progress:{current_user.id}:{attempt_id}")
    except Exception:
        logger.warning("Redis unavailable for progress clear", exc_info=True)


@router.delete("/{attempt_id}", status_code=204)
def delete_attempt(
    attempt_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Delete own attempt + answers + reminders for that quiz. Cannot delete course quiz attempts."""
    attempt = db.query(QuizAttempt).filter(
        QuizAttempt.id == attempt_id,
        QuizAttempt.user_id == current_user.id,
    ).first()
    if not attempt:
        raise HTTPException(status_code=404, detail="Attempt not found")

    # Block deletion of course quiz attempts
    quiz = db.query(Quiz).filter(Quiz.id == attempt.quiz_id).first()
    if quiz and quiz.course_id:
        raise HTTPException(status_code=403, detail="Cannot delete course quiz attempts")

    quiz_id = attempt.quiz_id
    # Delete all attempts for this quiz by this user (wipe history)
    from app.models.reminder import ReminderSchedule
    db.query(ReminderSchedule).filter(
        ReminderSchedule.user_id == current_user.id,
        ReminderSchedule.quiz_id == quiz_id,
    ).delete()
    db.delete(attempt)
    db.commit()


@router.get("/{attempt_id}/in-progress")
def get_in_progress_attempt(
    quiz_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Return the latest incomplete attempt for a quiz, or null."""
    attempt = db.query(QuizAttempt).filter(
        QuizAttempt.quiz_id == quiz_id,
        QuizAttempt.user_id == current_user.id,
        QuizAttempt.completed_at.is_(None),
    ).order_by(QuizAttempt.started_at.desc()).first()
    if not attempt:
        return None
    return AttemptResponse(
        id=attempt.id,
        quiz_id=attempt.quiz_id,
        score=0,
        total_questions=attempt.total_questions,
        percentage=0.0,
        started_at=attempt.started_at,
        completed_at=None,
    )


@router.get("/in-progress")
def get_in_progress_attempts(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Return all incomplete (not yet submitted) attempts for this user. Excludes course quizzes."""
    incomplete = (
        db.query(QuizAttempt)
        .join(Quiz, QuizAttempt.quiz_id == Quiz.id)
        .filter(
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.is_(None),
            Quiz.course_id.is_(None),  # exclude course quizzes
        )
        .order_by(QuizAttempt.started_at.desc())
        .all()
    )

    result = []
    for a in incomplete:
        quiz = db.query(Quiz).filter(Quiz.id == a.quiz_id).first()
        result.append({
            "attempt_id": a.id,
            "quiz_id": a.quiz_id,
            "quiz_title": quiz.title if quiz else f"Quiz {a.quiz_id}",
            "total_questions": a.total_questions,
            "started_at": a.started_at.isoformat() if a.started_at else None,
        })
    return result


@router.get("/history")
def get_quiz_history(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Return per-quiz attempt history for the performance line graph (excludes course quizzes and expired attempts)."""
    completed = (
        db.query(QuizAttempt)
        .join(Quiz, QuizAttempt.quiz_id == Quiz.id)
        .filter(
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.isnot(None),
            Quiz.course_id.is_(None),
            (QuizAttempt.expired == 0) | (QuizAttempt.expired.is_(None)),
        )
        .order_by(QuizAttempt.completed_at)
        .all()
    )

    # Group by quiz
    from collections import defaultdict
    by_quiz: dict = defaultdict(list)
    quiz_titles: dict = {}
    for a in completed:
        pct = round((a.score / a.total_questions * 100) if a.total_questions > 0 else 0, 1)
        by_quiz[a.quiz_id].append({
            "attempt_id": a.id,
            "date": a.completed_at.isoformat(),
            "percentage": pct,
            "score": a.score,
            "total": a.total_questions,
        })
        if a.quiz_id not in quiz_titles:
            quiz = db.query(Quiz).filter(Quiz.id == a.quiz_id).first()
            quiz_titles[a.quiz_id] = quiz.title if quiz else f"Quiz {a.quiz_id}"

    return [
        {"quiz_id": qid, "title": quiz_titles[qid], "attempts": attempts}
        for qid, attempts in by_quiz.items()
    ]


@router.get("/stats/dashboard", response_model=DashboardStats)
def get_dashboard_stats(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    total_docs = db.query(PDFDocument).filter(PDFDocument.user_id == current_user.id).count()
    # Count distinct quizzes the user has attempted (excludes course quizzes and expired attempts)
    total_quizzes = (
        db.query(QuizAttempt.quiz_id)
        .join(Quiz, QuizAttempt.quiz_id == Quiz.id)
        .filter(
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.isnot(None),
            Quiz.course_id.is_(None),
            (QuizAttempt.expired == 0) | (QuizAttempt.expired.is_(None)),
        )
        .distinct().count()
    )

    completed_attempts = (
        db.query(QuizAttempt)
        .join(Quiz, QuizAttempt.quiz_id == Quiz.id)
        .filter(
            QuizAttempt.user_id == current_user.id,
            QuizAttempt.completed_at.isnot(None),
            Quiz.course_id.is_(None),
            (QuizAttempt.expired == 0) | (QuizAttempt.expired.is_(None)),
        )
        .all()
    )

    total_attempts = len(completed_attempts)
    avg_score = 0.0
    if completed_attempts:
        scores = [(a.score / a.total_questions * 100) if a.total_questions > 0 else 0 for a in completed_attempts]
        avg_score = round(sum(scores) / len(scores), 1)

    # Per-quiz stats — based on quizzes the user has attempted, not created
    quiz_stats = []
    attempted_quiz_ids = {a.quiz_id for a in completed_attempts}
    quizzes = db.query(Quiz).filter(Quiz.id.in_(attempted_quiz_ids)).all() if attempted_quiz_ids else []
    for quiz in quizzes:
        quiz_attempts = [a for a in completed_attempts if a.quiz_id == quiz.id]
        if quiz_attempts:
            pcts = [(a.score / a.total_questions * 100) if a.total_questions > 0 else 0 for a in quiz_attempts]
            quiz_stats.append(QuizStats(
                quiz_id=quiz.id,
                quiz_title=quiz.title,
                attempts_count=len(quiz_attempts),
                best_score=round(max(pcts), 1),
                latest_score=round(pcts[-1], 1),
                average_score=round(sum(pcts) / len(pcts), 1),
            ))

    return DashboardStats(
        total_documents=total_docs,
        total_quizzes=total_quizzes,
        total_attempts=total_attempts,
        average_score=avg_score,
        quiz_stats=quiz_stats,
    )


@router.get("/{attempt_id}", response_model=AttemptDetail)
def get_attempt(
    attempt_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    attempt = db.query(QuizAttempt).filter(
        QuizAttempt.id == attempt_id,
        QuizAttempt.user_id == current_user.id,
    ).first()
    if not attempt:
        raise HTTPException(status_code=404, detail="Attempt not found")

    quiz = db.query(Quiz).filter(Quiz.id == attempt.quiz_id).first()
    is_course_quiz = quiz and quiz.course_id is not None
    review_allowed = not is_course_quiz or (quiz.allow_review == 1)

    # Show ALL questions in review, not just answered ones
    submitted_map = {ans.question_id: ans for ans in attempt.answers}
    answer_details = []
    if review_allowed:
        for q in get_quiz_questions(db, attempt.quiz_id):
            ans = submitted_map.get(q.id)
            answer_details.append(AnswerDetail(
                question_id=q.id,
                question_text=q.question_text,
                question_type=q.question_type,
                options=q.options,
                user_answer=ans.user_answer if ans else "",
                correct_answer=q.correct_answer,
                is_correct=ans.is_correct if ans else False,
                explanation=q.explanation,
            ))

    percentage = (attempt.score / attempt.total_questions * 100) if attempt.total_questions > 0 else 0
    return AttemptDetail(
        id=attempt.id,
        quiz_id=attempt.quiz_id,
        score=attempt.score,
        total_questions=attempt.total_questions,
        percentage=round(percentage, 1),
        started_at=attempt.started_at,
        completed_at=attempt.completed_at,
        answers=answer_details,
        course_id=quiz.course_id if quiz else None,
        allow_review=review_allowed,
    )
