"""LAN WS server — 같은 AP에 있는 phone 앱이 bridge에 직접 붙어 skill 명령 송신.

gateway 경로와 독립. gateway/MQTT 연결 상태와 무관하게 LAN 내에서 동작.
기존 skill 핸들러(handle_command)를 그대로 재사용 — 메시지 형식만 맞춰주면 됨.

Protocol:
- URL: `ws://0.0.0.0:{BRIDGE_LAN_PORT}/command[?token=<opt>]`
- Request: `{"type":"command","data":{"command_id":"...","command_type":"...","parameters":{...}}}`
- Response: `{"type":"command_response","data":{"command_id":"...", ...result}}`
"""
import asyncio
import json
import logging
import os
import uuid
from urllib.parse import urlparse, parse_qs

import websockets

from .ws_client import handle_command

log = logging.getLogger(__name__)

DEFAULT_PORT = int(os.getenv("BRIDGE_LAN_PORT", "8765"))
LAN_TOKEN = os.getenv("BRIDGE_LAN_TOKEN", "")  # 빈 값이면 LAN 신뢰 (token 검증 스킵)


def _extract_path(ws, legacy_path):
    """websockets 10~13 호환 — v13+는 handler(ws)만 받고 path는 ws.request.path."""
    if legacy_path is not None:
        return legacy_path
    req = getattr(ws, "request", None)
    return getattr(req, "path", "/") if req is not None else "/"


async def _accept(ws, path=None):
    path = _extract_path(ws, path)

    if LAN_TOKEN:
        qs = parse_qs(urlparse(path).query)
        if (qs.get("token") or [""])[0] != LAN_TOKEN:
            log.warning("[lan] rejected: invalid token")
            try:
                await ws.close(code=4001, reason="invalid token")
            except Exception:
                pass
            return

    peer = getattr(ws, "remote_address", None)
    log.info(f"[lan] client connected: {peer}")
    try:
        async for raw in ws:
            try:
                message = json.loads(raw)
            except (json.JSONDecodeError, TypeError):
                await ws.send(json.dumps({"error": "invalid_json"}))
                continue

            try:
                result = await handle_command(message)
                if not result:
                    result = {"status": "unknown", "message": "command not handled"}
            except Exception as e:
                log.exception(f"[lan] handle_command error: {e}")
                result = {"status": "error", "error": str(e)}

            cmd_id = (message.get("data") or {}).get("command_id") or str(uuid.uuid4())
            response = {
                "type": "command_response",
                "data": {"command_id": cmd_id, **result},
            }
            await ws.send(json.dumps(response))
    except websockets.exceptions.ConnectionClosed:
        pass
    except Exception as e:
        log.warning(f"[lan] session error: {e}")
    finally:
        log.info(f"[lan] client disconnected: {peer}")


async def run_lan_server(port: int = DEFAULT_PORT):
    """bridge 시작 시 asyncio 태스크로 병렬 실행."""
    log.info(
        f"[lan] listening on 0.0.0.0:{port} (token={'on' if LAN_TOKEN else 'off'})"
    )
    async with websockets.serve(_accept, "0.0.0.0", port):
        await asyncio.Future()  # forever
