"""Modules, assignments, attempts — learner quiz flow router."""
from datetime import datetime, timezone
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.api.deps import get_current_user, require_roles
from app.core.rbac import Role
from app.db.base import get_db
from app.models import (
    Assignment,
    Attempt,
    AttemptAnswer,
    AttemptStatus,
    CohortMember,
    LearnerProgress,
    ModuleModel,
    ModuleRule,
    Question,
    QuestionOption,
    QuizModel,
    QuizStatus,
    UserModel,
    WeakTopicSnapshot,
)
from app.schemas import (
    AnswerResult,
    AssignmentCreate,
    AssignmentOut,
    AttemptAnswerCreate,
    AttemptComplete,
    AttemptCreate,
    AttemptOut,
    ModuleCreate,
    ModuleOut,
    ModuleRuleOut,
    ModuleRuleUpdate,
)
from app.services.audit import log_audit

router = APIRouter(prefix="/api/v1", tags=["assignments-attempts"])


# ── Modules ─────────────────────────────────────────────
@router.post("/modules", response_model=ModuleOut, status_code=201)
async def create_module(
    body: ModuleCreate,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(require_roles(Role.LD_ADMIN, Role.DEPT_HEAD)),
) -> ModuleOut:
    module = ModuleModel(
        program_id=body.program_id,
        name=body.name,
        description=body.description,
        order_index=body.order_index,
        is_active=body.is_active,
    )
    db.add(module)
    await db.flush()

    # Create default rules
    rules = ModuleRule(module_id=module.id, pass_threshold=70.0, is_mandatory=True)
    db.add(rules)
    await db.commit()
    await db.refresh(module)

    result = await db.execute(
        select(ModuleModel)
        .where(ModuleModel.id == module.id)
        .options(selectinload(ModuleModel.rules))
    )
    module = result.scalar_one()
    return ModuleOut.model_validate(module)


@router.put("/modules/{module_id}/rules", response_model=ModuleRuleOut)
async def update_module_rules(
    module_id: UUID,
    body: ModuleRuleUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(require_roles(Role.LD_ADMIN, Role.DEPT_HEAD)),
) -> ModuleRuleOut:
    result = await db.execute(
        select(ModuleRule).where(ModuleRule.module_id == module_id)
    )
    rules = result.scalar_one_or_none()
    if not rules:
        rules = ModuleRule(module_id=module_id)
        db.add(rules)

    rules.pass_threshold = body.pass_threshold
    rules.is_mandatory = body.is_mandatory
    rules.max_replays = body.max_replays

    await log_audit(db, current_user.id, "module_rules_updated", "module", module_id,
                    details={"pass_threshold": body.pass_threshold, "is_mandatory": body.is_mandatory})
    await db.commit()
    await db.refresh(rules)
    return ModuleRuleOut.model_validate(rules)


# ── Assignments ────────────────────────────────────────
@router.post("/assignments", response_model=AssignmentOut, status_code=201)
async def create_assignment(
    body: AssignmentCreate,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(
        require_roles(Role.LD_ADMIN, Role.DEPT_HEAD, Role.SALES_MANAGER)
    ),
) -> AssignmentOut:
    # Verify quiz is published
    quiz = await db.get(QuizModel, body.quiz_id)
    if not quiz:
        raise HTTPException(status_code=404, detail="Quiz not found")
    if quiz.status != QuizStatus.PUBLISHED:
        raise HTTPException(status_code=409, detail="Quiz must be published before assignment")

    assignment = Assignment(
        quiz_id=body.quiz_id,
        cohort_id=body.cohort_id,
        user_id=body.user_id,
        due_date=body.due_date,
    )
    db.add(assignment)
    await log_audit(db, current_user.id, "assignment_created", "assignment", assignment.id,
                    details={"quiz_id": str(body.quiz_id)})
    await db.commit()
    await db.refresh(assignment)
    return AssignmentOut.model_validate(assignment)


@router.get("/assignments/my", response_model=list[AssignmentOut])
async def get_my_assignments(
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(get_current_user),
) -> list[AssignmentOut]:
    """Get assignments for the current learner (direct or via cohort) with score & progress details."""
    result = await db.execute(
        select(Assignment).where(
            and_(
                Assignment.is_active == True,
                (Assignment.user_id == current_user.id) |
                (Assignment.cohort_id.in_(
                    select(CohortMember.cohort_id).where(CohortMember.user_id == current_user.id)
                ))
            )
        )
    )
    assignments = result.scalars().all()

    out_list = []
    for a in assignments:
        a_out = AssignmentOut.model_validate(a)

        # Get quiz title & module_id
        quiz = await db.get(QuizModel, a.quiz_id)
        if quiz:
            a_out.quiz_title = quiz.title

            # Get learner progress for this module
            prog_res = await db.execute(
                select(LearnerProgress).where(
                    and_(
                        LearnerProgress.learner_id == current_user.id,
                        LearnerProgress.module_id == quiz.module_id,
                    )
                )
            )
            prog = prog_res.scalar_one_or_none()
            if prog:
                a_out.best_score = prog.best_score
                a_out.is_passed = prog.is_passed
                a_out.attempt_count = prog.attempt_count

        out_list.append(a_out)

    return out_list


# ── Attempts ───────────────────────────────────────────
@router.post("/attempts", response_model=AttemptOut, status_code=201)
async def create_attempt(
    body: AttemptCreate,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(get_current_user),
) -> AttemptOut:
    """Start a new quiz attempt. Unlimited replays allowed."""
    assignment = await db.get(Assignment, body.assignment_id)
    if not assignment:
        raise HTTPException(status_code=404, detail="Assignment not found")

    # Get the published quiz version to count questions
    quiz_result = await db.execute(
        select(QuizModel)
        .where(QuizModel.id == assignment.quiz_id)
        .options(selectinload(QuizModel.questions))
    )
    quiz = quiz_result.scalar_one_or_none()
    total_questions = len(quiz.questions) if quiz else 0

    attempt = Attempt(
        assignment_id=body.assignment_id,
        learner_id=current_user.id,
        module_id=body.module_id,
        status=AttemptStatus.IN_PROGRESS,
        total_questions=total_questions,
    )
    db.add(attempt)
    await log_audit(db, current_user.id, "attempt_started", "attempt", attempt.id,
                    details={"assignment_id": str(body.assignment_id)})
    await db.commit()
    await db.refresh(attempt)
    return AttemptOut.model_validate(attempt)


@router.post("/attempts/{attempt_id}/answers", response_model=AnswerResult)
async def submit_answer(
    attempt_id: UUID,
    body: AttemptAnswerCreate,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(get_current_user),
) -> AnswerResult:
    """Submit a single answer and get immediate feedback with explanation."""
    attempt = await db.get(Attempt, attempt_id)
    if not attempt:
        raise HTTPException(status_code=404, detail="Attempt not found")
    if attempt.learner_id != current_user.id:
        raise HTTPException(status_code=403, detail="Not your attempt")
    if attempt.status != AttemptStatus.IN_PROGRESS:
        raise HTTPException(status_code=409, detail="Attempt is not in progress")

    # Get question and selected option
    question_result = await db.execute(
        select(Question)
        .where(Question.id == body.question_id)
        .options(selectinload(Question.options))
    )
    question = question_result.scalar_one_or_none()
    if not question:
        raise HTTPException(status_code=404, detail="Question not found")

    selected = await db.get(QuestionOption, body.selected_option_id)
    if not selected or selected.question_id != body.question_id:
        raise HTTPException(status_code=422, detail="Invalid option for this question")

    is_correct = selected.is_correct

    # Find the correct option for feedback
    correct_option = next((o for o in question.options if o.is_correct), None)

    # Record the answer
    answer = AttemptAnswer(
        attempt_id=attempt_id,
        question_id=body.question_id,
        selected_option_id=body.selected_option_id,
        is_correct=is_correct,
    )
    db.add(answer)
    await db.commit()

    return AnswerResult(
        question_id=body.question_id,
        selected_option_id=body.selected_option_id,
        is_correct=is_correct,
        explanation=question.explanation,
        correct_option_id=correct_option.id if correct_option else None,
    )


from pydantic import BaseModel

class AttemptCompleteReq(BaseModel):
    correct_count: int | None = None
    total_questions: int | None = None


@router.post("/attempts/{attempt_id}/complete", response_model=AttemptComplete)
async def complete_attempt(
    attempt_id: UUID,
    body: AttemptCompleteReq | None = None,
    db: AsyncSession = Depends(get_db),
    current_user: UserModel = Depends(get_current_user),
) -> AttemptComplete:
    """Complete the attempt, compute score, update progress and weak topics."""
    attempt = await db.get(Attempt, attempt_id)
    if not attempt:
        raise HTTPException(status_code=404, detail="Attempt not found")
    if attempt.learner_id != current_user.id:
        raise HTTPException(status_code=403, detail="Not your attempt")

    # Get all answers for this attempt
    answers_result = await db.execute(
        select(AttemptAnswer, Question)
        .join(Question, AttemptAnswer.question_id == Question.id)
        .where(AttemptAnswer.attempt_id == attempt_id)
    )
    rows = answers_result.all()
    total = len(rows)
    correct = sum(1 for ans, _ in rows if ans.is_correct)

    if total == 0 and body and body.total_questions:
        total = body.total_questions
        correct = body.correct_count or 0

    if total == 0 and attempt.total_questions > 0:
        total = attempt.total_questions

    score = (correct / total * 100) if total > 0 else 0.0

    attempt.status = AttemptStatus.COMPLETED
    attempt.score = score
    attempt.correct_count = correct
    attempt.total_questions = total
    attempt.completed_at = datetime.now(timezone.utc)

    # Get module rules for pass threshold
    rules_result = await db.execute(
        select(ModuleRule).where(ModuleRule.module_id == attempt.module_id)
    )
    rules = rules_result.scalar_one_or_none()
    pass_threshold = rules.pass_threshold if rules else 70.0
    passed = score >= pass_threshold

    # Update learner progress
    progress_result = await db.execute(
        select(LearnerProgress).where(
            and_(
                LearnerProgress.learner_id == current_user.id,
                LearnerProgress.module_id == attempt.module_id,
            )
        )
    )
    progress = progress_result.scalar_one_or_none()
    if not progress:
        progress = LearnerProgress(
            learner_id=current_user.id,
            module_id=attempt.module_id,
            best_score=score,
            latest_score=score,
            attempt_count=1,
            is_passed=passed,
        )
        db.add(progress)
    else:
        progress.best_score = max(progress.best_score, score)
        progress.latest_score = score
        progress.attempt_count += 1
        progress.is_passed = progress.is_passed or passed

    # Update weak topic snapshots
    # Group answers by topic_tag
    topic_stats: dict = {}  # topic_tag -> (total, correct)
    for ans, question in rows:
        tag = question.topic_tag
        if tag not in topic_stats:
            topic_stats[tag] = [0, 0]
        topic_stats[tag][0] += 1
        if ans.is_correct:
            topic_stats[tag][1] += 1

    for tag, (t_total, t_correct) in topic_stats.items():
        weakness_ratio = 1.0 - (t_correct / t_total if t_total > 0 else 0)
        snap_result = await db.execute(
            select(WeakTopicSnapshot).where(
                and_(
                    WeakTopicSnapshot.learner_id == current_user.id,
                    WeakTopicSnapshot.module_id == attempt.module_id,
                    WeakTopicSnapshot.topic_tag == tag,
                )
            )
        )
        snap = snap_result.scalar_one_or_none()
        if not snap:
            snap = WeakTopicSnapshot(
                learner_id=current_user.id,
                module_id=attempt.module_id,
                topic_tag=tag,
                total_questions=t_total,
                correct_questions=t_correct,
                weakness_ratio=weakness_ratio,
            )
            db.add(snap)
        else:
            snap.total_questions += t_total
            snap.correct_questions += t_correct
            snap.weakness_ratio = 1.0 - (
                snap.correct_questions / snap.total_questions
                if snap.total_questions > 0 else 0
            )

    await log_audit(db, current_user.id, "attempt_completed", "attempt", attempt_id,
                    details={"score": score, "passed": passed})
    await db.commit()
    await db.refresh(attempt)

    weak_topics = [tag for tag, (t, c) in topic_stats.items()
                   if (c / t if t > 0 else 0) < 0.6]

    return AttemptComplete(
        attempt_id=attempt_id,
        score=score,
        correct_count=correct,
        total_questions=total,
        passed=passed,
        best_score=progress.best_score,
        weak_topics=weak_topics,
    )
