#!/usr/bin/env python3
"""Audit Bloom's taxonomy alignment across each module arc."""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
PLUGIN_ROOT = SCRIPT_DIR.parent
if str(SCRIPT_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPT_DIR))

from audit_mlo_drift import extract_verb_objects, _strip_html  # noqa: E402
from blooms import BLOOMS_LEVELS  # noqa: E402
from config_loader import load_course_config  # noqa: E402

LEVEL_BY_VALUE = {value: key for key, value in BLOOMS_LEVELS.items()}
UPPER_ORDER = {"Analyze", "Evaluate", "Create"}


def _level_value(level):
    return BLOOMS_LEVELS.get(str(level or ""), 0)


def _max_level(levels):
    values = [_level_value(level) for level in levels if _level_value(level)]
    return LEVEL_BY_VALUE[max(values)] if values else "Unknown"


def _min_level(levels):
    values = [_level_value(level) for level in levels if _level_value(level)]
    return LEVEL_BY_VALUE[min(values)] if values else "Unknown"


def classify_bloom_level(text, explicit=None):
    if explicit in BLOOMS_LEVELS:
        return explicit
    pairs = extract_verb_objects(text)
    for pair in pairs:
        if pair.get("level") in BLOOMS_LEVELS:
            return pair["level"]
    return "Unknown"


def _module_id(module):
    return module.get("id") or module.get("number") or module.get("module_id")


def _module_name(module):
    return module.get("title") or module.get("name") or f"Module {_module_id(module)}"


def _mlo_levels(module):
    levels = []
    for objective in module.get("objectives", []) or []:
        if isinstance(objective, dict):
            levels.append(classify_bloom_level(
                objective.get("text") or objective.get("objective") or "",
                explicit=objective.get("blooms") or objective.get("bloom_level"),
            ))
        else:
            levels.append(classify_bloom_level(str(objective)))
    return [level for level in levels if level in BLOOMS_LEVELS]


def _knowledge_check_items(assessments):
    items = []
    if isinstance(assessments, dict):
        kc = assessments.get("knowledge_check") or assessments.get("quiz")
        if isinstance(kc, dict):
            for idx, question in enumerate(kc.get("questions", []) or [], start=1):
                if isinstance(question, dict):
                    text = question.get("question_text") or question.get("text") or question.get("prompt")
                    name = question.get("name") or f"Question {idx}"
                else:
                    text = str(question)
                    name = f"Question {idx}"
                if _strip_html(text):
                    items.append({"name": name, "text": text, "explicit": None})
            text = kc.get("focus") or kc.get("prompt") or kc.get("description")
            if _strip_html(text):
                items.append({
                    "name": kc.get("title") or "Knowledge Check",
                    "text": text,
                    "explicit": kc.get("blooms") or kc.get("bloom_level"),
                })
    elif isinstance(assessments, list):
        for idx, item in enumerate(assessments, start=1):
            if not isinstance(item, dict):
                continue
            item_type = str(item.get("type") or "").lower()
            if item_type not in ("quiz", "knowledge_check"):
                continue
            for q_idx, question in enumerate(item.get("questions", []) or [], start=1):
                if isinstance(question, dict):
                    text = question.get("question_text") or question.get("text") or question.get("prompt")
                    name = question.get("name") or f"Question {q_idx}"
                else:
                    text = str(question)
                    name = f"Question {q_idx}"
                if _strip_html(text):
                    items.append({"name": name, "text": text, "explicit": None})
            text = item.get("prompt") or item.get("description") or item.get("focus") or item.get("title")
            if _strip_html(text):
                items.append({
                    "name": item.get("title") or f"Knowledge Check {idx}",
                    "text": text,
                    "explicit": item.get("blooms") or item.get("bloom_level"),
                })
    return items


def _artifact_items(assessments):
    items = []
    if isinstance(assessments, dict):
        artifact = assessments.get("artifact") or assessments.get("assignment")
        if isinstance(artifact, dict):
            text = (
                artifact.get("prompt")
                or artifact.get("description")
                or artifact.get("instructions")
                or artifact.get("title")
            )
            if _strip_html(text):
                items.append({
                    "name": artifact.get("title") or "Create an Artifact",
                    "text": text,
                    "explicit": artifact.get("blooms") or artifact.get("bloom_level"),
                })
    elif isinstance(assessments, list):
        for idx, item in enumerate(assessments, start=1):
            if not isinstance(item, dict):
                continue
            item_type = str(item.get("type") or "").lower()
            title = str(item.get("title") or "")
            if item_type not in ("artifact", "assignment") and "artifact" not in title.lower():
                continue
            text = item.get("prompt") or item.get("description") or item.get("instructions") or title
            if _strip_html(text):
                items.append({
                    "name": title or f"Artifact {idx}",
                    "text": text,
                    "explicit": item.get("blooms") or item.get("bloom_level"),
                })
    return items


def _finding(module, item_type, item_name, mlo_level, item_level, issue):
    return {
        "module_id": _module_id(module),
        "item_type": item_type,
        "item_name": item_name,
        "mlo_bloom_level": mlo_level,
        "item_bloom_level": item_level,
        "issue": issue,
        "issue_type": issue,
    }


def audit_bloom_alignment(course_id=None, course_config=None, config_path=None):
    """Return Bloom alignment findings for configured modules."""
    if course_config is None:
        course_config = load_course_config(course_id=course_id, config_path=config_path)

    findings = []
    for module in course_config.get("modules", []) or []:
        if not isinstance(module, dict):
            continue
        mlo_levels = _mlo_levels(module)
        if not mlo_levels:
            continue
        mlo_ceiling = _max_level(mlo_levels)
        mlo_floor = _min_level(mlo_levels)
        assessments = module.get("assessments", {})
        assessed_levels = []

        for item in _knowledge_check_items(assessments):
            item_level = classify_bloom_level(item["text"], explicit=item.get("explicit"))
            if item_level in BLOOMS_LEVELS:
                assessed_levels.append(item_level)
            if _level_value(item_level) > _level_value(mlo_ceiling):
                findings.append(_finding(
                    module,
                    "knowledge_check",
                    item["name"],
                    mlo_ceiling,
                    item_level,
                    "quiz_level_above_mlo_ceiling",
                ))

        for item in _artifact_items(assessments):
            item_level = classify_bloom_level(item["text"], explicit=item.get("explicit"))
            if item_level in BLOOMS_LEVELS:
                assessed_levels.append(item_level)
            if _level_value(item_level) and _level_value(item_level) < _level_value(mlo_floor):
                findings.append(_finding(
                    module,
                    "artifact",
                    item["name"],
                    mlo_floor,
                    item_level,
                    "assignment_level_below_mlo_floor",
                ))

        upper_mlos = [level for level in mlo_levels if level in UPPER_ORDER]
        if upper_mlos:
            required = _max_level(upper_mlos)
            strongest_assessment = _max_level(assessed_levels)
            if _level_value(strongest_assessment) < _level_value(required):
                findings.append(_finding(
                    module,
                    "module",
                    _module_name(module),
                    required,
                    strongest_assessment if strongest_assessment != "Unknown" else "None",
                    "missing_upper_order_assessment",
                ))

    return findings


def main():
    parser = argparse.ArgumentParser(description="Audit Bloom alignment across module arcs")
    parser.add_argument("--course-id", required=True, help="Canvas course ID")
    parser.add_argument("--output", choices=["text", "json"], default="text")
    parser.add_argument("--config", help="Optional course-config.json path")
    args = parser.parse_args()

    findings = audit_bloom_alignment(course_id=args.course_id, config_path=args.config)
    if args.output == "json":
        print(json.dumps(findings, indent=2))
        return
    if not findings:
        print("No Bloom alignment findings.")
        return
    for finding in findings:
        print(
            f"Module {finding['module_id']} {finding['item_name']}: "
            f"{finding['issue']} ({finding['item_bloom_level']} vs {finding['mlo_bloom_level']})"
        )


if __name__ == "__main__":
    main()
