"""Motor bridge for RoboPark Pi client.

Translates motor commands (currently delivered via LiveKit data channel
from the agent) into HTTP POSTs against the local RoboVisionAI_PI motor
server (same endpoints the agent already calls: /trigger-motor and
/stop-motors).

Command wire format (JSON on the data channel):
    {"op": "motor", "name": "arm", "seconds": 3}
    {"op": "stop"}
"""

import asyncio
import os
import json
import logging
from typing import Awaitable, Callable, Optional

import httpx

log = logging.getLogger("robopark-pi.motor")


class MotorBridge:
    def __init__(self, motor_server_url: Optional[str], dry_run: bool = False):
        self.motor_server_url = (motor_server_url or "").rstrip("/")
        self.dry_run = dry_run
        token = os.getenv("ROBOPARK_MOTOR_TOKEN", "").strip() or os.getenv("ROBOPARK_MESH_TOKEN", "").strip()
        self.headers = {"X-RoboPark-Motor-Token": token} if token else {}
        self._lock = asyncio.Lock()  # the local motor server allows one motor at a time

    async def handle_command(self, raw: str | bytes) -> str:
        """Handle one command from the data channel. Returns a short result string."""
        try:
            text = raw.decode() if isinstance(raw, (bytes, bytearray)) else raw
            msg = json.loads(text)
        except Exception as e:
            log.warning(f"Could not parse motor command {raw!r}: {e}")
            return f"error: invalid json ({e})"

        op = (msg.get("op") or "").lower()
        if op == "motor":
            name = msg.get("name")
            seconds = int(msg.get("seconds", 3))
            if not name:
                return "error: missing 'name'"
            seconds = max(1, min(seconds, 30))
            return await self.move(name, seconds)
        elif op == "stop":
            return await self.stop_all()
        elif op == "ping":
            return "pong"
        else:
            log.warning(f"Unknown motor op: {op!r}")
            return f"error: unknown op {op!r}"

    async def move(self, name: str, seconds: int) -> str:
        if self.dry_run or not self.motor_server_url:
            log.info(f"[DRY-RUN] would move motor '{name}' for {seconds}s")
            return f"dry-run: {name} {seconds}s"
        async with self._lock:
            try:
                async with httpx.AsyncClient(timeout=seconds + 5.0) as c:
                    r = await c.post(
                        f"{self.motor_server_url}/trigger-motor",
                        json={"motor_name": name, "seconds": seconds}, headers=self.headers,
                    )
                if r.status_code == 200:
                    return f"moved {name} for {seconds}s"
                return f"motor server {r.status_code}: {r.text[:200]}"
            except Exception as e:
                log.error(f"move({name}) failed: {e}")
                return f"error: {e}"

    async def stop_all(self) -> str:
        if self.dry_run or not self.motor_server_url:
            log.info("[DRY-RUN] would stop all motors")
            return "dry-run: stop"
        try:
            async with httpx.AsyncClient(timeout=5.0) as c:
                r = await c.post(f"{self.motor_server_url}/stop-motors", json={}, headers=self.headers)
            return f"stop {r.status_code}"
        except Exception as e:
            log.error(f"stop_all failed: {e}")
            return f"error: {e}"
