"""Tests for ACP JSON-RPC router and device/session handlers."""

from __future__ import annotations

import json
from typing import AsyncIterator
from unittest.mock import AsyncMock

import pytest

from device_agent.acp.device_handler import DeviceHandler
from device_agent.acp.router import ACPRouter
from device_agent.acp.session_handler import SessionHandler
from device_agent.runtime.base import (
    DeviceCapability,
    DeviceInfo,
    DeviceRuntime,
    DeviceType,
)


class MockRuntime(DeviceRuntime):
    """Minimal mock runtime for testing ACP handlers."""

    async def connect(self) -> None:
        pass

    async def disconnect(self) -> None:
        pass

    async def get_info(self) -> DeviceInfo:
        return DeviceInfo(
            type=DeviceType.ANDROID,
            name="Test Device",
            os="Android 14",
            width=1080,
            height=2400,
            capabilities=[DeviceCapability.TOUCH, DeviceCapability.SCREENSHOT],
            metadata={"serial": "TEST123"},
        )

    async def screenshot(self) -> bytes:
        return b"\x89PNG\r\n\x1a\n"  # minimal PNG header

    async def get_ui_tree(self) -> list[dict]:
        return [{"class": "android.widget.Button", "text": "OK", "clickable": True}]

    async def tap(self, x: int, y: int) -> None:
        pass

    async def long_press(self, x: int, y: int, duration_ms: int = 1000) -> None:
        pass

    async def swipe(self, x1: int, y1: int, x2: int, y2: int, duration_ms: int = 300) -> None:
        pass

    async def type_text(self, text: str) -> None:
        pass

    async def press_key(self, keycode: str) -> None:
        pass

    async def open_app(self, package_or_path: str) -> None:
        pass

    async def install_app(self, apk_path: str) -> None:
        pass

    async def execute_shell(self, command: str) -> str:
        return f"output of: {command}"

    async def start_screen_stream(self) -> AsyncIterator[bytes]:
        yield b""


def _make_router() -> ACPRouter:
    runtime = MockRuntime()
    device_handler = DeviceHandler(runtime)
    session_handler = SessionHandler()
    return ACPRouter(device_handler, session_handler)


@pytest.mark.asyncio
async def test_device_info():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 1, "method": "device/info", "params": {}
    })
    response = await router.handle_message(request)
    assert response is not None
    result = json.loads(response)
    assert result["result"]["name"] == "Test Device"
    assert result["result"]["type"] == "android"


@pytest.mark.asyncio
async def test_device_screenshot():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 2, "method": "device/screenshot", "params": {}
    })
    response = await router.handle_message(request)
    assert response is not None
    result = json.loads(response)
    assert result["result"]["format"] == "png"
    assert "image" in result["result"]


@pytest.mark.asyncio
async def test_device_ui_tree():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 3, "method": "device/ui-tree", "params": {}
    })
    response = await router.handle_message(request)
    result = json.loads(response)
    assert len(result["result"]["nodes"]) == 1
    assert result["result"]["nodes"][0]["text"] == "OK"


@pytest.mark.asyncio
async def test_device_action_tap():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 4, "method": "device/action",
        "params": {"action": "tap", "x": 100, "y": 200}
    })
    response = await router.handle_message(request)
    result = json.loads(response)
    assert result["result"]["status"] == "ok"


@pytest.mark.asyncio
async def test_device_shell():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 5, "method": "device/shell",
        "params": {"command": "ls /"}
    })
    response = await router.handle_message(request)
    result = json.loads(response)
    assert "output of: ls /" in result["result"]["output"]


@pytest.mark.asyncio
async def test_session_new_and_list():
    router = _make_router()
    new_req = json.dumps({
        "jsonrpc": "2.0", "id": 6, "method": "session/new", "params": {}
    })
    resp = await router.handle_message(new_req)
    result = json.loads(resp)
    session_id = result["result"]["session_id"]
    assert session_id

    list_req = json.dumps({
        "jsonrpc": "2.0", "id": 7, "method": "session/list", "params": {}
    })
    resp = await router.handle_message(list_req)
    result = json.loads(resp)
    assert len(result["result"]["sessions"]) == 1


@pytest.mark.asyncio
async def test_unknown_method():
    router = _make_router()
    request = json.dumps({
        "jsonrpc": "2.0", "id": 8, "method": "unknown/method", "params": {}
    })
    response = await router.handle_message(request)
    result = json.loads(response)
    assert "error" in result
    assert result["error"]["code"] == -32601


@pytest.mark.asyncio
async def test_parse_error():
    router = _make_router()
    response = await router.handle_message("not json")
    result = json.loads(response)
    assert result["error"]["code"] == -32700


@pytest.mark.asyncio
async def test_notification_no_response():
    router = _make_router()
    notification = json.dumps({
        "jsonrpc": "2.0", "method": "unknown/notification", "params": {}
    })
    response = await router.handle_message(notification)
    assert response is None
