#!/usr/bin/env python3
"""ASUCB Build Progress Tracker — per-course, module-by-module progress.

Tracks which pages/assessments have been built for each module so Claude
can resume builds across sessions and show the ID exactly where they left off.

Storage: courses/{courseId}/build-progress.json
"""

import argparse
import json
import os
import re
import sys
from datetime import datetime, timezone

sys.path.insert(0, os.path.dirname(__file__))
from canvas_api import get_plugin_root


# Canonical page types in a Gold Standard module
PAGE_TYPES = [
    "overview",
    "prepare-to-learn",
    "lesson",
    "knowledge-check",
    "guided-practice",
    "create-artifact",
    "conclusion",
]

VALID_STATUSES = ["pending", "in-progress", "complete"]


def _progress_path(course_id):
    """Return path to build-progress.json, creating parent dir if needed."""
    root = get_plugin_root()
    dirpath = os.path.join(root, "courses", str(course_id))
    os.makedirs(dirpath, exist_ok=True)
    return os.path.join(dirpath, "build-progress.json")


def _now_iso():
    """Return current UTC time as ISO 8601 string."""
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _empty_skeleton():
    """Return an empty progress structure."""
    return {
        "lastUpdated": _now_iso(),
        "phase": "",
        "modules": {},
        "history": [],
    }


def _empty_pages():
    """Return a fresh pages dict with all types set to pending."""
    return {
        pt: {"status": "pending", "updatedAt": "", "note": "", "slug": ""}
        for pt in PAGE_TYPES
    }


def _compute_module_status(pages):
    """Derive module status from individual page statuses."""
    statuses = [p.get("status", "pending") for p in pages.values()]
    if all(s == "complete" for s in statuses):
        return "complete"
    if any(s in ("in-progress", "complete") for s in statuses):
        return "in-progress"
    return "pending"


# ============================================================
# Core API
# ============================================================

def load_progress(course_id):
    """Load build-progress.json or return empty skeleton."""
    path = _progress_path(course_id)
    if not os.path.exists(path):
        return _empty_skeleton()
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError):
        return _empty_skeleton()


def save_progress(course_id, progress):
    """Write build-progress.json with updated lastUpdated timestamp."""
    progress["lastUpdated"] = _now_iso()
    path = _progress_path(course_id)
    with open(path, "w", encoding="utf-8") as f:
        json.dump(progress, f, indent=2, ensure_ascii=False)
    return {"ok": True, "path": path}


def update_progress(course_id, module_num, page_type, status, note="",
                    slug=""):
    """Update a single page's status. Auto-calculates module status.

    Args:
        course_id: Canvas course ID
        module_num: Module number (int or str)
        page_type: One of PAGE_TYPES
        status: One of: pending, in-progress, complete
        note: Optional note (e.g., "needs images")
        slug: Optional actual Canvas page slug (stored for drift detection).
              If not provided, drift check falls back to canonical patterns.

    Returns:
        dict with ok, module_status, page_status
    """
    module_num = str(module_num)

    if page_type not in PAGE_TYPES:
        return {"ok": False, "error": f"Invalid page type: {page_type}. "
                f"Must be one of: {PAGE_TYPES}"}
    if status not in VALID_STATUSES:
        return {"ok": False, "error": f"Invalid status: {status}. "
                f"Must be one of: {VALID_STATUSES}"}

    progress = load_progress(course_id)

    # Ensure module entry exists
    if module_num not in progress["modules"]:
        progress["modules"][module_num] = {
            "title": "",
            "status": "pending",
            "pages": _empty_pages(),
        }

    module = progress["modules"][module_num]

    # Ensure pages dict has all types
    if "pages" not in module:
        module["pages"] = _empty_pages()
    for pt in PAGE_TYPES:
        if pt not in module["pages"]:
            module["pages"][pt] = {"status": "pending", "updatedAt": "",
                                   "note": "", "slug": ""}

    # Update the page
    module["pages"][page_type]["status"] = status
    module["pages"][page_type]["updatedAt"] = _now_iso()
    if note:
        module["pages"][page_type]["note"] = note
    if slug:
        module["pages"][page_type]["slug"] = slug

    # Recalculate module status
    module["status"] = _compute_module_status(module["pages"])

    save_progress(course_id, progress)

    return {
        "ok": True,
        "module_num": module_num,
        "page_type": page_type,
        "page_status": status,
        "module_status": module["status"],
    }


def set_module_title(course_id, module_num, title):
    """Set or update a module's title in the progress file."""
    module_num = str(module_num)
    progress = load_progress(course_id)

    if module_num not in progress["modules"]:
        progress["modules"][module_num] = {
            "title": title,
            "status": "pending",
            "pages": _empty_pages(),
        }
    else:
        progress["modules"][module_num]["title"] = title

    save_progress(course_id, progress)
    return {"ok": True, "module_num": module_num, "title": title}


def set_phase(course_id, phase):
    """Set the current build phase."""
    progress = load_progress(course_id)
    progress["phase"] = phase
    save_progress(course_id, progress)
    return {"ok": True, "phase": phase}


def add_history_entry(course_id, action, scope, reason=""):
    """Append to the history array. Auto-timestamps."""
    progress = load_progress(course_id)
    entry = {
        "date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
        "action": action,
        "scope": scope,
    }
    if reason:
        entry["reason"] = reason
    progress["history"].append(entry)
    save_progress(course_id, progress)
    return {"ok": True, "entry": entry}


def get_summary(course_id):
    """Return a compact text summary for dashboard display.

    Examples:
        'Module 3 in progress (lesson needs images)'
        'Build complete — 5 modules'
        'Not started'
    """
    progress = load_progress(course_id)
    modules = progress.get("modules", {})

    if not modules:
        return "Not started"

    total = len(modules)
    complete = sum(1 for m in modules.values() if m.get("status") == "complete")

    if complete == total:
        return f"Build complete — {total} modules"

    # Find the first in-progress module
    for num in sorted(modules.keys(), key=lambda x: int(x) if x.isdigit() else 0):
        mod = modules[num]
        if mod.get("status") == "in-progress":
            # Prioritize in-progress pages (actively being worked on) over pending
            pages = mod.get("pages", {})
            in_progress = []
            pending = []
            for pt in PAGE_TYPES:
                info = pages.get(pt, {})
                if info.get("status") == "in-progress":
                    label = pt.replace("-", " ")
                    note = info.get("note", "")
                    in_progress.append(
                        f"{label}{f' — {note}' if note else ''}")
                elif info.get("status") == "pending":
                    pending.append(pt.replace("-", " "))

            # Show in-progress items first, then first pending
            highlight = (in_progress or pending)
            if highlight:
                return f"Module {num} in progress ({highlight[0]})"
            return f"Module {num} in progress"

    # Fallback
    return f"{complete}/{total} modules complete"


def check_drift(course_id, config):
    """Compare build-progress.json against live Canvas page timestamps.

    For each 'complete' page, fetches the Canvas page and compares
    its updated_at against the local updatedAt.

    Args:
        course_id: Canvas course ID
        config: Canvas API config dict (from canvas_api.get_config())

    Returns:
        list of discrepancy dicts
    """
    import canvas_api

    progress = load_progress(course_id)
    modules = progress.get("modules", {})
    discrepancies = []

    for mod_num, mod in modules.items():
        pages = mod.get("pages", {})
        for page_type, info in pages.items():
            if info.get("status") != "complete":
                continue
            local_updated = info.get("updatedAt", "")
            if not local_updated:
                continue

            # Use stored slug if available, otherwise try canonical patterns
            stored_slug = info.get("slug", "")
            slugs_to_try = []
            if stored_slug:
                slugs_to_try.append(stored_slug)
            slugs_to_try.append(f"m{mod_num}-{page_type}")
            slugs_to_try.append(f"module-{mod_num}-{page_type}")

            page = None
            for slug in slugs_to_try:
                page = canvas_api.get_page(config, slug)
                if page:
                    break

            if not page:
                continue

            canvas_updated = page.get("updated_at", "")
            if not canvas_updated:
                continue

            # Compare: if Canvas was updated after our last tracked push,
            # someone edited it outside the plugin
            try:
                canvas_dt = datetime.fromisoformat(
                    canvas_updated.replace("Z", "+00:00"))
                local_dt = datetime.fromisoformat(
                    local_updated.replace("Z", "+00:00"))
                if canvas_dt > local_dt:
                    discrepancies.append({
                        "module": int(mod_num) if mod_num.isdigit() else mod_num,
                        "page": page_type,
                        "local_updated": local_updated,
                        "canvas_updated": canvas_updated,
                        "issue": "modified on Canvas after last tracked push",
                    })
            except (ValueError, TypeError):
                continue

    return discrepancies


# ============================================================
# Slug parser — best-effort fallback for push_to_canvas
# ============================================================

# Patterns: m1-overview, m3-lesson-topic-name, module-2-knowledge-check
_SLUG_RE = re.compile(
    r"^(?:m|module[- ]?)(\d+)[- ]("
    + "|".join(re.escape(pt) for pt in PAGE_TYPES)
    + r")(?:[- ].*)?$",
    re.IGNORECASE,
)


def try_parse_slug(slug):
    """Best-effort parse of module number and page type from a page slug.

    Returns (module_num: str, page_type: str) or (None, None).
    """
    match = _SLUG_RE.match(slug)
    if match:
        return match.group(1), match.group(2).lower()
    return None, None


# ============================================================
# CLI
# ============================================================

def main():
    parser = argparse.ArgumentParser(description="ASUCB Build Progress Tracker")
    parser.add_argument("--course-id", required=True, help="Canvas course ID")

    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--update", nargs=3,
                       metavar=("MODULE_NUM", "PAGE_TYPE", "STATUS"),
                       help="Update page status: MODULE_NUM PAGE_TYPE STATUS")
    group.add_argument("--summary", action="store_true",
                       help="Show compact progress summary")
    group.add_argument("--show", action="store_true",
                       help="Show full progress JSON")
    group.add_argument("--history", nargs=2,
                       metavar=("ACTION", "SCOPE"),
                       help="Add history entry: ACTION SCOPE")
    group.add_argument("--set-title", nargs=2,
                       metavar=("MODULE_NUM", "TITLE"),
                       help="Set module title")
    group.add_argument("--set-phase", metavar="PHASE",
                       help="Set build phase")
    group.add_argument("--check-drift", action="store_true",
                       help="Check for Canvas modifications since last push")

    parser.add_argument("--note", default="", help="Note for --update")
    parser.add_argument("--slug", default="",
                        help="Actual Canvas page slug for --update "
                        "(stored for drift detection)")
    parser.add_argument("--reason", default="", help="Reason for --history")

    args = parser.parse_args()

    if args.update:
        mod_num, page_type, status = args.update
        result = update_progress(args.course_id, mod_num, page_type,
                                 status, args.note, slug=args.slug)
        print(json.dumps(result, indent=2))

    elif args.summary:
        print(get_summary(args.course_id))

    elif args.show:
        progress = load_progress(args.course_id)
        print(json.dumps(progress, indent=2))

    elif args.history:
        action, scope = args.history
        result = add_history_entry(args.course_id, action, scope, args.reason)
        print(json.dumps(result, indent=2))

    elif args.set_title:
        mod_num, title = args.set_title
        result = set_module_title(args.course_id, mod_num, title)
        print(json.dumps(result, indent=2))

    elif args.set_phase:
        result = set_phase(args.course_id, args.set_phase)
        print(json.dumps(result, indent=2))

    elif args.check_drift:
        import canvas_api
        config = canvas_api.get_config(course_id=args.course_id)
        drifts = check_drift(args.course_id, config)
        if drifts:
            print(json.dumps({"ok": True, "drift_detected": True,
                               "discrepancies": drifts}, indent=2))
        else:
            print(json.dumps({"ok": True, "drift_detected": False}, indent=2))


if __name__ == "__main__":
    main()
