#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：base_simulator.py
模块描述：模拟设备基类，定义通用接口

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

import json
import logging
import threading
import time
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional

from app.services.mqtt_manager import MqttManager

logger = logging.getLogger(__name__)


class BaseSimulator(ABC):
    """
    模拟设备基类

    所有模拟设备类型（门口屏、无纸化、中控、集控客户端）继承此类。
    提供设备注册、定时上报、指令响应等通用功能。

    Attributes:
        device_id (str): 设备唯一标识
        env_config_id (str): 关联环境配置 ID
        device_type (str): 设备类型标识
        mqtt (MqttManager): MQTT 连接管理器
        _running (bool): 是否正在运行
        _thread (threading.Thread): 定时上报线程
        _report_interval (int): 上报间隔（秒）
        _on_report_callback (callable): 上报回调（用于记录日志）
    """

    def __init__(self, device_id: str, env_config_id: str,
                 device_type: str, mqtt_manager: MqttManager,
                 report_interval: int = 30,
                 topics: Optional[dict] = None):
        """
        初始化模拟设备

        Args:
            device_id: 设备 ID
            env_config_id: 环境配置 ID
            device_type: 设备类型
            mqtt_manager: MQTT 管理器实例
            report_interval: 上报间隔（秒）
            topics: 多主题前缀映射，key 为设备类型，value 为主题前缀
        """
        self.device_id = device_id
        self.env_config_id = env_config_id
        self.device_type = device_type
        self.mqtt = mqtt_manager
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._report_interval = report_interval
        self._on_report_callback = None
        self._lock = threading.Lock()
        self.topics = topics or {}

    def get_topic_prefix(self) -> str:
        """
        获取设备主题前缀

        优先使用环境配置中的多主题映射（topics），如果没有配置则使用默认值。

        Returns:
            str: 主题前缀，如 "device/door"
        """
        if self.topics and self.device_type in self.topics:
            return self.topics[self.device_type]
        return f"device/{self.device_type}"

    @abstractmethod
    def build_register_payload(self) -> dict:
        """
        构建设备注册消息

        Returns:
            dict: 注册消息内容
        """
        pass

    @abstractmethod
    def build_status_payload(self) -> dict:
        """
        构建设备状态上报消息

        Returns:
            dict: 状态消息内容
        """
        pass

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

        Args:
            topic: 指令主题
            payload: 指令内容
        """
        pass

    def set_report_callback(self, callback) -> None:
        """
        设置上报回调

        Args:
            callback: 回调函数 (topic, payload, direction, status, error) -> None
        """
        self._on_report_callback = callback

    def _notify_report(self, topic: str, payload: dict,
                       direction: str = "publish",
                       status: str = "success",
                       error: Optional[str] = None) -> None:
        """
        通知上报事件（触发回调记录日志）

        Args:
            topic: 主题
            payload: 内容
            direction: 方向
            status: 状态
            error: 错误信息
        """
        if self._on_report_callback:
            try:
                self._on_report_callback(
                    device_id=self.device_id,
                    topic=topic,
                    payload=payload,
                    direction=direction,
                    status=status,
                    error_message=error
                )
            except Exception as e:
                logger.error(f"上报回调异常: {e}")

    def start(self) -> bool:
        """
        启动模拟设备

        流程：
        1. 发送注册消息
        2. 订阅平台下发指令主题
        3. 开启定时上报线程

        Returns:
            bool: 是否启动成功
        """
        with self._lock:
            if self._running:
                logger.warning(f"设备已在运行: {self.device_id}")
                return False

            try:
                # 1. 发送注册消息
                register_topic = f"{self.get_topic_prefix()}/{self.device_id}/register"
                register_payload = self.build_register_payload()
                reg_success = self.mqtt.publish(self.env_config_id, register_topic, register_payload)
                self._notify_report(register_topic, register_payload,
                                    status="success" if reg_success else "failed")

                # 2. 订阅平台下发指令主题
                command_topic = f"{self.get_topic_prefix()}/{self.device_id}/command"
                sub_success = self.mqtt.subscribe(self.env_config_id, command_topic, self._on_command)
                if not sub_success:
                    logger.warning(f"订阅指令主题失败: {command_topic}")

                # 3. 发送初始状态
                status_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
                status_payload = self.build_status_payload()
                status_payload["status"] = "online"
                self.mqtt.publish(self.env_config_id, status_topic, status_payload)

                # 4. 开启定时上报线程
                self._running = True
                self._thread = threading.Thread(
                    target=self._auto_report_loop,
                    name=f"sim_{self.device_id[:8]}",
                    daemon=True
                )
                self._thread.start()

                logger.info(f"模拟设备启动成功: {self.device_id} ({self.device_type})")
                return True

            except Exception as e:
                self._running = False
                logger.error(f"模拟设备启动失败: {self.device_id}, error={e}")
                return False

    def stop(self) -> bool:
        """
        停止模拟设备

        流程：
        1. 发送离线状态
        2. 停止定时上报线程

        Returns:
            bool: 是否停止成功
        """
        with self._lock:
            if not self._running:
                return False

            try:
                self._running = False

                # 发送离线状态
                offline_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
                offline_payload = {"status": "offline", "device_id": self.device_id}
                self.mqtt.publish(self.env_config_id, offline_topic, offline_payload)
                self._notify_report(offline_topic, offline_payload, status="success")

                # 取消订阅指令主题
                command_topic = f"{self.get_topic_prefix()}/{self.device_id}/command"
                self.mqtt.unsubscribe(self.env_config_id, command_topic)

                logger.info(f"模拟设备停止成功: {self.device_id}")
                return True

            except Exception as e:
                logger.error(f"模拟设备停止失败: {self.device_id}, error={e}")
                return False

    def report(self, topic: str, payload: dict) -> bool:
        """
        手动触发上报

        Args:
            topic: 上报主题（相对路径，如 "event" 会补全为完整主题）
            payload: 上报数据

        Returns:
            bool: 是否上报成功
        """
        if not topic.startswith(self.get_topic_prefix()):
            full_topic = f"{self.get_topic_prefix()}/{self.device_id}/{topic}"
        else:
            full_topic = topic

        success = self.mqtt.publish(self.env_config_id, full_topic, payload)
        self._notify_report(full_topic, payload, status="success" if success else "failed")
        return success

    def is_running(self) -> bool:
        """
        检查设备是否在运行

        Returns:
            bool: 是否运行中
        """
        return self._running

    def _auto_report_loop(self) -> None:
        """
        定时上报循环

        在独立线程中运行，定时上报设备状态。
        """
        logger.info(f"定时上报线程启动: {self.device_id}, 间隔={self._report_interval}s")

        while self._running:
            try:
                status_topic = f"{self.get_topic_prefix()}/{self.device_id}/status"
                status_payload = self.build_status_payload()
                success = self.mqtt.publish(self.env_config_id, status_topic, status_payload)
                self._notify_report(status_topic, status_payload,
                                    status="success" if success else "failed")
            except Exception as e:
                logger.error(f"定时上报异常: {self.device_id}, error={e}")

            # 等待下次上报（分段等待，支持快速停止）
            for _ in range(self._report_interval):
                if not self._running:
                    break
                time.sleep(1)

        logger.info(f"定时上报线程结束: {self.device_id}")