"""End to end: stage -> push_to_canvas.push_page -> verify -> restore_backup.

This is the money path — the exact sequence every Canvas page write in the
plugin goes through (§Write Safety Guard in CLAUDE.md), exercised for real
against the sandbox instead of mocked.
"""

import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "scripts"))

import backup_manager  # noqa: E402
import canvas_api  # noqa: E402
import push_to_canvas  # noqa: E402
import staging_manager  # noqa: E402


def _fetch_when_body_contains(config, slug, sentinel):
    """Return the page dict once its live body contains `sentinel`, else None.

    Plain get_page() always returns a truthy dict, even with a stale body,
    so polling on object existence alone wouldn't catch eventual-consistency
    lag — this checks the actual content each poll.
    """
    page = canvas_api.get_page(config, slug)
    if page and sentinel in (page.get("body") or ""):
        return page
    return None


def test_stage_push_verify_restore_cycle(live_config, page_factory, wait_for, run_id):
    sentinel_a = f"pushcycle-{run_id}-bodyA"
    sentinel_b = f"pushcycle-{run_id}-bodyB"
    body_a = f"<p>ITEST-PUSHCYCLE: {sentinel_a}</p>"
    body_b = f"<p>ITEST-PUSHCYCLE: {sentinel_b}</p>"

    # 1. Create a live page with body A.
    slug, page = page_factory("push-cycle", body_a)
    assert sentinel_a in (page.get("body") or ""), "create_page did not save body A"

    course_id = live_config["course_id"]

    # 2. Stage body B for the same slug.
    stage_result = staging_manager.stage(course_id, "pages", slug, body_b)
    assert stage_result["ok"], f"stage() failed: {stage_result}"

    # 3. Push the staged content.
    push_result = push_to_canvas.push_page(live_config, slug, course_id)
    assert push_result["ok"], f"push_page failed: {push_result}"

    # 4. Field-targeted verification (MUST-2: never whole-body equality).
    # push_page() already verifies live content before returning ok, but we
    # poll here too rather than assume the following read is consistent.
    page_after_push = wait_for(
        lambda: _fetch_when_body_contains(live_config, slug, sentinel_b)
    )
    assert page_after_push is not None, (
        f"Live body never showed sentinel {sentinel_b!r} after push"
    )
    live_body = page_after_push.get("body") or ""
    assert sentinel_b in live_body, f"Expected body B live; got: {live_body!r}"
    assert sentinel_a not in live_body, f"Expected body A gone; got: {live_body!r}"

    # 5. Staging cleared; a pre-push backup of body A exists.
    assert staging_manager.get_staged(course_id, "pages", slug) is None, (
        "Expected staging to be cleared after a successful push"
    )
    backups = backup_manager.list_backups(course_id, "pages", slug)
    assert backups, "Expected a pre-push backup of body A to exist"
    backup_content = backup_manager.get_backup_content(
        course_id, "pages", slug, backups[0]["timestamp"]
    )
    assert sentinel_a in (backup_content or ""), (
        f"Expected the backup to hold body A; got: {backup_content!r}"
    )

    # 6. Restore the backup and confirm body A is live again.
    restore_result = backup_manager.restore_backup(live_config, course_id, "pages", slug)
    assert restore_result.get("ok"), f"restore_backup failed: {restore_result}"

    page_after_restore = wait_for(
        lambda: _fetch_when_body_contains(live_config, slug, sentinel_a)
    )
    assert page_after_restore is not None, (
        f"Live body never showed sentinel {sentinel_a!r} after restore"
    )
    restored_body = page_after_restore.get("body") or ""
    assert sentinel_a in restored_body, (
        f"Expected body A restored live; got: {restored_body!r}"
    )
