# 联软LV7000 API客户端
"""
联软LV7000终端安全管理系统 API 客户端。

认证流程：
1. 第一层：IP白名单（在联软后台配置，调用时自动生效）
2. 第二层：账号密码（ApiAccount + ApiPassword）
3. 第三层：Token（先调getToken获取，30分钟有效，自动缓存+刷新）

接口调用方式：
- GET请求：参数通过query string传递
- POST请求：参数通过form-data传递
- 统一携带 token + apiAccount + apiPassword + validatekey

使用示例：
    client = LianruanClient(base_url, api_account, api_password, validate_key)
    terminals = await client.query_dev_by_params(strusername="songxian")
    detail = await client.get_dev_all_info(strdevname="IT-SONGXIAN")
"""

import time
import logging
from typing import Optional

import httpx

from app.integrations.lianruan.exceptions import (
    LianruanApiError,
    LianruanAuthError,
    LianruanConnectionError,
)
from app.integrations.lianruan.models import (
    TerminalBasicInfo,
    TerminalAllInfo,
    UserInfo,
    OrgDeptInfo,
    OnlineStatus,
    TerminalSoftwareInfo,
)

logger = logging.getLogger(__name__)


class LianruanClient:
    """联软LV7000 API客户端。
    
    Attributes:
        base_url: 联软API地址，如 http://192.168.x.x:30098
        api_account: API账号
        api_password: API密码
        validate_key: 验证密钥
        _token: 缓存的Token
        _token_expire: Token过期时间戳
    """

    def __init__(
        self,
        base_url: str,
        api_account: str,
        api_password: str,
        validate_key: str = "",
        timeout: float = 30.0,
    ):
        self.base_url = base_url.rstrip("/")
        self.api_account = api_account
        self.api_password = api_password
        self.validate_key = validate_key
        self.timeout = timeout

        # Token缓存（30分钟有效，提前5分钟刷新）
        self._token: str = ""
        self._token_expire: float = 0.0

        # httpx异步客户端（连接池复用）
        self._client: Optional[httpx.AsyncClient] = None

    async def _get_client(self) -> httpx.AsyncClient:
        """获取或创建httpx异步客户端（懒初始化+连接池复用）"""
        if self._client is None or self._client.is_closed:
            self._client = httpx.AsyncClient(
                timeout=self.timeout,
                verify=False,  # 内网自签证书
            )
        return self._client

    async def close(self) -> None:
        """关闭httpx客户端，释放连接池"""
        if self._client and not self._client.is_closed:
            await self._client.aclose()

    # ==========================================================================
    # Token管理
    # ==========================================================================

    async def _ensure_token(self) -> str:
        """确保Token有效，过期则自动刷新。
        
        联软Token默认30分钟有效，提前5分钟刷新。
        
        Returns:
            str: 有效的Token字符串
        """
        now = time.time()
        # Token还有5分钟以上有效期，直接复用
        if self._token and now < self._token_expire - 300:
            return self._token

        # 重新获取Token
        logger.info("联软Token过期或为空，正在刷新...")
        try:
            client = await self._get_client()
            url = f"{self.base_url}/token"
            params = {
                "act": "getToken",
                "apiAccount": self.api_account,
                "apiPassword": self.api_password,
            }
            if self.validate_key:
                params["validatekey"] = self.validate_key

            resp = await client.get(url, params=params)
            resp.raise_for_status()
            data = resp.json()

            if data.get("status") != "SUCCESS":
                raise LianruanAuthError(
                    f"获取Token失败: {data.get('msg', '未知错误')}",
                    detail=str(data),
                )

            self._token = data.get("data", data.get("rows", ""))
            if not self._token:
                # 有些版本返回格式不同
                self._token = str(data.get("token", ""))
            
            # 30分钟有效期
            self._token_expire = now + 1800
            logger.info("联软Token刷新成功，有效期至 %s", 
                       time.strftime("%H:%M:%S", time.localtime(self._token_expire)))
            return self._token

        except httpx.ConnectError as e:
            raise LianruanConnectionError(
                f"无法连接联软服务器 {self.base_url}: {e}",
                detail=str(e),
            )
        except httpx.TimeoutException as e:
            raise LianruanConnectionError(
                f"连接联软服务器超时: {e}",
                detail=str(e),
            )

    # ==========================================================================
    # 通用请求方法
    # ==========================================================================

    async def _request(
        self,
        path: str,
        act: str,
        params: Optional[dict] = None,
        method: str = "GET",
    ) -> dict:
        """发送请求到联软API。
        
        自动携带认证参数（token + apiAccount + apiPassword）。
        
        Args:
            path: API路径，如 /terminal 或 /querydeptuser
            act: 操作类型，如 queryDevByParams
            params: 额外业务参数
            method: 请求方法（GET/POST）
            
        Returns:
            dict: 联软API返回的JSON数据
            
        Raises:
            LianruanAuthError: 认证失败
            LianruanApiError: 业务错误
            LianruanConnectionError: 网络错误
        """
        token = await self._ensure_token()
        client = await self._get_client()

        # 构建完整参数：认证参数 + 业务参数
        full_params = {
            "act": act,
            "apiAccount": self.api_account,
            "apiPassword": self.api_password,
            "token": token,
        }
        if self.validate_key:
            full_params["validatekey"] = self.validate_key
        if params:
            full_params.update(params)

        url = f"{self.base_url}{path}"

        try:
            if method.upper() == "POST":
                resp = await client.post(url, data=full_params)
            else:
                resp = await client.get(url, params=full_params)

            resp.raise_for_status()
            data = resp.json()

        except httpx.ConnectError as e:
            raise LianruanConnectionError(
                f"无法连接联软服务器: {e}",
                detail=str(e),
            )
        except httpx.TimeoutException as e:
            raise LianruanConnectionError(
                f"请求联软超时: {e}",
                detail=str(e),
            )
        except httpx.HTTPStatusError as e:
            raise LianruanApiError(
                f"联软HTTP错误 {e.response.status_code}",
                status=str(e.response.status_code),
                detail=str(e),
            )

        # 检查联软业务状态码
        status = data.get("status", "")
        if status == "INVALID":
            # Token可能过期，清除缓存重试一次
            self._token = ""
            self._token_expire = 0
            raise LianruanAuthError(
                f"联软认证失败（IP不在白名单或Token无效）: {data.get('msg', '')}",
                detail=str(data),
            )
        elif status == "ERROR":
            raise LianruanApiError(
                f"联软API错误: {data.get('msg', '')}",
                status=status,
                detail=str(data),
            )
        elif status == "Exceed":
            raise LianruanApiError(
                f"联软数据量超限: {data.get('msg', '')}",
                status=status,
                detail=str(data),
            )
        elif status != "SUCCESS":
            raise LianruanApiError(
                f"联软未知状态: {status} - {data.get('msg', '')}",
                status=status,
                detail=str(data),
            )

        return data

    # ==========================================================================
    # P0接口 — 终端设备查询
    # ==========================================================================

    async def query_dev_by_params(
        self,
        strusername: str = "",
        strdevname: str = "",
        strdevip: str = "",
        strmac: str = "",
        page: int = 1,
        per_page: int = 20,
    ) -> dict:
        """查询终端设备（核心映射接口）。
        
        ⭐ strusername 参数可直接按员工账号查终端，这是联软最大的优势！
        
        Args:
            strusername: 员工账号（映射金钥匙）
            strdevname: 计算机名
            strdevip: IP地址
            strmac: MAC地址
            page: 页码（从1开始）
            per_page: 每页条数
            
        Returns:
            dict: {"items": [TerminalBasicInfo], "total": int}
        """
        params: dict = {}
        if strusername:
            params["strusername"] = strusername
        if strdevname:
            params["strdevname"] = strdevname
        if strdevip:
            params["strdevip"] = strdevip
        if strmac:
            params["strmac"] = strmac

        # 联软分页参数
        params["page"] = str(page)
        params["rows"] = str(per_page)

        data = await self._request("/terminal", "queryDevByParams", params)

        rows = data.get("rows", [])
        total = data.get("total", len(rows))
        items = [TerminalBasicInfo(**row) for row in rows]

        return {"items": items, "total": total}

    async def get_dev_all_info(
        self,
        strdevname: str = "",
        strdevip: str = "",
    ) -> TerminalAllInfo:
        """查询终端详细信息（极详细硬件+软件+资产+网络）。
        
        比火绒_info2更丰富，包含逻辑磁盘使用率、显示器信息、内存条详情。
        
        Args:
            strdevname: 计算机名（二选一）
            strdevip: IP地址（二选一）
            
        Returns:
            TerminalAllInfo: 终端详细信息
        """
        params: dict = {}
        if strdevname:
            params["strdevname"] = strdevname
        if strdevip:
            params["strdevip"] = strdevip

        data = await self._request(
            "/devallinfoshowwithpaging", "getDevAllInfo", params
        )

        # 返回格式：data.equipment + data.equipmentdetail
        equipment = data.get("equipment", data.get("rows", [{}]))
        if isinstance(equipment, list) and equipment:
            equipment = equipment[0]

        equipment_detail = data.get("equipmentdetail", {})
        if isinstance(equipment_detail, list) and equipment_detail:
            equipment_detail = equipment_detail[0]
        dev_detail = equipment_detail.get("devdetail", equipment_detail)

        # 解析硬件详情
        result = TerminalAllInfo(
            strdevname=equipment.get("strdevname", ""),
            strip1=equipment.get("strip1", ""),
            strmac=equipment.get("strmac", ""),
            strdeptname=equipment.get("strdeptname", ""),
            strusername=equipment.get("strusername", ""),
            struserdes=equipment.get("struserdes", ""),
            stros=equipment.get("stros", ""),
            strdomain=equipment.get("strdomain", ""),
            istatus=dev_detail.get("istatus", equipment.get("istatus", "")),
            strverofuaagent=dev_detail.get("strverofuaagent", ""),
            devassetno=dev_detail.get("devassetno", ""),
            devgroup=dev_detail.get("devgroup", ""),
        )

        # 解析硬件列表
        self._parse_hardware_list(dev_detail, "CPUInformation", result.cpu)
        self._parse_hardware_list(dev_detail, "MemoryInformation", result.memory)
        self._parse_hardware_list(dev_detail, "HardDiskInformation", result.hard_disk)
        self._parse_hardware_list(dev_detail, "GraphicsCardInformation", result.graphics_card)
        self._parse_hardware_list(dev_detail, "MainboardInformation", result.mainboard)

        # 解析逻辑磁盘（含使用率）
        for ld in dev_detail.get("LogicalDiskInformation", []):
            result.logical_disk.append(LogicalDiskInfo(
                name=ld.get("strlogicaldiskname", ""),
                file_system=ld.get("strfilesystem", ""),
                total_size=ld.get("strtotalsize", ""),
                free_space=ld.get("strfreespace", ""),
                usage_percent=ld.get("strusagepercent", ""),
            ))

        # 解析网卡
        for nc in dev_detail.get("NetworkCardInformation", []):
            result.network_card.append(NetworkCardInfo(
                name=nc.get("strnetcardname", ""),
                is_wireless=nc.get("iswireless", ""),
                vendor=nc.get("strnetcardvendor", ""),
                mac=nc.get("strnetcardmac", ""),
            ))

        # 解析显示器
        for d in dev_detail.get("DisplayInformation", []):
            result.display.append(DisplayInfo(
                vendor=d.get("strdisplayvendor", ""),
                model=d.get("strdisplaymodel", ""),
                serial=d.get("strdisplayserial", ""),
                size=d.get("strdisplaysize", ""),
            ))

        return result

    def _parse_hardware_list(
        self, dev_detail: dict, key: str, target_list: list
    ) -> None:
        """解析硬件信息列表（CPU/内存/硬盘等）"""
        from app.integrations.lianruan.models import HardwareInfo

        for item in dev_detail.get(key, []):
            target_list.append(HardwareInfo(
                name=item.get("strcpuname", item.get("strmemname", item.get("strdiskname", ""))),
                model=item.get("strcpumodel", item.get("strmemmodel", item.get("strdiskmodel", ""))),
                vendor=item.get("strcpuvendor", item.get("strmemvendor", item.get("strdiskvendor", ""))),
                capacity=item.get("strcpufrequency", item.get("strmemcapacity", item.get("strdiskcapacity", ""))),
                serial=item.get("strcpuserial", item.get("strmemserial", item.get("strdiskserial", ""))),
            ))

    # ==========================================================================
    # P0接口 — 组织架构/用户
    # ==========================================================================

    async def get_user_info_by_account(self, useraccount: str) -> Optional[UserInfo]:
        """按账号查询用户信息。
        
        Args:
            useraccount: 用户账号
            
        Returns:
            UserInfo或None
        """
        data = await self._request(
            "/querydeptuser",
            "getUserInfoByAccount",
            {"useraccount": useraccount},
        )
        rows = data.get("rows", data.get("row", []))
        if rows:
            row = rows[0] if isinstance(rows, list) else rows
            return UserInfo(**row)
        return None

    async def get_all_org_info(self) -> list[OrgDeptInfo]:
        """获取全量组织架构（部门+用户）。
        
        用于定时同步，构建组织架构映射。
        
        Returns:
            list[OrgDeptInfo]: 部门列表，每个部门含用户列表
        """
        data = await self._request("/querydeptuser", "getAllOrgInfo")
        rows = data.get("rows", [])
        result = []
        for dept_data in rows:
            users = []
            for u in dept_data.get("users", []):
                users.append(UserInfo(**u))
            result.append(OrgDeptInfo(
                deptid=dept_data.get("deptid", ""),
                deptname=dept_data.get("deptname", ""),
                parentid=dept_data.get("parentid", ""),
                users=users,
            ))
        return result

    # ==========================================================================
    # P1接口 — 准入控制
    # ==========================================================================

    async def exist_online_user(
        self, username: str, strdevip: str = ""
    ) -> OnlineStatus:
        """查询终端用户是否在线。
        
        可精确判断某员工在某IP是否当前在线。
        
        Args:
            username: 用户名
            strdevip: IP地址（可选）
            
        Returns:
            OnlineStatus: 在线状态
        """
        params = {"username": username}
        if strdevip:
            params["strdevip"] = strdevip

        data = await self._request(
            "/access/onlineUser", "existOnlineUser", params
        )
        is_online = data.get("data", "0") == "1"
        return OnlineStatus(
            username=username,
            ip=strdevip,
            is_online=is_online,
        )

    # ==========================================================================
    # P1接口 — 终端操作
    # ==========================================================================

    async def notice_agent_msg(
        self, strdevip: str, message: str
    ) -> bool:
        """向终端推送弹窗消息。
        
        Args:
            strdevip: 终端IP
            message: 消息内容
            
        Returns:
            bool: 是否成功
        """
        data = await self._request(
            "/terminal",
            "noticeAgentMsg",
            {"strdevip": strdevip, "msg": message},
        )
        return data.get("status") == "SUCCESS"

    async def remote_wake_up(
        self, strdevip: str, strmac: str
    ) -> bool:
        """远程唤醒终端。
        
        Args:
            strdevip: 终端IP
            strmac: 终端MAC地址
            
        Returns:
            bool: 是否成功
        """
        data = await self._request(
            "/terminal",
            "remoteWakeUp",
            {"strdevip": strdevip, "strmac": strmac},
        )
        return data.get("status") == "SUCCESS"

    async def query_software_by_dev(
        self, strdevname: str = "", strdevip: str = ""
    ) -> Optional[TerminalSoftwareInfo]:
        """查询终端安装软件。
        
        Args:
            strdevname: 计算机名
            strdevip: IP地址
            
        Returns:
            TerminalSoftwareInfo或None
        """
        params: dict = {}
        if strdevname:
            params["strdevname"] = strdevname
        if strdevip:
            params["strdevip"] = strdevip

        data = await self._request("/software", "querysoftwarebydev", params)
        rows = data.get("rows", [])
        if not rows:
            return None

        row = rows[0] if isinstance(rows, list) else rows
        softwares = []
        for s in row.get("softwares", []):
            softwares.append(SoftwareInfo(
                name=s.get("strsoftware", ""),
                version=s.get("strversion", ""),
                vendor=s.get("strvendor", ""),
                install_date=s.get("installdate", ""),
            ))
        return TerminalSoftwareInfo(
            strdevname=row.get("strdevname", ""),
            strdevip=row.get("strdevip", ""),
            strmac=row.get("strmac", ""),
            strusername=row.get("strusername", ""),
            softwares=softwares,
        )

    # ==========================================================================
    # 测试连接
    # ==========================================================================

    async def test_connection(self) -> dict:
        """测试联软API连接。
        
        使用getToken接口验证：
        1. 网络连通性
        2. IP白名单
        3. 账号密码正确性
        4. Token获取成功
        
        Returns:
            dict: {"success": bool, "message": str}
        """
        try:
            token = await self._ensure_token()
            if token:
                return {
                    "success": True,
                    "message": "联软API连接成功，Token获取正常",
                }
            else:
                return {
                    "success": False,
                    "message": "Token获取失败，返回为空",
                }
        except LianruanAuthError as e:
            return {"success": False, "message": e.message}
        except LianruanConnectionError as e:
            return {"success": False, "message": e.message}
        except Exception as e:
            return {"success": False, "message": f"未知错误: {str(e)}"}
