"""The Canvas 3-step file upload (canvas_api.upload_file) and the staged-image
token rewrite (push_to_canvas._resolve_staged_file_tokens), exercised live.

Both are impossible to genuinely pin with mocks: step 2 of upload_file POSTs
to a Canvas-issued, one-time upload_url that isn't config['course_url'], and
the redirect-or-inline-response branch (step 3) only exists live.

No binary fixture files: this file builds a valid, minimal 1x1 PNG in pure
Python (stdlib struct + zlib) rather than shipping a binary asset.
"""

import os
import re
import struct
import sys
import tempfile
import zlib

import requests

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

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


def _one_pixel_png() -> bytes:
    """Build a valid, minimal 1x1 white PNG (RGB, 8-bit) with stdlib only."""

    def _chunk(tag: bytes, data: bytes) -> bytes:
        payload = tag + data
        return struct.pack(">I", len(data)) + payload + struct.pack(">I", zlib.crc32(payload))

    signature = b"\x89PNG\r\n\x1a\n"
    ihdr = struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0)  # 1x1, 8-bit depth, RGB
    raw_scanline = b"\x00" + b"\xff\xff\xff"  # filter byte (None) + one white pixel
    idat = zlib.compress(raw_scanline)
    return signature + _chunk(b"IHDR", ihdr) + _chunk(b"IDAT", idat) + _chunk(b"IEND", b"")


def _delete_file(config, file_id):
    """Cleanup-only direct API call — no canvas_api.delete_file() exists."""
    try:
        requests.delete(
            f"{config['base_url']}/files/{file_id}",
            headers=config["headers"],
            timeout=30,
        )
    except requests.RequestException as exc:
        print(f"  cleanup: failed to delete file {file_id}: {exc}")


def test_upload_file_three_step_process(live_config, run_id, tmp_path, wait_for):
    folder_id = canvas_api.get_or_create_folder(
        live_config, "course files", f"itest-{run_id}"
    )
    assert folder_id, "get_or_create_folder did not return a folder id"

    src = tmp_path / "itest-upload.html"
    src.write_text("<p>ITEST-UPLOAD test fixture</p>", encoding="utf-8")

    file_id = None
    try:
        file_id = canvas_api.upload_file(
            live_config, str(src), folder_id, content_type="text/html"
        )
        assert file_id, "upload_file did not return a file id"

        # Confirm existence directly (no canvas_api.get_file() wrapper exists).
        def _get_file():
            resp = requests.get(
                f"{live_config['base_url']}/files/{file_id}",
                headers=live_config["headers"],
                timeout=30,
            )
            return resp.json() if resp.status_code == 200 else None

        file_meta = wait_for(_get_file)
        assert file_meta is not None, f"Uploaded file {file_id} not found via GET"
        assert file_meta.get("id") == file_id
    finally:
        if file_id:
            _delete_file(live_config, file_id)


def test_staged_image_token_rewritten_on_push(live_config, page_factory, wait_for, run_id):
    course_id = live_config["course_id"]
    key = f"itest-{run_id}-hero"

    png_path = None
    file_id_holder = {}
    try:
        fd, png_path = tempfile.mkstemp(suffix=".png")
        with os.fdopen(fd, "wb") as f:
            f.write(_one_pixel_png())

        stage_file_result = staging_manager.stage_file(
            course_id, key, "png", png_path,
            meta={"mime": "image/png", "alt": "ITEST hero image"},
        )
        assert stage_file_result["ok"], f"stage_file failed: {stage_file_result}"

        # Create the live page with neutral placeholder content — the
        # asucb-staged-file:// token only ever needs to exist in staging,
        # never live (push_page resolves it before writing).
        slug, _ = page_factory("staged-image", "<p>seed</p>")

        staging_manager.stage(
            course_id, "pages", slug,
            f'<p>ITEST-STAGED-IMAGE</p><img src="asucb-staged-file://{key}" alt="hero">',
        )

        push_result = push_to_canvas.push_page(live_config, slug, course_id)
        assert push_result["ok"], f"push_page failed: {push_result}"

        def _fetch_when_rewritten():
            page = canvas_api.get_page(live_config, slug)
            body = (page or {}).get("body") or ""
            if "asucb-staged-file://" not in body and "/files/" in body:
                return page
            return None

        page_after_push = wait_for(_fetch_when_rewritten)
        assert page_after_push is not None, (
            "Live page body never showed a rewritten /files/ img src"
        )
        live_body = page_after_push.get("body") or ""
        assert "asucb-staged-file://" not in live_body, (
            f"Expected staged-file token to be gone; got: {live_body!r}"
        )
        assert "/preview" in live_body and "/files/" in live_body, (
            f"Expected img src rewritten to a /files/{{id}}/preview URL; got: {live_body!r}"
        )

        # Recover the uploaded file id so it can be cleaned up too.
        match = re.search(r"/files/(\d+)/preview", live_body)
        if match:
            file_id_holder["id"] = int(match.group(1))

        # The staged file itself should be cleared by push_page on success.
        assert staging_manager.get_staged_file(course_id, key) is None, (
            "Expected the staged file to be cleared after a successful push"
        )
    finally:
        if png_path and os.path.exists(png_path):
            os.remove(png_path)
        staging_manager.clear_staged_file(course_id, key)
        if file_id_holder.get("id"):
            _delete_file(live_config, file_id_holder["id"])
