#!/usr/bin/env python3
"""ASUCB Course Navigator — browse course structure with caching.

Fetches the full module → items tree from Canvas, caches it for 5 minutes,
and provides search/filter capabilities.

Cache location: courses/{courseId}/staging/.course-tree.json
"""

import argparse
import json
import os
import re
import sys
import time

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

CACHE_TTL = 300  # 5 minutes


# ============================================================
# ITEM TYPE ICONS
# ============================================================
ICONS = {
    "Page": "[P]",
    "Assignment": "[A]",
    "Quiz": "[Q]",
    "Discussion": "[D]",
    "File": "[F]",
    "ExternalTool": "[T]",
    "ExternalUrl": "[U]",
    "SubHeader": "---",
}


def _cache_path(course_id):
    """Return the cache file path for a course tree."""
    root = canvas_api.get_plugin_root()
    cache_dir = os.path.join(root, "courses", str(course_id), "staging")
    os.makedirs(cache_dir, exist_ok=True)
    return os.path.join(cache_dir, ".course-tree.json")


def _load_cache(course_id):
    """Load cached tree if fresh (< CACHE_TTL seconds old)."""
    path = _cache_path(course_id)
    if not os.path.exists(path):
        return None
    try:
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        cached_at = data.get("cached_at", 0)
        if time.time() - cached_at < CACHE_TTL:
            return data.get("tree", [])
    except (json.JSONDecodeError, OSError):
        pass
    return None


def _save_cache(course_id, tree):
    """Save tree to cache file."""
    path = _cache_path(course_id)
    with open(path, "w", encoding="utf-8") as f:
        json.dump({"cached_at": time.time(), "tree": tree}, f, indent=2)


def fetch_course_tree(config):
    """Fetch full course tree from Canvas API (modules with items)."""
    modules = canvas_api.get_modules(config, include_items=True)
    tree = []
    for mod in modules:
        module = {
            "id": mod["id"],
            "name": mod.get("name", ""),
            "position": mod.get("position", 0),
            "published": mod.get("published", False),
            "items_count": mod.get("items_count", 0),
            "items": [],
        }
        for item in mod.get("items", []):
            module["items"].append({
                "id": item.get("id"),
                "title": item.get("title", ""),
                "type": item.get("type", ""),
                "indent": item.get("indent", 0),
                "published": item.get("published", False),
                "page_url": item.get("page_url", ""),
                "content_id": item.get("content_id"),
                "html_url": item.get("html_url", ""),
                "external_url": item.get("external_url", ""),
            })
        tree.append(module)
    return tree


def get_tree(config, force_refresh=False):
    """Get course tree, using cache when available."""
    course_id = config["course_id"]
    if not force_refresh:
        cached = _load_cache(course_id)
        if cached is not None:
            return cached
    tree = fetch_course_tree(config)
    _save_cache(course_id, tree)
    return tree


def print_tree(tree):
    """Format tree as human-readable text."""
    lines = []
    for mod in tree:
        pub = "+" if mod["published"] else "-"
        lines.append(f"{pub} {mod['name']}")
        for item in mod["items"]:
            indent = "    " + ("  " * item.get("indent", 0))
            icon = ICONS.get(item["type"], "[?]")
            if item["type"] == "SubHeader":
                lines.append(f"{indent}--- {item['title']}")
            else:
                slug = f" [{item['page_url']}]" if item.get("page_url") else ""
                lines.append(f"{indent}{icon} {item['title']}{slug}")
    lines.append("")
    lines.append("[P] Page  [A] Assignment  [Q] Quiz  [D] Discussion")
    lines.append("[F] File  [T] External Tool  [U] External URL  --- Text Header")
    lines.append("+ Published  - Unpublished")
    return "\n".join(lines)


def find_items(tree, query):
    """Search tree by module number, item type, slug, or title substring."""
    query_lower = query.lower().strip()
    results = []

    # Try module number match
    mod_match = re.search(r"module\s*(\d+)", query_lower)
    mod_num = int(mod_match.group(1)) if mod_match else None

    # Try type filter
    type_map = {
        "page": "Page", "pages": "Page",
        "assignment": "Assignment", "assignments": "Assignment",
        "quiz": "Quiz", "quizzes": "Quiz",
        "discussion": "Discussion", "discussions": "Discussion",
        "file": "File", "files": "File",
    }
    type_filter = None
    for keyword, canvas_type in type_map.items():
        if keyword in query_lower:
            type_filter = canvas_type
            break

    for mod in tree:
        # Extract module number from name
        m = re.search(r"module\s*(\d+)", mod["name"], re.IGNORECASE)
        this_mod_num = int(m.group(1)) if m else None

        if mod_num is not None and this_mod_num != mod_num:
            continue

        for item in mod["items"]:
            if type_filter and item["type"] != type_filter:
                continue
            # Slug match
            if item.get("page_url") and query_lower in item["page_url"].lower():
                results.append({"module": mod["name"], **item})
                continue
            # Title substring match
            if query_lower in item["title"].lower():
                results.append({"module": mod["name"], **item})
                continue
            # If only filtering by module + type, include all matches
            if mod_num is not None and type_filter is None:
                results.append({"module": mod["name"], **item})
            elif mod_num is not None and type_filter and item["type"] == type_filter:
                results.append({"module": mod["name"], **item})

    return results


# ============================================================
# CLI
# ============================================================
def main():
    parser = argparse.ArgumentParser(description="ASUCB Course Navigator")
    parser.add_argument("--course-id", help="Course ID override")
    parser.add_argument("--tree", action="store_true", help="Display full tree")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    parser.add_argument("--refresh", action="store_true", help="Force refresh cache")
    parser.add_argument("--find", help="Search query")

    args = parser.parse_args()
    config = canvas_api.get_config(course_id=args.course_id)
    canvas_api.require_course_id(config)

    tree = get_tree(config, force_refresh=args.refresh)

    if args.find:
        results = find_items(tree, args.find)
        if args.json:
            print(json.dumps(results, indent=2))
        else:
            for r in results:
                icon = ICONS.get(r["type"], "[?]")
                slug = f" [{r.get('page_url', '')}]" if r.get("page_url") else ""
                print(f"  {r['module']} → {icon} {r['title']}{slug}")
            if not results:
                print("  No items found.")
    elif args.json:
        print(json.dumps(tree, indent=2))
    else:
        print(print_tree(tree))


if __name__ == "__main__":
    main()
