#!/usr/bin/env python3
"""ASUCB Timeline Logger — structured per-course milestone events."""

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

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


VALID_EVENT_TYPES = [
    "audit_completed",
    "accessibility_audit_completed",
    "readiness_check_completed",
    "push_started",
    "push_verified",
    "rollback_completed",
    "mockup_updated",
    "mockup_approved",
    "mockup_promoted",
    "alignment_mapping_approved",
]


def _timeline_path(course_id):
    """Return courses/{courseId}/timeline.jsonl, creating parent dirs."""
    course_dir = os.path.join(get_plugin_root(), "courses", str(course_id))
    os.makedirs(course_dir, exist_ok=True)
    return os.path.join(course_dir, "timeline.jsonl")


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


def _read_jsonl(path):
    """Read JSONL records from a file, skipping malformed lines."""
    if not os.path.exists(path):
        return []

    records = []
    try:
        with open(path, "r", encoding="utf-8") as f:
            for line_num, line in enumerate(f, start=1):
                line = line.strip()
                if not line:
                    continue
                try:
                    records.append(json.loads(line))
                except json.JSONDecodeError as exc:
                    logging.warning(
                        "Failed to parse timeline record at %s:%s: %s",
                        path, line_num, exc,
                    )
    except OSError as exc:
        logging.warning("Failed to read timeline for course %s: %s", path, exc)
        return []
    return records


def append_event(course_id, event_type, payload):
    """Append a structured event to timeline.jsonl.

    Write failures are caught and returned as ok:false.
    """
    if event_type not in VALID_EVENT_TYPES:
        return {"ok": False, "error": f"Invalid event type: {event_type}"}
    if not isinstance(payload, dict):
        return {"ok": False, "error": "Payload must be a JSON object"}

    event = {
        "timestamp": _now_iso(),
        "event_type": event_type,
        "course_id": str(course_id),
        "payload": payload,
    }

    path = _timeline_path(course_id)
    try:
        with open(path, "a", encoding="utf-8") as f:
            f.write(json.dumps(event, ensure_ascii=False) + "\n")
    except OSError as exc:
        logging.warning(
            "Failed to append timeline event for course %s: %s", course_id, exc
        )
        return {"ok": False, "error": str(exc), "event": event}

    return {"ok": True, "path": path, "event": event}


def emit_event(course_id, event_type, payload):
    """Best-effort timeline emission — swallows errors, never interrupts caller.

    Use this from write paths (push, backup, rollback) where timeline logging
    must not crash the operation. For strict emission with ok/error return,
    use append_event directly.
    """
    try:
        result = append_event(course_id, event_type, payload)
        if not result.get("ok"):
            logging.warning(
                "Failed to emit %s event for course %s: %s",
                event_type, course_id, result.get("error", "unknown"),
            )
        return result
    except Exception as exc:
        logging.warning(
            "Failed to emit %s event for course %s: %s",
            event_type, course_id, exc,
        )
        return {"ok": False, "error": str(exc)}


def list_events(course_id):
    """Return all timeline events for a course."""
    return _read_jsonl(_timeline_path(course_id))


def search_events(course_id, event_type):
    """Return timeline events matching an allowed event type."""
    if event_type not in VALID_EVENT_TYPES:
        return []
    return [e for e in list_events(course_id) if e.get("event_type") == event_type]


def main():
    parser = argparse.ArgumentParser(description="ASUCB Timeline Logger")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--append", action="store_true", help="Append an event")
    group.add_argument("--list", action="store_true", help="List events")
    group.add_argument("--search", action="store_true", help="Search events")

    parser.add_argument("--course-id", help="Canvas course ID")
    parser.add_argument("--event-type", choices=VALID_EVENT_TYPES,
                        help="Timeline event type")
    parser.add_argument("--payload", help="JSON object payload string")

    args = parser.parse_args()

    if args.append:
        if not args.course_id or not args.event_type or args.payload is None:
            parser.error("--append requires --course-id, --event-type, and --payload")
        try:
            payload = json.loads(args.payload)
        except json.JSONDecodeError as exc:
            parser.error(f"--payload must be valid JSON: {exc}")
        if not isinstance(payload, dict):
            parser.error("--payload must be a JSON object")
        print(json.dumps(
            append_event(args.course_id, args.event_type, payload),
            indent=2,
        ))

    elif args.list:
        if not args.course_id:
            parser.error("--list requires --course-id")
        print(json.dumps({"ok": True, "events": list_events(args.course_id)}, indent=2))

    elif args.search:
        if not args.course_id or not args.event_type:
            parser.error("--search requires --course-id and --event-type")
        print(json.dumps({
            "ok": True,
            "events": search_events(args.course_id, args.event_type),
        }, indent=2))


if __name__ == "__main__":
    main()
