"""CLI entry point for the DeviceAgent.

Full mode (requires Bridge + optional MQTT)::

    python -m device_agent \\
        --bridge wss://bridge.beeos.ai \\
        --key ~/.beeos/device-agent.key \\
        --adb R5CT1234 \\
        --mqtt-host mqtt.beeos.ai \\
        --mqtt-port 8883 \\
        --vlm-model bytedance/ui-tars-1.5-7b

Standalone mode (zero backend dependencies, HTTP API only)::

    python -m device_agent --standalone --adb R5CT1234

Interactive chat mode (standalone + REPL)::

    python -m device_agent --chat --vlm-api-key sk-...
"""

from __future__ import annotations

import argparse
import asyncio
import logging
import os
import signal

from .agent import DeviceAgent, DeviceAgentConfig
from .runtime.android_adb import AndroidADBRuntime
from .runtime.desktop import DesktopRuntime

logger = logging.getLogger(__name__)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        prog="device-agent",
        description="BeeOS DeviceAgent — AI-powered device control",
    )
    parser.add_argument("--standalone", action="store_true",
                        help="Standalone mode: skip Bridge/MQTT, auto-enable HTTP API")
    parser.add_argument("--chat", action="store_true",
                        help="Interactive chat mode: standalone + REPL to drive device via LLM")
    parser.add_argument("--bridge", default="", help="Bridge WebSocket URL (required unless --standalone)")
    parser.add_argument("--key", help="Ed25519 key file path")
    parser.add_argument("--service", default="acp", help="Bridge service name")
    parser.add_argument("--mode", choices=["android", "desktop"], default="android", help="Runtime mode")
    parser.add_argument("--adb", help="ADB device serial (android mode, auto-detected if omitted)")
    parser.add_argument("--adb-host", default="127.0.0.1", help="ADB server host")
    parser.add_argument("--adb-port", type=int, default=5037, help="ADB server port")
    parser.add_argument("--display", help="X11 DISPLAY (desktop mode, e.g. :1)")
    parser.add_argument("--device-id", help="Device ID for MQTT topics")
    parser.add_argument("--mqtt-host", default="", help="MQTT broker host")
    parser.add_argument("--mqtt-port", type=int, default=8883, help="MQTT broker port")
    parser.add_argument("--mqtt-username", help="MQTT username")
    parser.add_argument("--mqtt-password", help="MQTT password")
    parser.add_argument("--no-mqtt-tls", action="store_true", help="Disable MQTT TLS")
    parser.add_argument("--vlm-api-key", default="",
                        help="VLM API key (or env VLM_API_KEY)")
    parser.add_argument("--vlm-base-url", default="",
                        help="VLM API base URL (or env VLM_BASE_URL). "
                             "e.g. https://openrouter.ai/api/v1")
    parser.add_argument("--vlm-model", default="",
                        help="VLM model identifier (or env VLM_MODEL). "
                             "Recommended: bytedance/ui-tars-1.5-7b (SOTA GUI agent, cheapest). "
                             "Alternatives: qwen/qwen3-vl-32b-instruct, openai/gpt-4o")
    parser.add_argument("--cooldown", type=float, default=30.0, help="Stream cooldown (seconds)")
    parser.add_argument("--http", action="store_true", help="Enable HTTP REST API")
    parser.add_argument("--http-host", default="0.0.0.0", help="HTTP API host")
    parser.add_argument("--http-port", type=int, default=9090, help="HTTP API port")
    parser.add_argument("--log-level", default="INFO", help="Log level")

    args = parser.parse_args()

    if args.chat:
        args.standalone = True

    if not args.standalone and not args.bridge:
        parser.error("--bridge is required (or use --standalone / --chat for local-only mode)")

    if args.standalone:
        args.http = True

    return args


async def run(args: argparse.Namespace) -> None:
    if args.mode == "desktop":
        runtime = DesktopRuntime(display=args.display)
    else:
        runtime = AndroidADBRuntime(
            serial=args.adb,
            adb_host=args.adb_host,
            adb_port=args.adb_port,
        )

    mqtt_host = "" if args.standalone else (args.mqtt_host or os.environ.get("MQTT_HOST", ""))

    config = DeviceAgentConfig(
        standalone=args.standalone,
        bridge_url=args.bridge,
        bridge_service=args.service,
        key_file=args.key,
        device_id=args.device_id or "",
        mqtt_host=mqtt_host,
        mqtt_port=args.mqtt_port,
        mqtt_username=args.mqtt_username or os.environ.get("MQTT_USERNAME"),
        mqtt_password=args.mqtt_password or os.environ.get("MQTT_PASSWORD"),
        mqtt_use_tls=not args.no_mqtt_tls,
        vlm_api_key=args.vlm_api_key or os.environ.get("VLM_API_KEY", ""),
        vlm_base_url=args.vlm_base_url or os.environ.get("VLM_BASE_URL", ""),
        vlm_model=args.vlm_model or os.environ.get("VLM_MODEL", ""),
        cooldown_seconds=args.cooldown,
        http_enabled=args.http,
        http_host=args.http_host,
        http_port=args.http_port,
    )

    agent = DeviceAgent(config, runtime)

    stop_event = asyncio.Event()

    def handle_signal(sig: int, frame: object) -> None:
        logging.info("Received signal %d, shutting down...", sig)
        stop_event.set()

    signal.signal(signal.SIGINT, handle_signal)
    signal.signal(signal.SIGTERM, handle_signal)

    await agent.start()

    if args.chat:
        await _chat_loop(agent, stop_event)
    else:
        await stop_event.wait()

    await agent.stop()


# ---------------------------------------------------------------------------
# Interactive chat REPL
# ---------------------------------------------------------------------------

_CHAT_HELP = """Commands:
  <any text>     Send instruction to AI (e.g. "打开设置")
  screenshot     Save a screenshot to ./screenshot.png
  info           Show device info
  help           Show this help
  quit / exit    Stop the agent and exit
"""


async def _chat_loop(agent: DeviceAgent, stop_event: asyncio.Event) -> None:
    """Interactive REPL: read instructions from stdin, execute via TaskEngine."""
    info = await agent._runtime.get_info()
    port = agent._config.http_port

    print(f"\nDeviceAgent connected: {info.name} ({info.os}, {info.width}x{info.height})")
    if agent._config.http_enabled:
        print(f"HTTP API: http://localhost:{port}")
    print(_CHAT_HELP)

    loop = asyncio.get_event_loop()

    while not stop_event.is_set():
        try:
            line = await loop.run_in_executor(None, _read_input)
        except (EOFError, KeyboardInterrupt):
            break

        if line is None:
            break

        cmd = line.strip()
        if not cmd:
            continue

        if cmd in ("quit", "exit"):
            break

        if cmd == "help":
            print(_CHAT_HELP)
            continue

        if cmd == "info":
            dev = await agent._runtime.get_info()
            print(f"  Device:     {dev.name}")
            print(f"  OS:         {dev.os}")
            print(f"  Screen:     {dev.width}x{dev.height} @{dev.density}dpi")
            print(f"  Serial:     {dev.metadata.get('serial', '?')}")
            caps = ", ".join(c.value for c in dev.capabilities)
            print(f"  Caps:       {caps}")
            continue

        if cmd == "screenshot":
            data = await agent._runtime.screenshot()
            path = "screenshot.png"
            with open(path, "wb") as f:
                f.write(data)
            print(f"  Saved to {path} ({len(data)} bytes)")
            continue

        if not agent._task_engine:
            print("  [error] TaskEngine not initialized")
            continue

        task = await agent._task_engine.submit(cmd)
        print(f"  Task {task.id[:8]} submitted...")

        last_step_count = 0
        while True:
            await asyncio.sleep(0.5)
            t = agent._task_engine.get_task(task.id)
            if t is None:
                break

            for step in t.steps[last_step_count:]:
                action = step.get("action", "?")
                reasoning = step.get("reasoning", "")
                params = step.get("parameters", {})
                param_str = ""
                if params:
                    parts = [f"{k}={v}" for k, v in params.items()]
                    param_str = f"({', '.join(parts)})"
                print(f"  [Step {step.get('index', '?')}] {action}{param_str} — {reasoning}")
            last_step_count = len(t.steps)

            if t.status.value in ("completed", "failed", "cancelled"):
                elapsed = round(t.elapsed, 1)
                if t.status.value == "completed":
                    print(f"  Task completed ({len(t.steps)} steps, {elapsed}s)")
                    if t.result and t.result != f"Completed in {len(t.steps)} steps":
                        print(f"  Result: {t.result}")
                elif t.status.value == "failed":
                    print(f"  Task failed ({elapsed}s): {t.error}")
                else:
                    print(f"  Task cancelled ({elapsed}s)")
                break

    stop_event.set()


def _read_input() -> str | None:
    """Blocking stdin read (runs in executor)."""
    try:
        return input("> ")
    except (EOFError, KeyboardInterrupt):
        return None


def main() -> None:
    args = parse_args()

    if args.chat:
        log_level = logging.WARNING
    else:
        log_level = getattr(logging, args.log_level.upper(), logging.INFO)

    logging.basicConfig(
        level=log_level,
        format="%(asctime)s %(name)-30s %(levelname)-5s %(message)s",
    )
    asyncio.run(run(args))


if __name__ == "__main__":
    main()
