#!/usr/bin/env python3
"""
clone_runner — drives a clone job with OpenHands (0.62.0) + self-hosted Qwen via vLLM.

The agent does EVERYTHING (rip, strip tracking, brand/CTA/text/media changes, QA) by reading
the composed memory layers, written into the job's `.openhands/microagents/repo.md` — a
no-frontmatter repo microagent is ALWAYS loaded into the OpenHands agent's context.

OpenHands 0.62.0 headless entry:
    python -m openhands.core.main -t "<task>" -d <workdir> -i <max_iters> --config-file <cfg>
(auto-continues without stdin by default). LLM endpoint comes from the [llm] section of the
config file. Nothing here calls a third-party API: the LLM is the local vLLM endpoint.
"""

import os
import subprocess
from pathlib import Path

# ── OpenHands harness (0.x line: invoked as a python module, not a bare binary) ──
OPENHANDS_PYTHON = os.environ.get("OPENHANDS_PYTHON", "/opt/openhands/venv/bin/python")
OPENHANDS_CONFIG = os.environ.get(
    "OPENHANDS_CONFIG",
    str(Path(__file__).resolve().parent / "infra" / "openhands-config.toml"),
)
MAX_ITER = os.environ.get("OPENHANDS_MAX_ITERATIONS", "300")

# ── Memory layers ─────────────────────────────────────────────────────────────
MEMORY_DIR   = Path(os.environ.get("MEMORY_DIR", "/srv/site-clones/memory"))
DEFAULT_TEAM = os.environ.get("DEFAULT_TEAM", "adsshabang")


def compose_instructions(job_dir: Path, user_id: str = "") -> str:
    """Stack the memory layers: master → team → member → job."""
    parts = []
    for path in (
        MEMORY_DIR / "master.md",
        MEMORY_DIR / "teams" / DEFAULT_TEAM / "team.md",
        (MEMORY_DIR / "users" / user_id / "member.md") if user_id and user_id != "unknown" else None,
        job_dir / "job.md",
    ):
        if path and path.exists():
            parts.append(path.read_text(encoding="utf-8"))
    return "\n\n---\n\n".join(parts)


def write_repo_microagent(job_dir: Path, instructions: str) -> Path:
    """A repo.md with NO YAML frontmatter is always loaded into the agent's context."""
    d = job_dir / ".openhands" / "microagents"
    d.mkdir(parents=True, exist_ok=True)
    p = d / "repo.md"
    p.write_text(instructions, encoding="utf-8")
    return p


def run(job_dir: Path, task: str, on_log=None, timeout: int = 1800) -> tuple[int, str]:
    """
    Run OpenHands headless with `job_dir` as the working directory. Streams stdout to
    on_log(line). Returns (returncode, full_output).
    """
    job_dir = Path(job_dir)

    cmd = [
        OPENHANDS_PYTHON, "-m", "openhands.core.main",
        "-t", task,
        "-d", str(job_dir),
        "-i", str(MAX_ITER),
        "--config-file", OPENHANDS_CONFIG,
    ]

    # Full agent transcript is teed to <job_dir>/openhands.log so it can be tailed on the server
    # (Slack only gets the tail). Open in append mode so re-runs/change-requests accumulate.
    log_path = job_dir / "openhands.log"
    proc = subprocess.Popen(
        cmd, cwd=str(job_dir), env=dict(os.environ),
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
        text=True, bufsize=1,
    )
    lines: list[str] = []
    try:
        assert proc.stdout is not None
        with open(log_path, "a", encoding="utf-8") as lf:
            lf.write(f"\n\n===== RUN: {' '.join(cmd)} =====\n")
            for line in proc.stdout:
                lines.append(line)
                lf.write(line)
                lf.flush()
                if on_log:
                    try:
                        on_log(line.rstrip())
                    except Exception:
                        pass
        proc.wait(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        lines.append(f"\n[clone_runner] killed after {timeout}s timeout\n")
        return 124, "".join(lines)

    return proc.returncode or 0, "".join(lines)
