#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：door_simulator.py
模块描述：门口屏模拟器，模拟门禁设备行为

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

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

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

logger = logging.getLogger(__name__)


class DoorSimulator(BaseSimulator):
    """
    门口屏模拟器

    模拟门口屏设备，支持：
    - 设备注册与状态上报
    - 门禁事件（开门/关门/超时未关/非法闯入）
    - 呼叫记录（访客呼叫、业主应答、呼叫超时）
    - 接收平台开锁指令

    Attributes:
        _door_status (str): 门状态 (open/closed)
        _battery_level (int): 电池电量 (0-100)
        _call_active (bool): 是否正在呼叫中
    """

    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):
        """
        初始化门口屏模拟器

        Args:
            device_id: 设备 ID
            env_config_id: 环境配置 ID
            mqtt_manager: MQTT 管理器
            report_config: 上报配置，如 {'interval': 30, 'enabled': True}
            topics: 多主题前缀映射
            topic_params: 主题动态参数
            token_api_host: Token 接口主机地址（启动时调用 getTokenInfoByToken）
        """
        super().__init__(device_id, env_config_id, "door", mqtt_manager,
                         report_config, topics, topic_params, token_api_host)
        self._door_status = "closed"
        self._battery_level = random.randint(60, 100)
        self._call_active = False
        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_register_payload(self) -> dict:
        """构建设备注册消息"""
        return {
            "device_id": self.device_id,
            "device_type": "door_station",
            "name": f"门口屏-{self.device_id}",
            "model": "DS-KD8003",
            "version": "V2.1.6",
            "location": f"单元门-{random.choice(['东', '南', '西', '北'])}",
            "capabilities": ["door_control", "video_call", "card_reader", "password"]
        }

    def build_status_payload(self) -> dict:
        """构建设备状态上报消息"""
        # 模拟电池缓慢下降
        self._battery_level = max(10, self._battery_level - random.randint(0, 2))
        # 偶尔模拟门状态变化
        if random.random() < 0.1:
            self._door_status = "open" if self._door_status == "closed" else "closed"

        return {
            "device_id": self.device_id,
            "status": "online",
            "door_status": self._door_status,
            "battery": self._battery_level,
            "signal": random.randint(70, 100),
            "temperature": round(random.uniform(25, 40), 1),
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
        }

    def start(self) -> bool:
        """
        启动门口屏模拟器

        流程：
        1. 调用 getTokenInfoByToken 接口获取 token
        2. 保存 token 信息并回填 topic_params
        3. 调用父类 start() 执行 MQTT 注册/订阅/上报

        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

                # 登记用户提供的 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_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}"
                )

            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_list_polling()
        else:
            logger.info(f"门口屏未配置 token 或无 authorization，跳过消息列表拉取: {self.device_id}")

        return True

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

        支持的指令：
        - unlock: 远程开锁
        - lock: 远程锁门
        - reboot: 重启设备
        - set_volume: 设置音量
        """
        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 == "unlock":
                self._door_status = "open"
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "unlock",
                    "result": "success",
                    "message": "门已打开"
                })
                # 模拟5秒后自动关门
                threading_timer = __import__('threading').Timer
                threading_timer(5.0, self._auto_close_door).start()

            elif command == "lock":
                self._door_status = "closed"
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "lock",
                    "result": "success",
                    "message": "门已锁定"
                })

            elif command == "reboot":
                self._notify_report(response_topic, {
                    "device_id": self.device_id,
                    "command": "reboot",
                    "result": "success",
                    "message": "设备正在重启"
                })
                # 模拟重启（短暂离线后恢复）
                self._simulate_reboot()

            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 _auto_close_door(self) -> None:
        """模拟自动关门"""
        self._door_status = "closed"
        if self._has_real_topics:
            publish_topics = self.get_publish_topics()
            status_topic = publish_topics[0]["topic"] if publish_topics else self.get_topic_prefix()
        else:
            status_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
        self.mqtt.publish(self.env_config_id, status_topic, {
            "device_id": self.device_id,
            "status": "online",
            "door_status": "closed",
            "event": "auto_close"
        })

    def _simulate_reboot(self) -> None:
        """模拟设备重启"""
        import threading as t
        # 发送离线
        if self._has_real_topics:
            publish_topics = self.get_publish_topics()
            offline_topic = publish_topics[0]["topic"] if publish_topics else self.get_topic_prefix()
        else:
            offline_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
        self.mqtt.publish(self.env_config_id, offline_topic, {
            "device_id": self.device_id, "status": "offline", "reason": "reboot"
        })
        # 5秒后恢复
        def _recover():
            time.sleep(5)
            if self._running:
                if self._has_real_topics:
                    publish_topics = self.get_publish_topics()
                    status_topic = publish_topics[0]["topic"] if publish_topics else self.get_topic_prefix()
                else:
                    status_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
                self.mqtt.publish(self.env_config_id, status_topic, self.build_status_payload())
        t.Thread(target=_recover, daemon=True).start()

    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_list_polling(self) -> None:
        """
        启动消息列表 + 人脸页面 + 全局配置定时拉取线程

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

        conference_id = self.topic_params.get("conference_id", "0")
        logger.info(
            f"启动消息列表/人脸页面/全局配置定时拉取: device_id={self.device_id}, "
            f"interval={self._report_interval}s, conferenceId={conference_id}"
        )

        # 立即执行一次
        self._call_message_list_once()
        self._call_face_page_once()
        self._call_ex_global_config_once()

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

    def _call_face_page_once(self) -> bool:
        """调用一次今日会议人脸页面接口，记录结果到 ReportLog"""
        if not self._token_client or not self._authorization:
            return False

        conference_id = self.topic_params.get("conference_id", "0")
        company_number = self.topic_params.get("company_id", "")
        try:
            success = self._token_client.get_today_meeting_face_page(
                authorization=self._authorization,
                conference_id=conference_id,
                company_number=company_number,
            )
            self._notify_report(
                topic=f"face_page_{'success' if success else 'failed'}",
                payload={
                    "device_id": self.device_id,
                    "conference_id": conference_id,
                    "success": success,
                },
                direction="publish",
                status="success" if success else "failed",
            )
            return success
        except Exception as e:
            logger.warning(f"[FacePage] 调用异常: device_id={self.device_id}, error={e}")
            self._notify_report(
                topic="face_page_error",
                payload={"device_id": self.device_id, "error": str(e)},
                direction="publish",
                status="failed",
                error=str(e),
            )
            return False

    def _call_ex_global_config_once(self) -> bool:
        """调用一次全局配置接口，记录结果到 ReportLog"""
        if not self._token_client or not self._authorization:
            return False

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

    def _call_message_list_once(self) -> bool:
        """调用一次消息列表接口，记录结果到 ReportLog"""
        if not self._token_client or not self._authorization:
            return False

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

    def _message_list_loop(self) -> None:
        """
        消息列表 + 人脸页面 + 全局配置定时拉取循环

        在独立线程中运行，interval 秒调用一次：
        1. _call_message_list_once() — 消息列表接口
        2. _call_face_page_once() — 今日会议人脸页面接口
        3. _call_ex_global_config_once() — 全局配置接口
        调用失败仅记日志，不影响下次定时执行。
        """
        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_list_once()
            self._call_face_page_once()
            self._call_ex_global_config_once()

        logger.info(f"消息列表/人脸页面/全局配置定时线程结束: {self.device_id}")

    def trigger_event(self, event_type: str = "random") -> bool:
        """
        触发门禁事件

        Args:
            event_type: 事件类型 (random/door_open/door_close/timeout/intrusion)

        Returns:
            bool: 是否上报成功
        """
        events = {
            "door_open": {
                "event": "door_open",
                "method": random.choice(["card", "password", "remote", "fingerprint"]),
                "card_id": f"card_{random.randint(1000, 9999)}",
                "user": f"user_{random.randint(1, 100)}"
            },
            "door_close": {
                "event": "door_close",
                "method": "auto",
                "duration_seconds": random.randint(5, 60)
            },
            "timeout": {
                "event": "door_timeout",
                "duration_seconds": random.randint(30, 120),
                "alarm": True
            },
            "intrusion": {
                "event": "illegal_intrusion",
                "alarm": True,
                "severity": "high"
            }
        }

        if event_type == "random":
            event_type = random.choice(list(events.keys()))

        payload = events.get(event_type, events["door_open"])
        payload["device_id"] = self.device_id
        payload["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S")

        return self.report("event", payload)

    def trigger_call(self, call_type: str = "random") -> bool:
        """
        触发呼叫记录

        Args:
            call_type: 呼叫类型 (random/incoming/answered/timeout/canceled)

        Returns:
            bool: 是否上报成功
        """
        calls = {
            "incoming": {
                "call_type": "incoming",
                "caller": f"visitor_{random.randint(100, 999)}",
                "room": f"{random.randint(1, 30)}-{random.randint(1, 20)}"
            },
            "answered": {
                "call_type": "answered",
                "caller": f"visitor_{random.randint(100, 999)}",
                "room": f"{random.randint(1, 30)}-{random.randint(1, 20)}",
                "duration": random.randint(10, 120)
            },
            "timeout": {
                "call_type": "timeout",
                "caller": f"visitor_{random.randint(100, 999)}",
                "room": f"{random.randint(1, 30)}-{random.randint(1, 20)}",
                "timeout_seconds": 30
            },
            "canceled": {
                "call_type": "canceled",
                "caller": f"visitor_{random.randint(100, 999)}",
                "reason": "visitor_canceled"
            }
        }

        if call_type == "random":
            call_type = random.choice(list(calls.keys()))

        payload = calls.get(call_type, calls["incoming"])
        payload["device_id"] = self.device_id
        payload["timestamp"] = time.strftime("%Y-%m-%d %H:%M:%S")

        return self.report("call", payload)