from typing import Dict, List, Optional, Any, Set, Tuple, Union
from dataclasses import dataclass, field
from enum import Enum
import logging
import sys
import os
import json
import asyncio
import ipaddress
import socket
import subprocess
import re
import concurrent.futures
from datetime import datetime, timedelta
import aiohttp
import platform

# 引入相同的日志格式化处理
class JsonFormatter(logging.Formatter):
    def __init__(self, fmt=None, datefmt=None, style='%'):
        super().__init__(fmt, datefmt, style)
        
    def format(self, record):
        log_record = super().format(record)
        return log_record.encode('utf-8', errors='replace').decode('utf-8')

# 配置日志格式
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(stream=sys.stdout)
    ]
)

# 设置所有处理器使用UTF-8编码格式化
for handler in logging.root.handlers:
    if isinstance(handler, logging.StreamHandler):
        handler.setFormatter(JsonFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

logger = logging.getLogger("network_scanner")

from network_devices import DeviceManager, NetworkDevice, DeviceVendor, DeviceType, DeviceStatus

class ScanMethod(Enum):
    """扫描方法枚举"""
    PING = "ping"
    TCP_CONNECT = "tcp_connect"
    SNMP = "snmp"
    HTTP = "http"
    SSH_BANNER = "ssh_banner"

class DeviceDiscoveryMethod(Enum):
    """设备发现方法枚举"""
    PING_SWEEP = "ping_sweep"
    ARP_TABLE = "arp_table"  
    SNMP_WALK = "snmp_walk"
    PORT_SCAN = "port_scan"
    CDP_LLDP = "cdp_lldp"

@dataclass
class ScanResult:
    """单个IP扫描结果"""
    ip_address: str
    is_alive: bool = False
    response_time: Optional[float] = None
    hostname: Optional[str] = None
    mac_address: Optional[str] = None
    vendor: Optional[str] = None
    device_type: Optional[str] = None
    open_ports: List[int] = field(default_factory=list)
    services: Dict[int, str] = field(default_factory=dict)
    snmp_info: Dict[str, Any] = field(default_factory=dict)
    os_info: Optional[str] = None
    discovered_at: datetime = field(default_factory=datetime.now)
    discovery_method: Set[DeviceDiscoveryMethod] = field(default_factory=set)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "ip_address": self.ip_address,
            "is_alive": self.is_alive,
            "response_time": self.response_time,
            "hostname": self.hostname,
            "mac_address": self.mac_address,
            "vendor": self.vendor,
            "device_type": self.device_type,
            "open_ports": self.open_ports,
            "services": self.services,
            "snmp_info": self.snmp_info,
            "os_info": self.os_info,
            "discovered_at": self.discovered_at.isoformat(),
            "discovery_method": [method.value for method in self.discovery_method]
        }

@dataclass
class ScanConfiguration:
    """扫描配置"""
    timeout: float = 3.0
    max_concurrent: int = 50
    ping_enabled: bool = True
    port_scan_enabled: bool = True
    snmp_enabled: bool = True
    http_check_enabled: bool = True
    ssh_banner_enabled: bool = True
    common_ports: List[int] = field(default_factory=lambda: [22, 23, 80, 161, 443, 8080, 8443])
    snmp_communities: List[str] = field(default_factory=lambda: ["public", "private"])
    
class NetworkScanner:
    """网络扫描器"""
    
    def __init__(self, device_manager: DeviceManager):
        self.device_manager = device_manager
        self.data_dir = "data"
        self.scan_results_file = os.path.join(self.data_dir, "scan_results.json")
        
        # 创建数据目录
        os.makedirs(self.data_dir, exist_ok=True)
        
        # 加载历史扫描结果
        self.scan_results: Dict[str, ScanResult] = {}
        self.load_scan_results()
        
        # 厂商MAC地址前缀映射（OUI - Organizationally Unique Identifier）
        self.vendor_oui_map = {
            "00:00:0C": "Cisco",
            "00:01:42": "Cisco", 
            "00:01:43": "Cisco",
            "00:01:96": "Cisco",
            "00:01:97": "Cisco",
            "00:02:16": "Cisco",
            "00:02:17": "Cisco",
            "00:02:3D": "Cisco",
            "00:02:4A": "Cisco",
            "00:02:4B": "Cisco",
            "00:02:B9": "Cisco",
            "00:02:BA": "Cisco",
            "00:02:FC": "Cisco",
            "00:02:FD": "Cisco",
            "00:03:6B": "Cisco",
            "00:03:6C": "Cisco",
            "00:03:E3": "Cisco",
            "00:03:FD": "Cisco",
            "00:04:27": "Cisco",
            "00:04:28": "Cisco",
            "00:04:4D": "Cisco",
            "00:04:6D": "Cisco",
            "00:04:9A": "Cisco",
            "00:04:C0": "Cisco",
            "00:04:C1": "Cisco",
            "00:04:DD": "Cisco",
            "00:05:00": "Cisco",
            "00:05:01": "Cisco",
            "00:05:31": "Cisco",
            "00:05:32": "Cisco",
            "00:05:5E": "Cisco",
            "00:05:73": "Cisco",
            "00:05:74": "Cisco",
            "00:05:9A": "Cisco",
            "00:05:DC": "Cisco",
            "00:05:DD": "Cisco",
            "00:06:28": "Cisco",
            "00:06:2A": "Cisco",
            "00:06:52": "Cisco",
            "00:06:53": "Cisco",
            "00:06:7C": "Cisco",
            "00:06:C1": "Cisco",
            "00:06:D6": "Cisco",
            "00:06:D7": "Cisco",
            "00:06:F6": "Cisco",
            "00:07:0D": "Cisco",
            "00:07:0E": "Cisco",
            "00:07:4F": "Cisco",
            "00:07:50": "Cisco",
            "00:07:84": "Cisco",
            "00:07:85": "Cisco",
            "00:07:B3": "Cisco",
            "00:07:B4": "Cisco",
            "00:07:EB": "Cisco",
            "00:07:EC": "Cisco",
            "00:08:20": "Cisco",
            "00:08:21": "Cisco",
            "00:08:30": "Cisco",
            "00:08:31": "Cisco",
            "00:08:7C": "Cisco",
            "00:08:7D": "Cisco",
            "00:08:A3": "Cisco",
            "00:08:A4": "Cisco",
            "00:08:C2": "Cisco",
            "00:08:E2": "Cisco",
            "00:08:E3": "Cisco",
            "00:09:11": "Cisco",
            "00:09:12": "Cisco",
            "00:09:43": "Cisco",
            "00:09:44": "Cisco",
            "00:09:7B": "Cisco",
            "00:09:7C": "Cisco",
            "00:09:B6": "Cisco",
            "00:09:B7": "Cisco",
            "00:09:E8": "Cisco",
            "00:09:E9": "Cisco",
            "00:0A:41": "Cisco",
            "00:0A:42": "Cisco",
            "00:0A:8A": "Cisco",
            "00:0A:8B": "Cisco",
            "00:0A:B7": "Cisco",
            "00:0A:B8": "Cisco",
            "00:0A:F3": "Cisco",
            "00:0A:F4": "Cisco",
            "00:0B:45": "Cisco",
            "00:0B:46": "Cisco",
            "00:0B:5F": "Cisco",
            "00:0B:60": "Cisco",
            "00:0B:85": "Cisco",
            "00:0B:BE": "Cisco",
            "00:0B:BF": "Cisco",
            "00:0B:FC": "Cisco",
            "00:0B:FD": "Cisco",
            "00:0C:30": "Cisco",
            "00:0C:31": "Cisco",
            "00:0C:41": "Cisco",
            "00:0C:85": "Cisco",
            "00:0C:86": "Cisco",
            "00:0C:CE": "Cisco",
            "00:0C:CF": "Cisco",
            "00:0D:28": "Cisco",
            "00:0D:29": "Cisco",
            "00:0D:65": "Cisco",
            "00:0D:66": "Cisco",
            "00:0D:BC": "Cisco",
            "00:0D:BD": "Cisco",
            "00:0D:EC": "Cisco",
            "00:0D:ED": "Cisco",
            "00:0E:08": "Cisco",
            "00:0E:38": "Cisco",
            "00:0E:39": "Cisco",
            "00:0E:83": "Cisco",
            "00:0E:84": "Cisco",
            "00:0E:D6": "Cisco",
            "00:0E:D7": "Cisco",
            "00:0F:23": "Cisco",
            "00:0F:24": "Cisco",
            "00:0F:34": "Cisco",
            "00:0F:35": "Cisco",
            "00:0F:66": "Cisco",
            "00:0F:8F": "Cisco",
            "00:0F:90": "Cisco",
            "00:0F:F7": "Cisco",
            "00:0F:F8": "Cisco",
            "00:10:07": "Cisco",
            "00:10:11": "Cisco",
            "00:10:29": "Cisco",
            "00:10:2F": "Cisco",
            "00:10:54": "Cisco",
            "00:10:79": "Cisco",
            "00:10:7B": "Cisco",
            "00:10:A6": "Cisco",
            "00:10:F6": "Cisco",
            "00:11:20": "Cisco",
            "00:11:21": "Cisco",
            "00:11:5C": "Cisco",
            "00:11:5D": "Cisco",
            "00:11:92": "Cisco",
            "00:11:93": "Cisco",
            "00:11:BB": "Cisco",
            "00:11:BC": "Cisco",
            "00:12:00": "Cisco",
            "00:12:01": "Cisco",
            "00:12:17": "Cisco",
            "00:12:43": "Cisco",
            "00:12:44": "Cisco",
            "00:12:7F": "Cisco",
            "00:12:80": "Cisco",
            "00:12:D9": "Cisco",
            "00:12:DA": "Cisco",
            "00:13:19": "Cisco",
            "00:13:1A": "Cisco",
            "00:13:5F": "Cisco",
            "00:13:60": "Cisco",
            "00:13:7F": "Cisco",
            "00:13:80": "Cisco",
            "00:13:C3": "Cisco",
            "00:13:C4": "Cisco",
            "00:14:1B": "Cisco",
            "00:14:1C": "Cisco",
            "00:14:69": "Cisco",
            "00:14:6A": "Cisco",
            "00:14:A8": "Cisco",
            "00:14:A9": "Cisco",
            "00:14:BF": "Cisco",
            "00:14:F1": "Cisco",
            "00:14:F2": "Cisco",
            "00:15:2B": "Cisco",
            "00:15:2C": "Cisco",
            "00:15:62": "Cisco",
            "00:15:63": "Cisco",
            "00:15:C6": "Cisco",
            "00:15:C7": "Cisco",
            "00:15:F9": "Cisco",
            "00:15:FA": "Cisco",
            "00:16:46": "Cisco",
            "00:16:47": "Cisco",
            "00:16:9C": "Cisco",
            "00:16:9D": "Cisco",
            "00:16:B6": "Cisco",
            "00:16:C7": "Cisco",
            "00:16:C8": "Cisco",
            "00:17:0E": "Cisco",
            "00:17:0F": "Cisco",
            "00:17:33": "Cisco",
            "00:17:34": "Cisco",
            "00:17:59": "Cisco",
            "00:17:5A": "Cisco",
            "00:17:94": "Cisco",
            "00:17:95": "Cisco",
            "00:17:DF": "Cisco",
            "00:17:E0": "Cisco",
            "00:18:18": "Cisco",
            "00:18:19": "Cisco",
            "00:18:39": "Cisco",
            "00:18:68": "Cisco",
            "00:18:73": "Cisco",
            "00:18:74": "Cisco",
            "00:18:B9": "Cisco",
            "00:18:BA": "Cisco",
            "00:19:06": "Cisco",
            "00:19:07": "Cisco",
            "00:19:2F": "Cisco",
            "00:19:30": "Cisco",
            "00:19:47": "Cisco",
            "00:19:55": "Cisco",
            "00:19:56": "Cisco",
            "00:19:A9": "Cisco",
            "00:19:AA": "Cisco",
            "00:19:E7": "Cisco",
            "00:19:E8": "Cisco",
            "00:1A:2F": "Cisco",
            "00:1A:30": "Cisco",
            "00:1A:6C": "Cisco",
            "00:1A:6D": "Cisco",
            "00:1A:A1": "Cisco",
            "00:1A:A2": "Cisco",
            "00:1A:E2": "Cisco",
            "00:1A:E3": "Cisco",
            "00:1B:0C": "Cisco",
            "00:1B:0D": "Cisco",
            "00:1B:2A": "Cisco",
            "00:1B:2B": "Cisco",
            "00:1B:53": "Cisco",
            "00:1B:54": "Cisco",
            "00:1B:67": "Cisco",
            "00:1B:8F": "Cisco",
            "00:1B:90": "Cisco",
            "00:1B:D4": "Cisco",
            "00:1B:D5": "Cisco",
            "00:1C:0E": "Cisco",
            "00:1C:0F": "Cisco",
            "00:1C:57": "Cisco",
            "00:1C:58": "Cisco",
            "00:1C:B0": "Cisco",
            "00:1C:B1": "Cisco",
            "00:1C:F6": "Cisco",
            "00:1C:F9": "Cisco",
            "00:1D:45": "Cisco",
            "00:1D:46": "Cisco",
            "00:1D:70": "Cisco",
            "00:1D:71": "Cisco",
            "00:1D:A1": "Cisco",
            "00:1D:A2": "Cisco",
            "00:1D:E5": "Cisco",
            "00:1D:E6": "Cisco",
            "00:1E:13": "Cisco",
            "00:1E:14": "Cisco",
            "00:1E:49": "Cisco",
            "00:1E:4A": "Cisco",
            "00:1E:6B": "Cisco",
            "00:1E:79": "Cisco",
            "00:1E:7A": "Cisco",
            "00:1E:BD": "Cisco",
            "00:1E:BE": "Cisco",
            "00:1E:F6": "Cisco",
            "00:1E:F7": "Cisco",
            "00:1F:26": "Cisco",
            "00:1F:27": "Cisco",
            "00:1F:6C": "Cisco",
            "00:1F:6D": "Cisco",
            "00:1F:9D": "Cisco",
            "00:1F:9E": "Cisco",
            "00:1F:C9": "Cisco",
            "00:1F:CA": "Cisco",
            "00:20:35": "Cisco",
            "00:20:36": "Cisco",
            "00:20:BA": "Cisco",
            "00:20:BB": "Cisco",
            "00:21:1B": "Cisco",
            "00:21:1C": "Cisco",
            "00:21:29": "Cisco",
            "00:21:55": "Cisco",
            "00:21:56": "Cisco",
            "00:21:A0": "Cisco",
            "00:21:A1": "Cisco",
            "00:21:BE": "Cisco",
            "00:21:D7": "Cisco",
            "00:21:D8": "Cisco",
            "00:22:0C": "Cisco",
            "00:22:0D": "Cisco",
            "00:22:55": "Cisco",
            "00:22:56": "Cisco",
            "00:22:6B": "Cisco",
            "00:22:90": "Cisco",
            "00:22:91": "Cisco",
            "00:22:BD": "Cisco",
            "00:22:BE": "Cisco",
            "00:23:04": "Cisco",
            "00:23:05": "Cisco",
            "00:23:33": "Cisco",
            "00:23:34": "Cisco",
            "00:23:5D": "Cisco",
            "00:23:5E": "Cisco",
            "00:23:AB": "Cisco",
            "00:23:AC": "Cisco",
            "00:23:BE": "Cisco",
            "00:23:EA": "Cisco",
            "00:23:EB": "Cisco",
            "00:24:13": "Cisco",
            "00:24:14": "Cisco",
            "00:24:50": "Cisco",
            "00:24:51": "Cisco",
            "00:24:C3": "Cisco",
            "00:24:C4": "Cisco",
            "00:24:F7": "Cisco",
            "00:24:F9": "Cisco",
            "00:25:2E": "Cisco",
            "00:25:45": "Cisco",
            "00:25:46": "Cisco",
            "00:25:83": "Cisco",
            "00:25:84": "Cisco",
            "00:25:B4": "Cisco",
            "00:25:B5": "Cisco",
            "00:26:0A": "Cisco",
            "00:26:0B": "Cisco",
            "00:26:51": "Cisco",
            "00:26:52": "Cisco",
            "00:26:98": "Cisco",
            "00:26:99": "Cisco",
            "00:26:CA": "Cisco",
            "00:26:CB": "Cisco",
            "00:27:0C": "Cisco",
            "00:27:0D": "Cisco",
            "00:27:23": "Cisco",
            "00:27:24": "Cisco",
            "00:40:96": "Cisco",
            "00:50:0B": "Cisco",
            "00:50:14": "Cisco",
            "00:50:50": "Cisco",
            "00:50:53": "Cisco",
            "00:50:54": "Cisco",
            "00:50:73": "Cisco",
            "00:50:80": "Cisco",
            "00:50:A2": "Cisco",
            "00:50:BD": "Cisco",
            "00:50:D1": "Cisco",
            "00:50:E2": "Cisco",
            "00:50:F0": "Cisco",
            "00:60:2F": "Cisco",
            "00:60:3E": "Cisco",
            "00:60:47": "Cisco",
            "00:60:5C": "Cisco",
            "00:60:70": "Cisco",
            "00:60:83": "Cisco",
            "00:90:21": "Cisco",
            "00:90:2B": "Cisco",
            "00:90:86": "Cisco",
            "00:90:92": "Cisco",
            "00:90:A6": "Cisco",
            "00:90:AB": "Cisco",
            "00:90:B1": "Cisco",
            "00:90:BF": "Cisco",
            "00:90:F2": "Cisco",
            "00:A0:C9": "Cisco",
            "00:B0:64": "Cisco",
            "00:C0:1D": "Cisco",
            "00:D0:06": "Cisco",
            "00:D0:58": "Cisco",
            "00:D0:79": "Cisco",
            "00:D0:90": "Cisco",
            "00:D0:97": "Cisco",
            "00:D0:BA": "Cisco",
            "00:D0:BB": "Cisco",
            "00:D0:BC": "Cisco",
            "00:D0:C0": "Cisco",
            "00:D0:C4": "Cisco",
            "00:D0:FF": "Cisco",
            "00:E0:14": "Cisco",
            "00:E0:1E": "Cisco",
            "00:E0:34": "Cisco",
            "00:E0:4F": "Cisco",
            "00:E0:8F": "Cisco",
            "00:E0:A3": "Cisco",
            "00:E0:B0": "Cisco",
            "00:E0:F7": "Cisco",
            "00:E0:F9": "Cisco",
            "00:E0:FE": "Cisco",
            "04:2C:6A": "Cisco",
            "04:6C:59": "Cisco",
            "04:C5:A4": "Cisco",
            "04:DA:D2": "Cisco",
            "04:FE:7F": "Cisco",
            "08:17:35": "Cisco",
            "08:1F:F3": "Cisco",
            "08:7A:4C": "Cisco",
            "08:96:AD": "Cisco",
            "08:CC:68": "Cisco",
            "0C:27:24": "Cisco",
            "0C:68:03": "Cisco",
            "0C:75:BD": "Cisco",
            "0C:85:25": "Cisco",
            "0C:D9:96": "Cisco",
            "0C:F5:A4": "Cisco",
            "10:8C:CF": "Cisco",
            "10:BD:18": "Cisco",
            "14:B8:6F": "Cisco",
            "14:DA:E9": "Cisco",
            "14:F6:D8": "Cisco",
            "18:8B:9D": "Cisco",
            "18:8B:45": "Cisco",
            "18:9C:5D": "Cisco",
            "18:EF:63": "Cisco",
            "1C:17:D3": "Cisco",
            "1C:6A:7A": "Cisco",
            "1C:DE:A7": "Cisco",
            "1C:E6:C7": "Cisco",
            "1C:E8:5D": "Cisco",
            "20:37:06": "Cisco",
            "20:3A:07": "Cisco",
            "20:4C:03": "Cisco",
            "20:BB:C0": "Cisco",
            "24:01:C7": "Cisco",
            "24:B6:57": "Cisco",
            "24:E9:B3": "Cisco",
            "28:0D:FC": "Cisco",
            "28:39:5E": "Cisco",
            "28:6F:7F": "Cisco",
            "28:94:0F": "Cisco",
            "2C:36:F8": "Cisco",
            "2C:3E:CF": "Cisco",
            "2C:54:2D": "Cisco",
            "2C:5A:0F": "Cisco",
            "30:37:A6": "Cisco",
            "30:E4:DB": "Cisco",
            "34:A8:4E": "Cisco",
            "34:DB:FD": "Cisco",
            "38:C8:5C": "Cisco",
            "3C:CE:73": "Cisco",
            "40:55:39": "Cisco",
            "44:58:29": "Cisco",
            "44:AD:D9": "Cisco",
            "44:E0:8E": "Cisco",
            "48:F8:B3": "Cisco",
            "4C:4E:35": "Cisco",
            "50:06:04": "Cisco",
            "50:17:FF": "Cisco",
            "50:3D:E5": "Cisco",
            "50:57:A8": "Cisco",
            "50:87:89": "Cisco",
            "54:7F:EE": "Cisco",
            "54:78:1A": "Cisco",
            "58:6D:8F": "Cisco",
            "58:97:1E": "Cisco",
            "58:AC:78": "Cisco",
            "58:BF:EA": "Cisco",
            "5C:50:15": "Cisco",
            "5C:83:8F": "Cisco",
            "60:73:5C": "Cisco",
            "64:00:F1": "Cisco",
            "64:16:8D": "Cisco",
            "64:A0:E7": "Cisco",
            "64:D1:54": "Cisco",
            "68:BD:AB": "Cisco",
            "68:EF:BD": "Cisco",
            "6C:20:56": "Cisco",
            "6C:41:6A": "Cisco",
            "6C:FA:89": "Cisco",
            "70:0D:B9": "Cisco",
            "70:10:5C": "Cisco",
            "70:81:05": "Cisco",
            "70:CA:9B": "Cisco",
            "74:26:AC": "Cisco",
            "74:A0:2F": "Cisco",
            "78:9A:18": "Cisco",
            "78:BA:F9": "Cisco",
            "7C:0E:CE": "Cisco",
            "7C:69:F6": "Cisco",
            "7C:95:F3": "Cisco",
            "80:E0:1D": "Cisco",
            "84:3D:C6": "Cisco",
            "84:78:AC": "Cisco",
            "84:B5:17": "Cisco",
            "84:B8:02": "Cisco",
            "88:43:E1": "Cisco",
            "88:90:8D": "Cisco",
            "88:F0:31": "Cisco",
            "8C:60:4F": "Cisco",
            "8C:B6:4F": "Cisco",
            "90:E2:BA": "Cisco",
            "94:D4:69": "Cisco",
            "98:FC:11": "Cisco",
            "9C:37:F4": "Cisco",
            "9C:4E:20": "Cisco",
            "A0:E0:AF": "Cisco",
            "A0:F8:49": "Cisco",
            "A4:0C:C3": "Cisco",
            "A4:4C:11": "Cisco",
            "A4:6C:2A": "Cisco",
            "A4:93:4C": "Cisco",
            "A8:9D:21": "Cisco",
            "AC:4A:67": "Cisco",
            "B0:7D:47": "Cisco",
            "B4:14:89": "Cisco",
            "B4:A4:E3": "Cisco",
            "B8:38:61": "Cisco",
            "B8:62:1F": "Cisco",
            "BC:16:65": "Cisco",
            "BC:67:1C": "Cisco",
            "C0:62:6B": "Cisco",
            "C0:67:AF": "Cisco",
            "C4:14:3C": "Cisco",
            "C4:64:13": "Cisco",
            "C4:71:54": "Cisco",
            "C8:4C:75": "Cisco",
            "C8:9C:1D": "Cisco",
            "CC:16:7E": "Cisco",
            "CC:46:D6": "Cisco",
            "CC:EF:48": "Cisco",
            "D0:72:DC": "Cisco",
            "D0:D0:FD": "Cisco",
            "D4:8C:B5": "Cisco",
            "D4:A0:2A": "Cisco",
            "D8:24:BD": "Cisco",
            "D8:B1:90": "Cisco",
            "DC:7B:94": "Cisco",
            "E0:5F:B9": "Cisco",
            "E4:48:C7": "Cisco",
            "E4:AA:5D": "Cisco",
            "E4:C7:22": "Cisco",
            "E8:65:49": "Cisco",
            "E8:B7:48": "Cisco",
            "EC:1D:8B": "Cisco",
            "EC:44:76": "Cisco",
            "EC:BD:1D": "Cisco",
            "F0:25:72": "Cisco",
            "F0:29:29": "Cisco",
            "F0:7D:68": "Cisco",
            "F4:CF:E2": "Cisco",
            "F8:0B:CB": "Cisco",
            "F8:4F:57": "Cisco",
            "F8:66:F2": "Cisco",
            "F8:C2:88": "Cisco",
            "FC:99:47": "Cisco",
            # 华为 OUI 前缀
            "00:1E:10": "Huawei",
            "00:25:9E": "Huawei",
            "00:46:A7": "Huawei",
            "00:E0:FC": "Huawei",
            "10:47:80": "Huawei",
            "14:5A:05": "Huawei",
            "18:66:DA": "Huawei",
            "1C:61:B4": "Huawei",
            "20:F9:E0": "Huawei",
            "28:6E:D4": "Huawei",
            "2C:AB:00": "Huawei",
            "34:29:8F": "Huawei",
            "34:6B:D3": "Huawei",
            "34:CE:00": "Huawei",
            "38:90:A5": "Huawei",
            "3C:DF:A9": "Huawei",
            "40:B0:34": "Huawei",
            "44:31:92": "Huawei",
            "48:46:FB": "Huawei",
            "4C:54:99": "Huawei",
            "50:3D:E5": "Huawei",
            "54:89:98": "Huawei",
            "5C:63:BF": "Huawei",
            "60:DE:44": "Huawei",
            "68:BD:AB": "Huawei",
            "6C:92:BF": "Huawei",
            "70:72:3C": "Huawei",
            "74:51:BA": "Huawei",
            "78:11:DC": "Huawei",
            "7C:A2:3E": "Huawei",
            "80:89:17": "Huawei",
            "84:A9:3E": "Huawei",
            "88:CF:98": "Huawei",
            "8C:BE:BE": "Huawei",
            "90:2B:34": "Huawei",
            "94:23:3C": "Huawei",
            "A0:B4:A5": "Huawei",
            "A4:5E:60": "Huawei",
            "A8:4C:A6": "Huawei",
            "AC:85:3D": "Huawei",
            "B0:5A:DA": "Huawei",
            "B4:3A:28": "Huawei",
            "B8:50:01": "Huawei",
            "BC:3F:8F": "Huawei",
            "BC:76:70": "Huawei",
            "C0:A5:DD": "Huawei",
            "C4:F0:81": "Huawei",
            "C8:9C:1D": "Huawei",
            "CC:2D:21": "Huawei",
            "D0:54:2D": "Huawei",
            "D4:D7:48": "Huawei",
            "D8:49:2F": "Huawei",
            "DC:D2:FC": "Huawei",
            "E0:19:1D": "Huawei",
            "E4:3E:D7": "Huawei",
            "E8:4E:84": "Huawei",
            "EC:23:3D": "Huawei",
            "F0:92:1C": "Huawei",
            "F4:28:53": "Huawei",
            "F8:E7:1E": "Huawei",
            "FC:48:EF": "Huawei",
            # H3C OUI 前缀
            "00:01:A7": "H3C",
            "00:13:AC": "H3C",
            "00:1E:C9": "H3C", 
            "00:22:A1": "H3C",
            "00:24:A1": "H3C",
            "04:C5:A4": "H3C",
            "08:00:09": "H3C",
            "0C:45:BA": "H3C",
            "18:A9:05": "H3C",
            "1C:B7:2C": "H3C",
            "20:89:84": "H3C",
            "24:E9:B3": "H3C",
            "28:6E:D4": "H3C",
            "2C:AB:00": "H3C",
            "30:89:4A": "H3C",
            "3C:52:82": "H3C",
            "48:7B:6B": "H3C",
            "4C:60:DE": "H3C",
            "58:69:6C": "H3C",
            "6C:50:4D": "H3C",
            "70:F9:6D": "H3C",
            "78:AC:C0": "H3C",
            "7C:A2:3E": "H3C",
            "80:71:1F": "H3C",
            "84:A9:C4": "H3C",
            "94:75:2A": "H3C",
            "A0:1D:48": "H3C",
            "A4:5E:60": "H3C",
            "A8:40:25": "H3C",
            "B0:18:32": "H3C",
            "B4:99:BA": "H3C",
            "BC:54:36": "H3C",
            "C0:A5:DD": "H3C",
            "C8:9C:1D": "H3C",
            "D0:27:88": "H3C",
            "D4:6D:7D": "H3C",
            "E0:D9:E3": "H3C",
            "E4:AF:A1": "H3C",
            "EC:38:8F": "H3C",
            "F0:7D:68": "H3C",
            "F4:E2:C6": "H3C",
            "F8:63:3F": "H3C",
            # Juniper OUI 前缀
            "00:05:85": "Juniper",
            "00:10:DB": "Juniper",
            "00:12:1E": "Juniper",
            "00:17:CB": "Juniper",
            "00:19:E2": "Juniper",
            "00:1B:C0": "Juniper",
            "00:1D:B5": "Juniper",
            "00:1F:12": "Juniper",
            "00:21:59": "Juniper",
            "00:23:9C": "Juniper",
            "00:26:88": "Juniper",
            "00:90:69": "Juniper",
            "02:05:85": "Juniper",
            "08:81:F4": "Juniper",
            "0C:86:10": "Juniper",
            "10:0E:7E": "Juniper",
            "14:4F:8A": "Juniper",
            "2C:21:72": "Juniper",
            "2C:6B:F5": "Juniper",
            "3C:94:D5": "Juniper",
            "40:A6:77": "Juniper",
            "44:F4:77": "Juniper",
            "5C:45:27": "Juniper",
            "64:64:9B": "Juniper",
            "78:19:F7": "Juniper",
            "78:FE:3D": "Juniper",
            "80:71:1F": "Juniper",
            "84:18:88": "Juniper",
            "9C:CC:83": "Juniper",
            "A8:D0:E5": "Juniper",
            "B0:C6:9A": "Juniper",
            "DC:38:E1": "Juniper",
            "EC:3E:F7": "Juniper",
            "F0:1C:2D": "Juniper",
            "F4:A7:39": "Juniper",
            "F4:CC:55": "Juniper"
        }
        
        # 常见网络设备端口映射
        self.device_port_signatures = {
            22: "SSH/Network Device",
            23: "Telnet/Network Device", 
            80: "HTTP/Web Management",
            161: "SNMP/Network Device",
            443: "HTTPS/Web Management",
            830: "NETCONF",
            8080: "HTTP Alt/Web Management",
            8443: "HTTPS Alt/Web Management"
        }
    
    def save_scan_results(self):
        """保存扫描结果到文件"""
        try:
            results_dict = {
                ip: result.to_dict() for ip, result in self.scan_results.items()
            }
            with open(self.scan_results_file, 'w', encoding='utf-8') as f:
                json.dump(results_dict, f, ensure_ascii=False, indent=2)
            logger.info(f"扫描结果已保存到: {self.scan_results_file}")
        except Exception as e:
            logger.error(f"保存扫描结果失败: {e}")
    
    def load_scan_results(self):
        """从文件加载扫描结果"""
        if not os.path.exists(self.scan_results_file):
            logger.info("扫描结果文件不存在，将创建新的")
            return
        
        try:
            with open(self.scan_results_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            for ip, result_data in data.items():
                result = ScanResult(
                    ip_address=result_data["ip_address"],
                    is_alive=result_data["is_alive"],
                    response_time=result_data.get("response_time"),
                    hostname=result_data.get("hostname"),
                    mac_address=result_data.get("mac_address"),
                    vendor=result_data.get("vendor"),
                    device_type=result_data.get("device_type"),
                    open_ports=result_data.get("open_ports", []),
                    services=result_data.get("services", {}),
                    snmp_info=result_data.get("snmp_info", {}),
                    os_info=result_data.get("os_info"),
                    discovered_at=datetime.fromisoformat(result_data["discovered_at"]),
                    discovery_method=set(DeviceDiscoveryMethod(method) for method in result_data.get("discovery_method", []))
                )
                self.scan_results[ip] = result
            
            logger.info(f"已加载{len(self.scan_results)}个扫描结果")
        except Exception as e:
            logger.error(f"加载扫描结果失败: {e}")
    
    async def ping_host(self, ip: str, timeout: float = 3.0) -> Tuple[bool, Optional[float]]:
        """异步ping主机"""
        try:
            # 根据操作系统选择ping命令
            if platform.system().lower() == "windows":
                cmd = ["ping", "-n", "1", "-w", str(int(timeout * 1000)), ip]
            else:
                cmd = ["ping", "-c", "1", "-W", str(int(timeout)), ip]
            
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await asyncio.wait_for(
                process.communicate(), timeout=timeout + 1
            )
            
            if process.returncode == 0:
                # 解析ping时间
                output = stdout.decode()
                if platform.system().lower() == "windows":
                    # Windows ping输出解析
                    time_match = re.search(r'时间[<=](\d+)ms|time[<=](\d+)ms', output, re.IGNORECASE)
                    if time_match:
                        time_value = time_match.group(1) or time_match.group(2)
                        return True, float(time_value)
                else:
                    # Linux/Unix ping输出解析
                    time_match = re.search(r'time=(\d+\.?\d*)ms', output)
                    if time_match:
                        return True, float(time_match.group(1))
                
                return True, None
            else:
                return False, None
                
        except asyncio.TimeoutError:
            return False, None
        except Exception as e:
            logger.debug(f"Ping {ip} 失败: {e}")
            return False, None
    
    async def scan_port(self, ip: str, port: int, timeout: float = 3.0) -> bool:
        """异步扫描单个端口"""
        try:
            reader, writer = await asyncio.wait_for(
                asyncio.open_connection(ip, port),
                timeout=timeout
            )
            writer.close()
            await writer.wait_closed()
            return True
        except:
            return False
    
    async def scan_ports(self, ip: str, ports: List[int], timeout: float = 3.0) -> List[int]:
        """异步扫描多个端口"""
        tasks = [self.scan_port(ip, port, timeout) for port in ports]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        open_ports = []
        for port, result in zip(ports, results):
            if isinstance(result, bool) and result:
                open_ports.append(port)
        
        return open_ports
    
    async def get_hostname(self, ip: str) -> Optional[str]:
        """获取主机名"""
        try:
            hostname = socket.gethostbyaddr(ip)[0]
            return hostname
        except:
            return None
    
    def get_vendor_by_mac(self, mac_address: str) -> Optional[str]:
        """根据MAC地址前缀获取厂商信息"""
        if not mac_address:
            return None
        
        # 标准化MAC地址格式
        mac_upper = mac_address.upper().replace("-", ":").replace(".", ":")
        
        # 获取前6位字符（OUI部分）
        if len(mac_upper) >= 8:
            oui = mac_upper[:8]  # XX:XX:XX 格式
            return self.vendor_oui_map.get(oui)
        
        return None
    
    def infer_device_type(self, scan_result: ScanResult) -> DeviceType:
        """根据扫描结果推断设备类型"""
        open_ports = scan_result.open_ports
        vendor = scan_result.vendor or ""
        hostname = scan_result.hostname or ""
        
        # 基于端口判断
        if 161 in open_ports:  # SNMP端口
            if 22 in open_ports or 23 in open_ports:
                # 同时有SSH/Telnet和SNMP，可能是网络设备
                if any(keyword in hostname.lower() for keyword in ["switch", "sw", "router", "rt", "gw", "gateway"]):
                    if "switch" in hostname.lower() or "sw" in hostname.lower():
                        return DeviceType.SWITCH
                    elif "router" in hostname.lower() or "rt" in hostname.lower() or "gw" in hostname.lower():
                        return DeviceType.ROUTER
                elif vendor.lower() in ["cisco", "huawei", "h3c", "juniper"]:
                    return DeviceType.SWITCH  # 默认网络设备厂商为交换机
        
        # 基于厂商判断
        if vendor.lower() == "cisco":
            return DeviceType.SWITCH
        elif vendor.lower() in ["huawei", "h3c"]:
            return DeviceType.SWITCH
        elif vendor.lower() == "juniper":
            return DeviceType.ROUTER
        
        # 基于主机名判断
        hostname_lower = hostname.lower()
        if any(keyword in hostname_lower for keyword in ["switch", "sw"]):
            return DeviceType.SWITCH
        elif any(keyword in hostname_lower for keyword in ["router", "rt", "gw", "gateway"]):
            return DeviceType.ROUTER
        elif any(keyword in hostname_lower for keyword in ["firewall", "fw", "asa", "pix"]):
            return DeviceType.FIREWALL
        elif any(keyword in hostname_lower for keyword in ["ap", "wireless", "wifi"]):
            return DeviceType.ACCESS_POINT
        
        # 默认返回其他类型
        return DeviceType.OTHER
    
    def infer_device_vendor(self, scan_result: ScanResult) -> DeviceVendor:
        """根据扫描结果推断设备厂商"""
        vendor = scan_result.vendor or ""
        hostname = scan_result.hostname or ""
        
        # 优先使用MAC地址推断的厂商
        if vendor:
            vendor_lower = vendor.lower()
            if "cisco" in vendor_lower:
                return DeviceVendor.CISCO
            elif "huawei" in vendor_lower:
                return DeviceVendor.HUAWEI
            elif "h3c" in vendor_lower:
                return DeviceVendor.H3C
            elif "juniper" in vendor_lower:
                return DeviceVendor.JUNIPER
        
        # 基于主机名判断
        hostname_lower = hostname.lower()
        if any(keyword in hostname_lower for keyword in ["cisco", "cat", "nexus"]):
            return DeviceVendor.CISCO
        elif any(keyword in hostname_lower for keyword in ["huawei", "hw"]):
            return DeviceVendor.HUAWEI
        elif "h3c" in hostname_lower:
            return DeviceVendor.H3C
        elif any(keyword in hostname_lower for keyword in ["juniper", "mx", "ex", "qfx"]):
            return DeviceVendor.JUNIPER
        
        return DeviceVendor.OTHER
    
    async def snmp_query(self, ip: str, community: str = "public", oid: str = "1.3.6.1.2.1.1.1.0", timeout: float = 3.0) -> Optional[str]:
        """SNMP查询（需要安装pysnmp库）"""
        try:
            # 这里应该使用pysnmp库，但为了避免依赖，先返回None
            # 可以后续添加SNMP支持
            return None
        except Exception as e:
            logger.debug(f"SNMP查询 {ip} 失败: {e}")
            return None
    
    async def scan_single_host(self, ip: str, config: ScanConfiguration) -> ScanResult:
        """扫描单个主机"""
        result = ScanResult(ip_address=ip)
        
        try:
            # 1. Ping检测
            if config.ping_enabled:
                is_alive, response_time = await self.ping_host(ip, config.timeout)
                result.is_alive = is_alive
                result.response_time = response_time
                if is_alive:
                    result.discovery_method.add(DeviceDiscoveryMethod.PING_SWEEP)
            
            # 如果主机不可达，跳过其他检测
            if not result.is_alive:
                return result
            
            # 2. 端口扫描
            if config.port_scan_enabled:
                open_ports = await self.scan_ports(ip, config.common_ports, config.timeout)
                result.open_ports = open_ports
                if open_ports:
                    result.discovery_method.add(DeviceDiscoveryMethod.PORT_SCAN)
                
                # 识别服务
                for port in open_ports:
                    service = self.device_port_signatures.get(port, f"Unknown/{port}")
                    result.services[port] = service
            
            # 3. 获取主机名
            hostname = await self.get_hostname(ip)
            if hostname and hostname != ip:
                result.hostname = hostname
            
            # 4. SNMP检测
            if config.snmp_enabled and 161 in result.open_ports:
                for community in config.snmp_communities:
                    snmp_result = await self.snmp_query(ip, community, timeout=config.timeout)
                    if snmp_result:
                        result.snmp_info[community] = snmp_result
                        result.discovery_method.add(DeviceDiscoveryMethod.SNMP_WALK)
                        break
            
            # 5. 推断设备信息
            if result.mac_address:
                result.vendor = self.get_vendor_by_mac(result.mac_address)
            
            result.device_type = self.infer_device_type(result).value
            vendor_enum = self.infer_device_vendor(result)
            if result.vendor is None:
                result.vendor = vendor_enum.value
            
        except Exception as e:
            logger.error(f"扫描主机 {ip} 时出错: {e}")
        
        return result
    
    async def scan_network_range(self, network: str, config: Optional[ScanConfiguration] = None) -> List[ScanResult]:
        """扫描网络范围"""
        if config is None:
            config = ScanConfiguration()
        
        logger.info(f"开始扫描网络范围: {network}")
        
        try:
            # 解析网络范围
            net = ipaddress.ip_network(network, strict=False)
            ip_list = [str(ip) for ip in net.hosts()]
            
            # 限制扫描范围（避免扫描过大的网络）
            if len(ip_list) > 1000:
                logger.warning(f"网络范围过大 ({len(ip_list)} 个主机)，限制为前1000个")
                ip_list = ip_list[:1000]
            
            logger.info(f"将扫描 {len(ip_list)} 个主机")
            
            # 使用信号量限制并发数
            semaphore = asyncio.Semaphore(config.max_concurrent)
            
            async def scan_with_semaphore(ip):
                async with semaphore:
                    return await self.scan_single_host(ip, config)
            
            # 并发扫描
            tasks = [scan_with_semaphore(ip) for ip in ip_list]
            results = await asyncio.gather(*tasks, return_exceptions=True)
            
            # 过滤成功的结果
            valid_results = []
            for result in results:
                if isinstance(result, ScanResult):
                    valid_results.append(result)
                    # 保存到结果字典
                    self.scan_results[result.ip_address] = result
                elif isinstance(result, Exception):
                    logger.error(f"扫描任务异常: {result}")
            
            # 过滤活跃主机
            alive_results = [r for r in valid_results if r.is_alive]
            
            logger.info(f"扫描完成，发现 {len(alive_results)} 个活跃主机")
            
            # 保存扫描结果
            self.save_scan_results()
            
            return alive_results
            
        except Exception as e:
            logger.error(f"扫描网络范围失败: {e}")
            return []
    
    async def discover_devices_from_scan(self, scan_results: List[ScanResult], auto_create: bool = True) -> List[NetworkDevice]:
        """从扫描结果中发现并创建设备"""
        discovered_devices = []
        
        for result in scan_results:
            if not result.is_alive:
                continue
                
            # 检查设备是否已存在
            existing_devices = self.device_manager.list_devices()
            device_exists = any(device.ip_address == result.ip_address for device in existing_devices)
            
            if device_exists:
                logger.debug(f"设备已存在: {result.ip_address}")
                continue
            
            if auto_create:
                # 创建新设备
                device_name = result.hostname or f"Device-{result.ip_address.replace('.', '-')}"
                
                try:
                    device_type = DeviceType(result.device_type) if result.device_type else DeviceType.OTHER
                except ValueError:
                    device_type = DeviceType.OTHER
                
                try:
                    vendor = DeviceVendor(result.vendor.lower()) if result.vendor else DeviceVendor.OTHER
                except (ValueError, AttributeError):
                    vendor = DeviceVendor.OTHER
                
                new_device = NetworkDevice(
                    name=device_name,
                    ip_address=result.ip_address,
                    device_type=device_type,
                    vendor=vendor,
                    status=DeviceStatus.UNKNOWN,
                    description=f"通过网络扫描自动发现 (扫描时间: {result.discovered_at.strftime('%Y-%m-%d %H:%M:%S')})",
                    tags=["网络扫描发现", f"响应时间:{result.response_time}ms" if result.response_time else ""]
                )
                
                # 添加到设备管理器
                device_id = self.device_manager.add_device(new_device)
                discovered_devices.append(new_device)
                
                logger.info(f"创建新设备: {new_device.name} ({new_device.ip_address}) - {new_device.vendor.value}")
        
        return discovered_devices
    
    def get_scan_statistics(self) -> Dict[str, Any]:
        """获取扫描统计信息"""
        total_scanned = len(self.scan_results)
        alive_hosts = len([r for r in self.scan_results.values() if r.is_alive])
        
        # 厂商统计
        vendor_stats = {}
        for result in self.scan_results.values():
            if result.is_alive and result.vendor:
                vendor_stats[result.vendor] = vendor_stats.get(result.vendor, 0) + 1
        
        # 设备类型统计
        device_type_stats = {}
        for result in self.scan_results.values():
            if result.is_alive and result.device_type:
                device_type_stats[result.device_type] = device_type_stats.get(result.device_type, 0) + 1
        
        # 发现方法统计
        discovery_method_stats = {}
        for result in self.scan_results.values():
            if result.is_alive:
                for method in result.discovery_method:
                    discovery_method_stats[method.value] = discovery_method_stats.get(method.value, 0) + 1
        
        return {
            "total_scanned": total_scanned,
            "alive_hosts": alive_hosts,
            "discovery_rate": f"{(alive_hosts/total_scanned*100):.1f}%" if total_scanned > 0 else "0%",
            "vendor_distribution": vendor_stats,
            "device_type_distribution": device_type_stats,
            "discovery_method_distribution": discovery_method_stats,
            "last_scan_time": max([r.discovered_at for r in self.scan_results.values()]).isoformat() if self.scan_results else None
        }
    
    def clear_scan_results(self):
        """清空扫描结果"""
        self.scan_results.clear()
        self.save_scan_results()
        logger.info("扫描结果已清空")

# 创建网络扫描器单例
def create_network_scanner(device_manager: DeviceManager) -> NetworkScanner:
    """创建网络扫描器实例"""
    return NetworkScanner(device_manager) 