#!/usr/bin/env python3
"""ASUCB Post-Write Verification — confirm Canvas writes succeeded.

Fetches objects back from Canvas after a push and verifies the result.
Returns structured JSON with ok status, Canvas link, and warnings.
"""

import argparse
import json
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))
from canvas_api import (
    get_config, require_course_id,
    get_page, get_assignment, get_quiz, get_quiz_questions,
    get_discussion,
)


def _canvas_link(config, path):
    """Build a full Canvas URL."""
    return f"https://{config['domain']}/courses/{config['course_id']}/{path}"


def verify_page(config, slug):
    """Verify a wiki page exists and has content.

    Returns dict with ok, type, slug, title, published, content_length,
    canvas_url, and warnings.
    """
    page = get_page(config, slug)
    if not page:
        return {
            "ok": False,
            "type": "page",
            "slug": slug,
            "error": f"Page '{slug}' not found on Canvas.",
        }

    body = page.get("body", "") or ""
    warnings = []

    if len(body.strip()) < 10:
        warnings.append("Page body is empty or very short (< 10 chars).")
    if not page.get("published", False):
        warnings.append("Page is not published.")

    return {
        "ok": True,
        "type": "page",
        "slug": slug,
        "title": page.get("title", ""),
        "published": page.get("published", False),
        "content_length": len(body),
        "canvas_url": _canvas_link(config, f"pages/{slug}"),
        "warnings": warnings,
    }


def verify_assignment(config, assignment_id, expected_points=None):
    """Verify an assignment exists and check key fields."""
    asn = get_assignment(config, assignment_id)
    if not asn:
        return {
            "ok": False,
            "type": "assignment",
            "id": assignment_id,
            "error": f"Assignment {assignment_id} not found.",
        }

    desc = asn.get("description", "") or ""
    warnings = []

    if len(desc.strip()) < 10:
        warnings.append("Assignment description is empty or very short.")
    if not asn.get("published", False):
        warnings.append("Assignment is not published.")
    if not asn.get("rubric_settings"):
        warnings.append("No rubric attached.")
    if expected_points is not None:
        actual = asn.get("points_possible", 0)
        if actual != expected_points:
            warnings.append(
                f"Points mismatch: expected {expected_points}, got {actual}.")

    return {
        "ok": True,
        "type": "assignment",
        "id": assignment_id,
        "title": asn.get("name", ""),
        "published": asn.get("published", False),
        "points_possible": asn.get("points_possible", 0),
        "content_length": len(desc),
        "has_rubric": bool(asn.get("rubric_settings")),
        "canvas_url": _canvas_link(config, f"assignments/{assignment_id}"),
        "warnings": warnings,
    }


def verify_quiz(config, quiz_id, expected_points=None, expected_questions=None):
    """Verify a quiz exists and check key fields.

    When expected_points or expected_questions are provided, mismatches
    are treated as hard failures (ok=False), not just warnings. This
    ensures push_quiz does not clear staging on partial writes.
    """
    quiz = get_quiz(config, quiz_id)
    if not quiz:
        return {
            "ok": False,
            "type": "quiz",
            "id": quiz_id,
            "error": f"Quiz {quiz_id} not found.",
        }

    warnings = []
    hard_failures = []

    # quiz.question_count is cached and lags behind question writes; fetch live
    actual_questions = get_quiz_questions(config, quiz_id) or []
    question_count = len(actual_questions)
    if question_count == 0:
        warnings.append("Quiz has no questions.")
    if not quiz.get("published", False):
        warnings.append("Quiz is not published.")

    # When expected values are provided, mismatches are hard failures
    if expected_points is not None:
        actual = quiz.get("points_possible", 0)
        if actual != expected_points:
            hard_failures.append(
                f"Points mismatch: expected {expected_points}, got {actual}.")
    if expected_questions is not None and question_count != expected_questions:
        hard_failures.append(
            f"Question count mismatch: expected {expected_questions}, "
            f"got {question_count}.")

    if hard_failures:
        return {
            "ok": False,
            "type": "quiz",
            "id": quiz_id,
            "title": quiz.get("title", ""),
            "error": "; ".join(hard_failures),
            "published": quiz.get("published", False),
            "points_possible": quiz.get("points_possible", 0),
            "question_count": question_count,
            "canvas_url": _canvas_link(config, f"quizzes/{quiz_id}"),
            "warnings": warnings,
        }

    return {
        "ok": True,
        "type": "quiz",
        "id": quiz_id,
        "title": quiz.get("title", ""),
        "published": quiz.get("published", False),
        "points_possible": quiz.get("points_possible", 0),
        "question_count": question_count,
        "allowed_attempts": quiz.get("allowed_attempts", 1),
        "time_limit": quiz.get("time_limit"),
        "canvas_url": _canvas_link(config, f"quizzes/{quiz_id}"),
        "warnings": warnings,
    }


def verify_discussion(config, topic_id):
    """Verify a discussion topic exists and check key fields."""
    disc = get_discussion(config, topic_id)
    if not disc:
        return {
            "ok": False,
            "type": "discussion",
            "id": topic_id,
            "error": f"Discussion {topic_id} not found.",
        }

    message = disc.get("message", "") or ""
    warnings = []

    if len(message.strip()) < 10:
        warnings.append("Discussion prompt (message) is empty or very short.")
    if not disc.get("published", False):
        warnings.append("Discussion is not published.")

    result = {
        "ok": True,
        "type": "discussion",
        "id": topic_id,
        "title": disc.get("title", ""),
        "published": disc.get("published", False),
        "content_length": len(message),
        "require_initial_post": disc.get("require_initial_post", False),
        "canvas_url": _canvas_link(config, f"discussion_topics/{topic_id}"),
        "warnings": warnings,
    }

    # Include assignment info if graded
    asn_id = disc.get("assignment_id")
    if asn_id:
        result["assignment_id"] = asn_id
        result["points_possible"] = disc.get("assignment", {}).get(
            "points_possible", None)

    return result


# ============================================================
# CLI
# ============================================================
def main():
    parser = argparse.ArgumentParser(description="ASUCB Post-Write Verification")
    parser.add_argument("--type", required=True,
                        choices=["page", "assignment", "quiz", "discussion"])
    parser.add_argument("--slug", help="Page slug (for page type)")
    parser.add_argument("--id", help="Object ID (for assignment/quiz/discussion)")
    parser.add_argument("--expected-points", type=float, help="Expected points total")
    parser.add_argument("--expected-questions", type=int, help="Expected question count")
    parser.add_argument("--course-id", help="Course ID override")

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

    if args.type == "page":
        if not args.slug:
            parser.error("--type page requires --slug")
        result = verify_page(config, args.slug)
    elif args.type == "assignment":
        if not args.id:
            parser.error("--type assignment requires --id")
        result = verify_assignment(config, args.id, args.expected_points)
    elif args.type == "quiz":
        if not args.id:
            parser.error("--type quiz requires --id")
        result = verify_quiz(config, args.id, args.expected_points,
                             args.expected_questions)
    elif args.type == "discussion":
        if not args.id:
            parser.error("--type discussion requires --id")
        result = verify_discussion(config, args.id)

    print(json.dumps(result, indent=2))

    if not result.get("ok"):
        sys.exit(1)
    if result.get("warnings"):
        print(f"\n⚠ {len(result['warnings'])} warning(s):", file=sys.stderr)
        for w in result["warnings"]:
            print(f"  - {w}", file=sys.stderr)


if __name__ == "__main__":
    main()
