#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：paperless_simulator.py
模块描述：无纸化会议设备模拟器

作者：czj
创建日期：2026-07-29
最后修改：2026-08-05
"""

import json
import logging
import random
import threading
import time
from datetime import datetime
from typing import Optional

from app.simulators.base_simulator import BaseSimulator
from app.services.mqtt_manager import MqttManager

logger = logging.getLogger(__name__)


class PaperlessSimulator(BaseSimulator):
    """
    无纸化会议设备模拟器

    模拟无纸化会议终端（华为平板 BAH3-W59），支持：
    - 设备注册与状态上报（rebootResponseTopic + /uams/android/broadcast）
    - 参会人员签到/签退
    - 会议文件同步状态
    - 投屏开始/结束/异常
    - 会议状态变化

    Attributes:
        _meeting_status (str): 会议状态 (idle/active/ended)
        _signed_in (bool): 是否已签到
        _file_sync_progress (int): 文件同步进度 (0-100)
        _screen_sharing (bool): 是否正在投屏
    """

    # 会议议题列表
    _AGENDA_ITEMS = [
        "会议开场致辞",
        "上季度工作总结",
        "项目进度汇报",
        "技术方案评审",
        "预算审批",
        "下季度计划制定",
        "自由讨论",
        "会议总结"
    ]

    def __init__(self, device_id: str, env_config_id: str,
                 mqtt_manager: MqttManager, report_config: Optional[dict] = None,
                 topics: Optional[dict] = None,
                 topic_params: Optional[dict] = None,
                 token_api_host: Optional[str] = None):
        super().__init__(device_id, env_config_id, "paperless", mqtt_manager,
                         report_config, topics, topic_params, token_api_host)
        self._meeting_status = "idle"
        self._signed_in = False
        self._file_sync_progress = 0
        self._screen_sharing = False
        self._current_agenda = ""
        # Token 获取相关属性
        self._token_info: Optional[dict] = None
        self._token_client: Optional[object] = None
        self._authorization: str = ""
        # 消息定时拉取控制
        self._msg_thread: Optional[threading.Thread] = None
        self._msg_running = False

    def build_reboot_response_payload(self) -> dict:
        """
        构建无纸化设备的 rebootResponseTopic 消息体

        无纸化设备与门口屏消息体有差异：
        - appName: "无纸化2.0"
        - deviceModel: "BAH3-W59"（华为平板）
        - wgtVersion: "0.0.31"
        - buildInfo: 空字符串
        - faceVersion: 空字符串
        - authCode: 格式如 "ANP-P7J-0020"
        - conferenceName: 会议室名称

        Returns:
            dict: reboot 响应消息内容
        """
        # 从 topic_params 获取动态参数（与门口屏一致，仅前缀 AND→ANP）
        conference_name = self.topic_params.get("conference_name", "测试会议室1")
        # 无纸化授权码：优先使用 paperless_token_prefix，否则将门口屏的 AND 前缀替换为 ANP
        paperless_prefix = self.topic_params.get("paperless_token_prefix", "")
        if paperless_prefix:
            # 使用环境配置中的无纸化前缀 + 序号
            token_start = int(self.topic_params.get("paperless_token_start", 1))
            token_seq = hash(self.device_id) % 10000 + token_start
            auth_code = f"{paperless_prefix}-{token_seq:04d}"
        else:
            # 回退：将门口屏的 AND 前缀替换为 ANP
            app_token = self.topic_params.get("app_token", "AND-518-0001")
            auth_code = app_token.replace("AND-", "ANP-", 1)

        # 内存和存储（华为平板 BAH3-W59 配置）
        total_mem = 3781
        free_mem = random.randint(1000, 2500)
        internal_total = 53768880128  # 约 50GB
        internal_free = random.randint(20000000000, 40000000000)

        # 系统时间
        sys_date_obj = datetime.now()
        sys_date = sys_date_obj.strftime("%a %b %d %H:%M:%S GMT+08:00 %Y")
        sys_date_str = sys_date_obj.strftime("%Y-%m-%d %H:%M:%S")

        # 运行时长（模拟）
        seed = hash(self.device_id) % 200000  # 最大约 55 小时
        hours = seed // 3600
        minutes = (seed % 3600) // 60
        seconds = seed % 60
        sys_elapsed_realtime = f"{hours}:{minutes:02d}:{seconds:02d}"

        # 传感器列表（华为平板传感器）
        sensors = [
            "accelerometer-kx021",
            "mag-mmc5603",
            "orientation",
            "als-tcs3707",
            "gravity",
            "linear Acceleration",
            "rotation Vector",
            "HALL sensor",
            "uncalibrated Magnetic Field",
            "significant Motion",
            "geomagnetic Rotation Vector",
            "phonecall sensor"
        ]

        # 构建 result 对象（无纸化设备特有格式）
        result_data = {
            "result": {
                "buildInfo": "",  # 无纸化设备此字段为空
                "faceVersion": "",  # 无纸化设备此字段为空
                "wgtVersion": "0.0.31",
                "deviceModel": "Pad-10Pro",
                "abiList": ["arm64-v8a", "armeabi-v7a", "armeabi"],
                "androidId": self.device_id,
                "appName": "无纸化2.0",
                "appPackageName": "com.ubains.local.gviewer",
                "appVersion": 414,
                "appVersionName": "1.3.414",
                "battery": round(random.uniform(0.5, 1.0), 2),
                "bluetoothEnabled": False,
                "camerasCount": 2,
                "charging": random.choice([True, False]),
                "density": 2,
                "freeAndTotalMemory": f"{free_mem}M/{total_mem}M",
                "internalAvailableSize": internal_free,
                "internalTotalSize": internal_total,
                "ipAddress": f"192.168.{random.randint(1, 254)}.{random.randint(1, 254)}",
                "macAddress": ":".join([f"{random.randint(0, 255):02X}" for _ in range(6)]),
                "networkType": "NETWORK_WIFI",
                "processCpuUsage": "ERR",  # 真实设备返回 ERR
                "resolution": "2000x1200",
                "romName": "huawei",
                "rooted": False,
                "sdkVersionCode": 29,
                "sdkVersionName": "10",
                "sysDate": sys_date,
                "sysDateStr": sys_date_str,
                "sysElapsedRealtime": sys_elapsed_realtime,
                "sysLanguage": "zh",
                "sysSupportedSensors": sensors,
                "authCode": auth_code,
                "conferenceName": conference_name
            }
        }

        # 外层消息结构
        return {
            "method": "/system/readSystemInfo",
            "clientId": self.device_id,
            "result": json.dumps(result_data, ensure_ascii=False)
        }

    def build_heartbeat_payload(self) -> dict:
        """
        构建 /uams/android/broadcast 心跳消息体

        Returns:
            dict: 心跳消息内容
        """
        return {
            "type": "heartbeat",
            "clientId": self.device_id,
            "appId": "com.ubains.local.gviewer",
            "deviceId": self.device_id
        }

    def start(self) -> bool:
        """
        启动无纸化模拟器

        流程（四步，与门口屏一致）：
        1. 调用 getTokenInfoByToken 接口获取 token
        2. 回填 topic_params（company_id / room_id / conference_id / conference_name / app_token）
        3. 重新解析主题 + 调用父类 start() 执行 MQTT 注册/订阅/上报
        4. 启动消息定时拉取线程（getMessageByAppToken，间隔 = 上报间隔）

        Returns:
            bool: 是否启动成功
        """
        # 第一步：获取 token
        token_host = self.token_api_host
        if not token_host:
            logger.warning(f"无纸化未配置 token_api_host，跳过 token 获取: {self.device_id}")
        else:
            try:
                from app.simulators.door_token_client import (
                    DoorTokenClient,
                    allocate_app_token,
                    generate_paperless_reg_udid,
                    PAPERLESS_TOKEN_API_PATH,
                )

                # 登记用户提供的 appToken（Excel 导入时提供的授权码，保证不重复）
                app_token = allocate_app_token(self.device_id, self.topic_params.get("app_token"))

                client = DoorTokenClient(
                    host=token_host,
                    device_id=self.device_id,
                    app_token=app_token,
                    token_api_path=PAPERLESS_TOKEN_API_PATH,
                    reg_udid_override=generate_paperless_reg_udid(self.device_id),
                )
                token_info = client.get_token()

                # 保存 token 客户端和授权信息（供消息定时拉取使用）
                self._token_client = client
                self._authorization = token_info.get("authorization", "")

                # 保存 token 完整信息
                self._token_info = token_info

                # 回填 topic_params
                token_data = token_info.get("token", {})
                company_number = token_data.get("companyNumber", "")
                cnum = token_data.get("cnum", "")
                conference = token_info.get("conference", {})
                conference_id = str(conference.get("conferenceId", ""))
                conference_name = conference.get("conferenceName", "")

                if company_number:
                    self.topic_params["company_id"] = company_number
                if cnum:
                    self.topic_params["room_id"] = cnum
                if conference_id:
                    self.topic_params["conference_id"] = conference_id
                if conference_name:
                    self.topic_params["conference_name"] = conference_name
                self.topic_params["app_token"] = app_token

                logger.info(
                    f"无纸化 token 获取成功: device_id={self.device_id}, "
                    f"company_id={company_number}, room_id={cnum}, "
                    f"conference_id={conference_id}, conference_name={conference_name}"
                )

            except Exception as e:
                logger.error(f"无纸化 token 获取失败: device_id={self.device_id}, error={e}")
                self._running = False
                # 将错误信息通知回调
                self._notify_report(
                    topic="token_get",
                    payload={"device_id": self.device_id, "error": str(e)},
                    direction="publish",
                    status="failed",
                    error=str(e),
                )
                return False

        # 第二步：重新解析主题（topic_params 已回填，解析出新的 MQTT 主题）
        self._resolve_topics()

        # 第三步：调用父类 MQTT 启动流程
        result = super().start()
        if not result:
            return False

        # 第四步：启动消息定时拉取（仅在 token 获取成功后有 authorization 才启动）
        if self._authorization:
            self._start_message_polling()
        else:
            logger.info(f"无纸化未配置 token 或无 authorization，跳过消息定时拉取: {self.device_id}")

        return True

    def stop(self) -> bool:
        """
        停止无纸化模拟器

        先停止消息定时拉取线程，再调用父类停止 MQTT 流程。
        """
        # 先停止消息拉取线程
        self._msg_running = False
        if self._msg_thread and self._msg_thread.is_alive():
            self._msg_thread.join(timeout=3)
            self._msg_thread = None
        return super().stop()

    def _start_message_polling(self) -> None:
        """
        启动无纸化消息定时拉取线程（getMessageByAppToken）

        间隔使用 self._report_interval（设备导入时填写的上报间隔，默认 30 秒）。
        首次调用立即执行一次，之后按间隔定时调用（与真实设备日志时间线一致）。
        """
        if not self._token_client or not self._authorization:
            return

        app_token = self.topic_params.get("app_token", "")
        logger.info(
            f"启动消息定时拉取: device_id={self.device_id}, "
            f"interval={self._report_interval}s, appToken={app_token}"
        )

        # 立即执行一次
        self._call_message_once()

        # 启动定时线程
        self._msg_running = True
        self._msg_thread = threading.Thread(
            target=self._message_loop,
            name=f"paperless_msg_{self.device_id[:8]}",
            daemon=True,
        )
        self._msg_thread.start()

    def _call_message_once(self) -> bool:
        """
        调用一次无纸化消息查询接口，记录结果到 ReportLog

        HTTP 200 即视为调用成功（业务码 A0034 会议不存在是正常业务响应）。

        Returns:
            bool: 是否调用成功（HTTP 200）
        """
        if not self._token_client or not self._authorization:
            return False

        app_token = self.topic_params.get("app_token", "")
        try:
            success = self._token_client.get_message_by_app_token(
                authorization=self._authorization,
                app_token=app_token,
            )
            self._notify_report(
                topic=f"paperless_message_{'success' if success else 'failed'}",
                payload={
                    "device_id": self.device_id,
                    "app_token": app_token,
                    "success": success,
                },
                direction="publish",
                status="success" if success else "failed",
            )
            return success
        except Exception as e:
            logger.warning(f"[PaperlessMsg] 调用异常: device_id={self.device_id}, error={e}")
            self._notify_report(
                topic="paperless_message_error",
                payload={"device_id": self.device_id, "error": str(e)},
                direction="publish",
                status="failed",
                error=str(e),
            )
            return False

    def _message_loop(self) -> None:
        """
        无纸化消息定时拉取循环

        在独立线程中运行，interval 秒调用一次 getMessageByAppToken。
        调用失败（网络错误/超时）仅记日志，不影响下次定时执行。
        """
        logger.info(f"消息定时拉取线程启动: {self.device_id}, 间隔={self._report_interval}s")
        while self._msg_running:
            for _ in range(self._report_interval):
                if not self._msg_running:
                    break
                time.sleep(1)

            if not self._msg_running:
                break

            self._call_message_once()

        logger.info(f"消息定时拉取线程结束: {self.device_id}")

    def build_register_payload(self) -> dict:
        """构建设备注册消息"""
        return {
            "device_id": self.device_id,
            "device_type": "paperless_terminal",
            "name": f"无纸化终端-{self.device_id}",
            "model": "MT-21P",
            "version": "V3.2.1",
            "screen_size": "21.5英寸",
            "resolution": "1920x1080",
            "capabilities": ["sign_in", "file_sync", "screen_share", "meeting_control"]
        }

    def build_status_payload(self) -> dict:
        """构建设备状态上报消息"""
        return {
            "device_id": self.device_id,
            "status": "online",
            "meeting_status": self._meeting_status,
            "signed_in": self._signed_in,
            "screen_sharing": self._screen_sharing,
            "cpu_usage": random.randint(20, 80),
            "memory_usage": random.randint(30, 70),
            "disk_usage": random.randint(20, 60),
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
        }

    def _on_command(self, topic: str, payload: dict) -> None:
        """
        处理平台下发的指令

        支持的指令：
        - start_meeting: 开始会议
        - end_meeting: 结束会议
        - start_screen_share: 开始投屏
        - stop_screen_share: 停止投屏
        - sync_file: 同步文件
        """
        try:
            command = payload.get("command", "")
            logger.info(f"无纸化设备收到指令: device_id={self.device_id}, command={command}")

            # 记录接收到的平台下发指令（direction=subscribe）
            command_topic = f"{self.get_topic_prefix()}/{self.device_id}/command"
            self._notify_report(
                command_topic,
                payload,
                direction="subscribe",
                status="success"
            )

            response_topic = f"{self.get_topic_prefix()}/{self.device_id}/command_response"

            if command == "start_meeting":
                self._meeting_status = "active"
                self._current_agenda = self._AGENDA_ITEMS[0]
                self._file_sync_progress = 0
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "start_meeting",
                    "result": "success",
                    "meeting_name": f"会议-{time.strftime('%Y%m%d')}"
                })

            elif command == "end_meeting":
                self._meeting_status = "ended"
                self._signed_in = False
                self._screen_sharing = False
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "end_meeting",
                    "result": "success",
                    "duration_minutes": random.randint(30, 120)
                })

            elif command == "start_screen_share":
                self._screen_sharing = True
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "start_screen_share",
                    "result": "success",
                    "share_content": self._current_agenda or "桌面"
                })

            elif command == "stop_screen_share":
                self._screen_sharing = False
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "stop_screen_share",
                    "result": "success"
                })

            elif command == "sync_file":
                # 模拟文件同步
                self._file_sync_progress = 0
                self._simulate_file_sync()
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "sync_file",
                    "result": "success",
                    "message": "文件同步已开始"
                })

            else:
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": command,
                    "result": "unknown",
                    "message": f"不支持的指令: {command}"
                })

        except Exception as e:
            logger.error(f"无纸化设备指令处理异常: {e}")

    def _simulate_file_sync(self) -> None:
        """模拟文件同步进度"""
        import threading as t

        def _sync():
            for i in range(1, 11):
                if not self._running:
                    break
                time.sleep(1)
                self._file_sync_progress = i * 10
                # 上报同步进度
                if self._has_real_topics:
                    # 使用真实主题，找 meeting_message 或第一个 publish 主题
                    topic = self.get_resolved_topic("meeting_message")
                    if not topic:
                        publish_topics = self.get_publish_topics()
                        topic = publish_topics[0]["topic"] if publish_topics else self.get_topic_prefix()
                else:
                    topic = f"{self.get_topic_prefix()}/{self.device_id}/file_sync"
                self.mqtt.publish(self.env_config_id, topic, {
                    "device_id": self.device_id,
                    "progress": self._file_sync_progress,
                    "file_name": f"meeting_materials_{time.strftime('%Y%m%d')}.pdf",
                    "file_size_mb": random.randint(5, 50),
                    "status": "syncing" if i < 10 else "completed"
                })

        t.Thread(target=_sync, daemon=True).start()

    def trigger_sign_in(self, user_name: Optional[str] = None) -> bool:
        """
        触发签到上报

        Args:
            user_name: 签到用户名称

        Returns:
            bool: 是否上报成功
        """
        self._signed_in = True
        payload = {
            "device_id": self.device_id,
            "action": "sign_in",
            "user_name": user_name or f"参会人员_{random.randint(1, 50)}",
            "department": random.choice(["研发部", "产品部", "测试部", "运维部", "市场部"]),
            "sign_time": time.strftime("%Y-%m-%d %H:%M:%S"),
            "meeting_name": f"会议-{time.strftime('%Y%m%d')}"
        }
        return self.report("signin", payload)

    def trigger_sign_out(self) -> bool:
        """触发签退上报"""
        self._signed_in = False
        payload = {
            "device_id": self.device_id,
            "action": "sign_out",
            "sign_time": time.strftime("%Y-%m-%d %H:%M:%S")
        }
        return self.report("signin", payload)

    def trigger_screen_status(self, status: str = "sharing") -> bool:
        """
        触发投屏状态上报

        Args:
            status: 投屏状态 (sharing/stopped/error)

        Returns:
            bool: 是否上报成功
        """
        payloads = {
            "sharing": {
                "device_id": self.device_id,
                "status": "sharing",
                "content": self._current_agenda or "桌面",
                "start_time": time.strftime("%Y-%m-%d %H:%M:%S")
            },
            "stopped": {
                "device_id": self.device_id,
                "status": "stopped",
                "reason": "normal",
                "duration_seconds": random.randint(60, 600)
            },
            "error": {
                "device_id": self.device_id,
                "status": "error",
                "error_code": "SCREEN_001",
                "error_message": "投屏连接中断"
            }
        }
        payload = payloads.get(status, payloads["sharing"])
        return self.report("screen", payload)