"""
工作手机SDK v3.0 - 设备自动发现服务
支持 UDP 广播 beacon + 局域网扫描 + 外网回退
"""

import asyncio
import json
import socket
import logging
import time
import struct
from typing import List, Dict, Optional, Any
from datetime import datetime

logger = logging.getLogger(__name__)

BEACON_PORT = 8898
BEACON_MAGIC = b"WORKPHONE_SDK"
SCAN_PORTS = [8899, 8080, 8443, 3000, 9000]
HEALTH_PATH = "/health"


def _get_local_ips() -> List[str]:
    """获取本机所有局域网 IP"""
    ips = []
    try:
        for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
            ip = info[4][0]
            if ip.startswith(("192.168.", "10.", "172.16.", "172.17.",
                              "172.18.", "172.19.", "172.2", "172.30.", "172.31.")):
                ips.append(ip)
    except Exception:
        pass
    if not ips:
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ips.append(s.getsockname()[0])
            s.close()
        except Exception:
            ips.append("127.0.0.1")
    return list(set(ips))


def _extract_subnets(ips: List[str]) -> List[str]:
    """从 IP 列表提取子网前缀"""
    subnets = set()
    for ip in ips:
        parts = ip.rsplit(".", 1)
        if len(parts) == 2:
            subnets.add(parts[0])
    return list(subnets)


class DiscoveryService:
    """局域网设备发现与服务广播"""

    def __init__(self):
        self.server_port: int = 8899
        self.beacon_running: bool = False
        self._beacon_task: Optional[asyncio.Task] = None
        self._scan_results: List[Dict[str, Any]] = []
        self._scan_time: float = 0
        self._discovered_servers: List[Dict[str, Any]] = []

    async def start_beacon(self, port: int = 8899):
        """启动 UDP 广播 beacon，让设备端能自动发现服务器"""
        self.server_port = port
        if self.beacon_running:
            return
        self.beacon_running = True
        self._beacon_task = asyncio.create_task(self._beacon_loop())
        logger.info(f"📡 UDP beacon 已启动 (port {BEACON_PORT})")

    async def stop_beacon(self):
        """停止 beacon"""
        self.beacon_running = False
        if self._beacon_task:
            self._beacon_task.cancel()
            try:
                await self._beacon_task
            except asyncio.CancelledError:
                pass
        logger.info("📡 UDP beacon 已停止")

    async def _beacon_loop(self):
        """每 5 秒广播一次服务器信息"""
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.setblocking(False)

        local_ips = _get_local_ips()
        beacon_data = {
            "magic": BEACON_MAGIC.decode(),
            "version": "3.0.0",
            "port": self.server_port,
            "ws_path": "/ws/device",
            "ips": local_ips,
            "hostname": socket.gethostname(),
        }

        while self.beacon_running:
            try:
                beacon_data["ts"] = int(time.time())
                beacon_data["ips"] = _get_local_ips()
                payload = json.dumps(beacon_data).encode()
                loop = asyncio.get_running_loop()
                await loop.run_in_executor(
                    None,
                    lambda: sock.sendto(payload, ("255.255.255.255", BEACON_PORT))
                )
            except Exception as e:
                logger.debug(f"Beacon 发送失败: {e}")
            await asyncio.sleep(5)

        sock.close()

    async def scan_lan(self, timeout: float = 3.0) -> List[Dict[str, Any]]:
        """扫描局域网中的工作手机 SDK 服务器（优化版：先 TCP 探活再 HTTP 验证）"""
        import httpx

        local_ips = _get_local_ips()
        subnets = _extract_subnets(local_ips)
        if not subnets:
            subnets = ["192.168.1"]

        results: List[Dict[str, Any]] = []
        my_ips_set = set(local_ips)

        async def _tcp_check(ip: str, port: int) -> bool:
            """快速 TCP 连接检测（0.5s 超时）"""
            try:
                _, writer = await asyncio.wait_for(
                    asyncio.open_connection(ip, port), timeout=0.5
                )
                writer.close()
                await writer.wait_closed()
                return True
            except Exception:
                return False

        async def _http_verify(ip: str, port: int):
            """HTTP 验证是否为 SDK 服务器"""
            try:
                async with httpx.AsyncClient(timeout=timeout) as client:
                    r = await client.get(f"http://{ip}:{port}{HEALTH_PATH}")
                    if r.status_code == 200:
                        data = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
                        results.append({
                            "ip": ip,
                            "port": port,
                            "url": f"http://{ip}:{port}",
                            "ws_url": f"ws://{ip}:{port}/ws/device",
                            "is_self": ip in my_ips_set and port == self.server_port,
                            "health": data,
                            "discovered_at": datetime.now().isoformat(),
                        })
            except Exception:
                pass

        # 第一轮：只扫描常用端口 8899，用 TCP 快速探活
        alive_hosts: list = []
        primary_port = 8899

        async def _quick_probe(ip: str):
            if await _tcp_check(ip, primary_port):
                alive_hosts.append(ip)

        for subnet in subnets:
            tasks = [_quick_probe(f"{subnet}.{host}") for host in range(1, 255)]
            batch_size = 100
            for i in range(0, len(tasks), batch_size):
                await asyncio.gather(*tasks[i:i + batch_size], return_exceptions=True)

        # 第二轮：对存活主机做 HTTP 验证
        verify_tasks = []
        for ip in alive_hosts:
            for port in SCAN_PORTS:
                verify_tasks.append(_http_verify(ip, port))
        if verify_tasks:
            await asyncio.gather(*verify_tasks, return_exceptions=True)

        self._scan_results = results
        self._scan_time = time.time()
        logger.info(f"LAN 扫描完成: {len(alive_hosts)} 台存活, {len(results)} 个 SDK 服务")
        return results

    async def listen_beacon(self, timeout: float = 10.0) -> List[Dict[str, Any]]:
        """监听 UDP beacon 来发现服务器"""
        servers: Dict[str, Dict[str, Any]] = {}
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        except AttributeError:
            pass
        sock.bind(("0.0.0.0", BEACON_PORT))
        sock.setblocking(False)

        loop = asyncio.get_running_loop()
        end_time = time.time() + timeout

        while time.time() < end_time:
            try:
                data, addr = await asyncio.wait_for(
                    loop.run_in_executor(None, lambda: sock.recvfrom(4096)),
                    timeout=min(2.0, end_time - time.time())
                )
                try:
                    info = json.loads(data.decode())
                    if info.get("magic") == BEACON_MAGIC.decode():
                        key = f"{addr[0]}:{info.get('port', 8899)}"
                        servers[key] = {
                            "ip": addr[0],
                            "port": info.get("port", 8899),
                            "url": f"http://{addr[0]}:{info.get('port', 8899)}",
                            "ws_url": f"ws://{addr[0]}:{info.get('port', 8899)}{info.get('ws_path', '/ws/device')}",
                            "hostname": info.get("hostname", ""),
                            "version": info.get("version", ""),
                            "server_ips": info.get("ips", []),
                            "discovered_at": datetime.now().isoformat(),
                            "method": "beacon",
                        }
                except (json.JSONDecodeError, UnicodeDecodeError):
                    pass
            except (asyncio.TimeoutError, BlockingIOError):
                continue

        sock.close()
        self._discovered_servers = list(servers.values())
        return self._discovered_servers

    def get_server_info(self) -> Dict[str, Any]:
        """返回本机服务器信息（供发现 API 使用）"""
        local_ips = _get_local_ips()
        return {
            "version": "3.0.0",
            "port": self.server_port,
            "hostname": socket.gethostname(),
            "ips": local_ips,
            "ws_url": f"ws://{local_ips[0] if local_ips else 'localhost'}:{self.server_port}/ws/device",
            "api_url": f"http://{local_ips[0] if local_ips else 'localhost'}:{self.server_port}",
            "beacon_port": BEACON_PORT,
            "beacon_running": self.beacon_running,
        }

    def get_last_scan(self) -> Dict[str, Any]:
        """获取上次扫描结果"""
        return {
            "results": self._scan_results,
            "scan_time": self._scan_time,
            "age_seconds": time.time() - self._scan_time if self._scan_time else -1,
        }


discovery_service = DiscoveryService()
