"""Bounded teaching controller; run only inside a disposable, restricted VM.

This is failure handling, NOT a security boundary against a hostile process.
The owner must review and independently test the resulting diff outside this VM.
"""

import hashlib
import os
import signal
import subprocess
import sys
from pathlib import Path


def run(command, output, seconds, prompt=None):
    with output.open("w") as log:
        process = subprocess.Popen(
            command,
            stdin=subprocess.PIPE if prompt is not None else subprocess.DEVNULL,
            stdout=log,
            stderr=subprocess.STDOUT,
            start_new_session=True,
            text=True,
        )
        try:
            process.communicate(prompt, timeout=seconds)
        except (subprocess.TimeoutExpired, KeyboardInterrupt):
            os.killpg(process.pid, signal.SIGTERM)
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                os.killpg(process.pid, signal.SIGKILL)
                process.wait()
            raise
    return process.returncode


def digest(paths):
    return {
        path: hashlib.sha256(Path(path).read_bytes()).hexdigest()
        if Path(path).is_file() and not Path(path).is_symlink()
        else None
        for path in paths
    }


def main():
    if os.environ.get("LAB_DISPOSABLE_VM") != "yes":
        raise SystemExit(
            "Complete the VM isolation gate; this marker is not isolation."
        )
    model = os.environ.get("LAB_MODEL")
    if not model:
        raise SystemExit("Set LAB_MODEL to the exact model ID you have access to.")
    if not os.environ.get("ANTHROPIC_API_KEY"):
        raise SystemExit("Inject a scoped API key for Claude --bare; never commit it.")
    if not Path("tests/test_completion.py").is_file():
        raise SystemExit("Enable the acceptance tests in Chapter 06 first.")
    if subprocess.check_output(["git", "status", "--porcelain"]).strip():
        raise SystemExit("Start from the committed, reviewed exercise baseline.")
    paths = subprocess.check_output(["git", "ls-files", "-z"]).decode().split("\0")
    protected = [
        p for p in paths if p and p not in {"taskboard.py", "IMPLEMENTATION_PLAN.md"}
    ]
    original = digest(protected)
    prompt = Path("PROMPT.md").read_text()
    Path("logs").mkdir(exist_ok=True)
    command = [
        "claude",
        "--bare",
        "-p",
        "--model",
        model,
        "--max-turns",
        "12",
        "--max-budget-usd",
        "1",
        "--allowedTools",
        "Read,Edit,Write,Bash",
    ]
    previous = digest(["taskboard.py"])
    for iteration in range(1, 4):
        result = run(command, Path(f"logs/agent-{iteration}.txt"), 300, prompt)
        if digest(protected) != original:
            raise SystemExit("Protected files changed; stop for owner review.")
        # An agent exit code alone cannot establish acceptance.
        valid = run(["devenv", "test"], Path("logs/validation.txt"), 180)
        if result == 0 and valid == 0:
            print(
                "Checks passed. Owner must review the diff and rerun in a fresh clone."
            )
            return
        current = digest(["taskboard.py"])
        if current == previous:
            raise SystemExit(
                "No source progress; inspect logs instead of buying another run."
            )
        previous = current
    raise SystemExit("Three attempts exhausted; inspect logs and revise the task.")


if __name__ == "__main__":
    try:
        main()
    except (subprocess.TimeoutExpired, KeyboardInterrupt):
        sys.exit(
            "Stopped: timeout or interrupt. Inspect VM processes and logs before retrying."
        )
