"""Lightweight HTTP API for the DeviceAgent.

Provides REST endpoints for task submission, device control, and
screenshot retrieval. Runs alongside the main DeviceAgent.
"""

from __future__ import annotations

import asyncio
import base64
import json
import logging
import os
import tempfile
from typing import Any

from aiohttp import web

from .ai.task_engine import TaskEngine
from .runtime.base import DeviceRuntime

logger = logging.getLogger(__name__)

_CORS_HEADERS = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
    "Access-Control-Max-Age": "86400",
}


@web.middleware
async def cors_middleware(request: web.Request, handler: Any) -> web.StreamResponse:
    if request.method == "OPTIONS":
        return web.Response(status=204, headers=_CORS_HEADERS)
    try:
        response = await handler(request)
    except web.HTTPException as exc:
        for k, v in _CORS_HEADERS.items():
            exc.headers[k] = v
        raise
    for k, v in _CORS_HEADERS.items():
        response.headers[k] = v
    return response


@web.middleware
async def error_middleware(request: web.Request, handler: Any) -> web.StreamResponse:
    try:
        return await handler(request)
    except json.JSONDecodeError:
        return web.json_response(
            {"error": "Invalid JSON in request body"}, status=400, headers=_CORS_HEADERS,
        )
    except web.HTTPException:
        raise
    except Exception as exc:
        logger.exception("Unhandled error in %s %s", request.method, request.path)
        return web.json_response(
            {"error": str(exc)}, status=500, headers=_CORS_HEADERS,
        )


class DeviceAgentAPI:
    """HTTP API server for direct device interaction."""

    def __init__(
        self,
        runtime: DeviceRuntime,
        task_engine: TaskEngine,
        host: str = "0.0.0.0",
        port: int = 9090,
    ) -> None:
        self._runtime = runtime
        self._task_engine = task_engine
        self._host = host
        self._port = port
        self._app = web.Application(middlewares=[cors_middleware, error_middleware])
        self._runner: web.AppRunner | None = None
        self._setup_routes()

    def _setup_routes(self) -> None:
        self._app.router.add_get("/health", self._health)
        self._app.router.add_get("/device/info", self._device_info)
        self._app.router.add_get("/device/screenshot", self._screenshot)
        self._app.router.add_get("/device/screenshot.png", self._screenshot_png)
        self._app.router.add_post("/device/action", self._device_action)
        self._app.router.add_post("/tasks", self._create_task)
        self._app.router.add_get("/tasks", self._list_tasks)
        self._app.router.add_get("/tasks/{task_id}", self._get_task)
        self._app.router.add_post("/tasks/{task_id}/cancel", self._cancel_task)
        self._app.router.add_post("/prompt", self._quick_prompt)
        self._app.router.add_post("/device/install-apk", self._install_apk)

    async def start(self) -> None:
        self._runner = web.AppRunner(self._app)
        await self._runner.setup()
        site = web.TCPSite(self._runner, self._host, self._port)
        await site.start()
        logger.info("HTTP API listening on %s:%d", self._host, self._port)

    async def stop(self) -> None:
        if self._runner:
            await self._runner.cleanup()

    async def _health(self, request: web.Request) -> web.Response:
        info = await self._runtime.get_info()
        return web.json_response({
            "status": "ok",
            "device": info.name,
            "connected": info.metadata.get("connected", True),
        })

    async def _device_info(self, request: web.Request) -> web.Response:
        info = await self._runtime.get_info()
        return web.json_response({
            "type": info.device_type.value,
            "name": info.name,
            "os": info.os,
            "version": info.os_version,
            "screen": {"width": info.width, "height": info.height, "density": info.density},
            "capabilities": [c.value for c in info.capabilities],
        })

    async def _screenshot(self, request: web.Request) -> web.Response:
        data = await self._runtime.screenshot()
        b64 = base64.b64encode(data).decode()
        return web.json_response({"image": b64, "format": "png"})

    async def _screenshot_png(self, request: web.Request) -> web.Response:
        data = await self._runtime.screenshot()
        return web.Response(body=data, content_type="image/png")

    async def _device_action(self, request: web.Request) -> web.Response:
        body = await request.json()
        action = body.get("action", "")
        params = body.get("parameters", {})

        if action == "tap":
            await self._runtime.tap(params.get("x", 0), params.get("y", 0))
        elif action == "swipe":
            await self._runtime.swipe(
                params.get("x1", 0), params.get("y1", 0),
                params.get("x2", 0), params.get("y2", 0),
                params.get("duration_ms", 300),
            )
        elif action == "type":
            await self._runtime.type_text(params.get("text", ""))
        elif action == "key":
            await self._runtime.press_key(params.get("keycode", ""))
        elif action == "back":
            await self._runtime.press_key("KEYCODE_BACK")
        elif action == "home":
            await self._runtime.press_key("KEYCODE_HOME")
        elif action == "shell":
            result = await self._runtime.execute_shell(params.get("command", ""))
            return web.json_response({"output": result})
        else:
            return web.json_response({"error": f"Unknown action: {action}"}, status=400)

        return web.json_response({"ok": True})

    async def _create_task(self, request: web.Request) -> web.Response:
        body = await request.json()
        instruction = body.get("instruction", "")
        if not instruction:
            return web.json_response({"error": "instruction is required"}, status=400)

        task = await self._task_engine.submit(instruction)
        return web.json_response({
            "task_id": task.id,
            "status": task.status.value,
            "instruction": task.instruction,
        }, status=201)

    async def _list_tasks(self, request: web.Request) -> web.Response:
        limit = int(request.query.get("limit", "50"))
        tasks = self._task_engine.list_tasks(limit=limit)
        return web.json_response({
            "tasks": [
                {
                    "id": t.id,
                    "instruction": t.instruction,
                    "status": t.status.value,
                    "steps": len(t.steps),
                    "elapsed": round(t.elapsed, 1),
                    "created_at": t.created_at,
                }
                for t in tasks
            ]
        })

    async def _get_task(self, request: web.Request) -> web.Response:
        task_id = request.match_info["task_id"]
        task = self._task_engine.get_task(task_id)
        if not task:
            return web.json_response({"error": "Task not found"}, status=404)
        return web.json_response({
            "id": task.id,
            "instruction": task.instruction,
            "status": task.status.value,
            "steps": task.steps,
            "result": task.result,
            "error": task.error,
            "elapsed": round(task.elapsed, 1),
        })

    async def _cancel_task(self, request: web.Request) -> web.Response:
        task_id = request.match_info["task_id"]
        ok = await self._task_engine.cancel(task_id)
        if not ok:
            return web.json_response({"error": "Task not found or already done"}, status=404)
        return web.json_response({"ok": True})

    async def _quick_prompt(self, request: web.Request) -> web.Response:
        """Shortcut: submit a task and wait for completion (up to timeout)."""
        body = await request.json()
        instruction = body.get("instruction", "") or body.get("prompt", "")
        timeout = float(body.get("timeout", 120))

        if not instruction:
            return web.json_response({"error": "instruction is required"}, status=400)

        task = await self._task_engine.submit(instruction)

        start = asyncio.get_event_loop().time()
        while (asyncio.get_event_loop().time() - start) < timeout:
            t = self._task_engine.get_task(task.id)
            if t and t.status.value in ("completed", "failed", "cancelled"):
                return web.json_response({
                    "task_id": t.id,
                    "status": t.status.value,
                    "result": t.result,
                    "error": t.error,
                    "steps": t.steps,
                    "elapsed": round(t.elapsed, 1),
                })
            await asyncio.sleep(1.0)

        return web.json_response({
            "task_id": task.id,
            "status": "running",
            "message": "Task still running, check /tasks/{task_id} for updates",
        }, status=202)

    async def _install_apk(self, request: web.Request) -> web.Response:
        """Upload and install an APK file on the device."""
        reader = await request.multipart()
        field = await reader.next()
        if field is None or field.name != "apk":
            return web.json_response({"error": "multipart field 'apk' required"}, status=400)

        filename = os.path.basename(field.filename or "upload.apk")
        if not filename.endswith(".apk"):
            return web.json_response({"error": "only .apk files accepted"}, status=400)

        tmp_dir = tempfile.mkdtemp(prefix="beeos_apk_")
        tmp_path = os.path.join(tmp_dir, filename)
        try:
            size = 0
            with open(tmp_path, "wb") as f:
                while True:
                    chunk = await field.read_chunk(8192)
                    if not chunk:
                        break
                    f.write(chunk)
                    size += len(chunk)

            logger.info("Installing APK: %s (%d bytes)", filename, size)
            await self._runtime.install_app(tmp_path)
            return web.json_response({"ok": True, "filename": filename, "size": size})
        except Exception as e:
            logger.exception("APK install failed")
            return web.json_response({"error": str(e)}, status=500)
        finally:
            if os.path.exists(tmp_path):
                os.unlink(tmp_path)
            if os.path.exists(tmp_dir):
                os.rmdir(tmp_dir)
