#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：device_sim.py
模块描述：设备模拟模块 API 路由

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

import logging
import json
from datetime import datetime
from typing import Optional, List

from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.schemas.device_sim import (
    EnvConfigCreate, EnvConfigUpdate, EnvConfigResponse, EnvConfigListResponse,
    SimulatorCreate, SimulatorUpdate, SimulatorResponse, SimulatorListResponse,
    ReportLogResponse, ReportLogListResponse,
    ManualReportRequest, TestConnectionResponse,
    TopicTemplateResponse, TopicTemplateListResponse,
    BatchOperationRequest, BatchOperationResponse,
)
from app.services.device_sim_service import DeviceSimService

logger = logging.getLogger(__name__)

# 创建路由实例
router = APIRouter()


def get_device_sim_service(db: AsyncSession = Depends(get_db)) -> DeviceSimService:
    """
    获取设备模拟服务实例（依赖注入）

    Args:
        db: 数据库会话

    Returns:
        DeviceSimService: 设备模拟服务实例
    """
    return DeviceSimService(db)


# ==================== 主题模板 API ====================


@router.get("/topic-templates/{device_type}", response_model=TopicTemplateListResponse)
async def get_topic_templates(device_type: str):
    """
    获取设备类型的主题模板列表

    返回该设备类型的所有主题模板，包含参数定义和默认值。
    """
    from app.simulators.topic_templates import (
        get_topic_templates as _get_topic_templates,
        has_real_topics,
    )

    templates = _get_topic_templates(device_type)
    return TopicTemplateListResponse(
        device_type=device_type,
        templates=[
            TopicTemplateResponse(
                key=t["key"],
                template=t["template"],
                label=t["label"],
                params=t["params"],
                param_labels=t["param_labels"],
                param_defaults=t["param_defaults"],
                direction=t["direction"],
            )
            for t in templates
        ],
        has_real_topics=has_real_topics(device_type),
    )


# ==================== 环境配置 API ====================


@router.get("/env-configs", response_model=EnvConfigListResponse)
async def list_env_configs(
    skip: int = Query(0, ge=0, description="跳过的记录数"),
    limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    获取环境配置列表

    支持分页查询，按创建时间倒序返回。
    """
    try:
        items, total = await service.list_env_configs(skip=skip, limit=limit)
        return EnvConfigListResponse(
            items=[EnvConfigResponse.model_validate(item) for item in items],
            total=total
        )
    except Exception as e:
        logger.error(f"获取环境配置列表失败: {e}")
        raise HTTPException(status_code=500, detail=f"获取环境配置列表失败: {str(e)}")


@router.post("/env-configs", response_model=EnvConfigResponse, status_code=201)
async def create_env_config(
    data: EnvConfigCreate,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    新增环境配置

    创建 MQTT Broker 连接配置。
    """
    try:
        config = await service.create_env_config(data)
        return EnvConfigResponse.model_validate(config)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"创建环境配置失败: {e}")
        raise HTTPException(status_code=500, detail=f"创建环境配置失败: {str(e)}")


@router.get("/env-configs/{config_id}", response_model=EnvConfigResponse)
async def get_env_config(
    config_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    获取环境配置详情
    """
    config = await service.get_env_config(config_id)
    if not config:
        raise HTTPException(status_code=404, detail="环境配置不存在")
    return EnvConfigResponse.model_validate(config)


@router.put("/env-configs/{config_id}", response_model=EnvConfigResponse)
async def update_env_config(
    config_id: str,
    data: EnvConfigUpdate,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    更新环境配置
    """
    config = await service.update_env_config(config_id, data)
    if not config:
        raise HTTPException(status_code=404, detail="环境配置不存在")
    return EnvConfigResponse.model_validate(config)


@router.delete("/env-configs/{config_id}")
async def delete_env_config(
    config_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    删除环境配置

    有关联模拟设备时禁止删除。
    """
    try:
        success = await service.delete_env_config(config_id)
        if not success:
            raise HTTPException(status_code=404, detail="环境配置不存在")
        return {"message": "环境配置已删除", "id": config_id}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"删除环境配置失败: {e}")
        raise HTTPException(status_code=500, detail=f"删除环境配置失败: {str(e)}")


@router.post("/env-configs/{config_id}/test", response_model=TestConnectionResponse)
async def test_env_connection(
    config_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    测试 MQTT 连接

    测试指定环境配置的 MQTT Broker 连接是否正常。
    """
    try:
        result = await service.test_env_connection(config_id)
        return result
    except Exception as e:
        logger.error(f"测试连接失败: {e}")
        raise HTTPException(status_code=500, detail=f"测试连接失败: {str(e)}")


# ==================== 模拟设备 API ====================


@router.get("/devices", response_model=SimulatorListResponse)
async def list_simulators(
    skip: int = Query(0, ge=0, description="跳过的记录数"),
    limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
    device_type: Optional[str] = Query(None, description="设备类型筛选"),
    env_config_id: Optional[str] = Query(None, description="环境配置筛选"),
    status: Optional[str] = Query(None, description="状态筛选"),
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    获取模拟设备列表

    支持按设备类型、环境配置、状态筛选。
    """
    try:
        items, total = await service.list_simulators(
            skip=skip, limit=limit,
            device_type=device_type,
            env_config_id=env_config_id,
            status=status,
        )
        return SimulatorListResponse(
            items=[SimulatorResponse.model_validate(item) for item in items],
            total=total
        )
    except Exception as e:
        logger.error(f"获取模拟设备列表失败: {e}")
        raise HTTPException(status_code=500, detail=f"获取模拟设备列表失败: {str(e)}")


@router.post("/devices", response_model=SimulatorResponse, status_code=201)
async def create_simulator(
    data: SimulatorCreate,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    创建模拟设备

    支持 5 种设备类型：door(门口屏)、paperless(无纸化)、central(中控)、client(集控客户端)
    """
    try:
        device = await service.create_simulator(data)
        return SimulatorResponse.model_validate(device)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"创建模拟设备失败: {e}")
        raise HTTPException(status_code=500, detail=f"创建模拟设备失败: {str(e)}")


# ==================== 批量操作 API ====================
# 注意：批量操作路由必须放在 /devices/{device_id} 之前，避免 batch 被当作 device_id


@router.post("/devices/batch/start", response_model=BatchOperationResponse)
async def batch_start_simulators(
    data: BatchOperationRequest,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    批量启动模拟设备

    批量启动多个设备，返回成功/失败数量。
    """
    try:
        result = await service.batch_start_simulators(data.device_ids)
        return BatchOperationResponse(**result)
    except Exception as e:
        logger.error(f"批量启动失败: {e}")
        raise HTTPException(status_code=500, detail=f"批量启动失败: {str(e)}")


@router.post("/devices/batch/stop", response_model=BatchOperationResponse)
async def batch_stop_simulators(
    data: BatchOperationRequest,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    批量停止模拟设备

    批量停止多个设备，返回成功/失败数量。
    """
    try:
        result = await service.batch_stop_simulators(data.device_ids)
        return BatchOperationResponse(**result)
    except Exception as e:
        logger.error(f"批量停止失败: {e}")
        raise HTTPException(status_code=500, detail=f"批量停止失败: {str(e)}")


@router.delete("/devices/batch", response_model=BatchOperationResponse)
async def batch_delete_simulators(
    data: BatchOperationRequest,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    批量删除模拟设备

    批量删除多个设备，返回成功/失败数量。
    """
    try:
        result = await service.batch_delete_simulators(data.device_ids)
        return BatchOperationResponse(**result)
    except Exception as e:
        logger.error(f"批量删除失败: {e}")
        raise HTTPException(status_code=500, detail=f"批量删除失败: {str(e)}")


# ==================== 单设备操作 API ====================


@router.get("/devices/{device_id}", response_model=SimulatorResponse)
async def get_simulator(
    device_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    获取模拟设备详情
    """
    device = await service.get_simulator(device_id)
    if not device:
        raise HTTPException(status_code=404, detail="模拟设备不存在")
    return SimulatorResponse.model_validate(device)


@router.put("/devices/{device_id}", response_model=SimulatorResponse)
async def update_simulator(
    device_id: str,
    data: SimulatorUpdate,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    更新模拟设备配置
    """
    device = await service.update_simulator(device_id, data)
    if not device:
        raise HTTPException(status_code=404, detail="模拟设备不存在")
    return SimulatorResponse.model_validate(device)


@router.delete("/devices/{device_id}")
async def delete_simulator(
    device_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    删除模拟设备

    会先停止设备，再删除关联上报记录。
    """
    try:
        success = await service.delete_simulator(device_id)
        if not success:
            raise HTTPException(status_code=404, detail="模拟设备不存在")
        return {"message": "模拟设备已删除", "id": device_id}
    except Exception as e:
        logger.error(f"删除模拟设备失败: {e}")
        raise HTTPException(status_code=500, detail=f"删除模拟设备失败: {str(e)}")


@router.post("/devices/{device_id}/start")
async def start_simulator(
    device_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    启动模拟设备

    连接 MQTT Broker，发送注册消息，开始定时上报。
    """
    try:
        success = await service.start_simulator(device_id)
        return {"message": "模拟设备已启动", "id": device_id, "status": "running"}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except ConnectionError as e:
        raise HTTPException(status_code=502, detail=str(e))
    except Exception as e:
        logger.error(f"启动模拟设备失败: {e}")
        raise HTTPException(status_code=500, detail=f"启动模拟设备失败: {str(e)}")


@router.post("/devices/{device_id}/stop")
async def stop_simulator(
    device_id: str,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    停止模拟设备

    发送离线状态消息，停止定时上报。
    """
    try:
        success = await service.stop_simulator(device_id)
        return {"message": "模拟设备已停止", "id": device_id, "status": "stopped"}
    except Exception as e:
        logger.error(f"停止模拟设备失败: {e}")
        raise HTTPException(status_code=500, detail=f"停止模拟设备失败: {str(e)}")


@router.post("/devices/{device_id}/report")
async def manual_report(
    device_id: str,
    data: ManualReportRequest,
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    手动触发上报

    指定主题和数据内容，手动触发设备上报。
    """
    try:
        success = await service.manual_report(device_id, data.topic, data.payload)
        return {"message": "上报成功", "success": success}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"手动上报失败: {e}")
        raise HTTPException(status_code=500, detail=f"手动上报失败: {str(e)}")


# ==================== 上报记录 API ====================


@router.get("/report-logs", response_model=ReportLogListResponse)
async def list_report_logs(
    device_id: Optional[str] = Query(None, description="设备 ID 筛选"),
    direction: Optional[str] = Query(None, description="方向筛选：publish(上报)/subscribe(接收)"),
    skip: int = Query(0, ge=0, description="跳过的记录数"),
    limit: int = Query(100, ge=1, le=500, description="返回记录数上限"),
    start_time: Optional[datetime] = Query(None, description="开始时间"),
    end_time: Optional[datetime] = Query(None, description="结束时间"),
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    获取上报记录列表

    支持按设备、方向、时间范围筛选和分页。
    """
    try:
        items, total = await service.list_report_logs(
            device_id=device_id,
            direction=direction,
            skip=skip,
            limit=limit,
            start_time=start_time,
            end_time=end_time,
        )
        return ReportLogListResponse(
            items=[ReportLogResponse.model_validate(item) for item in items],
            total=total
        )
    except Exception as e:
        logger.error(f"获取上报记录失败: {e}")
        raise HTTPException(status_code=500, detail=f"获取上报记录失败: {str(e)}")


@router.delete("/report-logs/cleanup")
async def cleanup_report_logs(
    days: int = Query(7, ge=1, le=30, description="保留天数"),
    service: DeviceSimService = Depends(get_device_sim_service)
):
    """
    清理过期上报记录

    默认保留 7 天，可配置保留天数（1-30天）。
    """
    try:
        count = await service.cleanup_report_logs(days=days)
        return {"message": f"已清理 {count} 条过期记录", "cleaned_count": count}
    except Exception as e:
        logger.error(f"清理上报记录失败: {e}")
        raise HTTPException(status_code=500, detail=f"清理上报记录失败: {str(e)}")


# ==================== WebSocket 实时消息推送 ====================

# 设备模拟消息 WebSocket 连接管理
_device_ws_connections: List[WebSocket] = []


async def _broadcast_device_message(message: dict):
    """向所有连接的 WebSocket 客户端广播设备消息"""
    if not _device_ws_connections:
        return

    message_str = json.dumps(message, ensure_ascii=False)
    disconnected = []

    for ws in _device_ws_connections:
        try:
            await ws.send_text(message_str)
        except Exception as e:
            logger.warning(f"WebSocket 发送失败: {e}")
            disconnected.append(ws)

    # 清理断开的连接
    for ws in disconnected:
        if ws in _device_ws_connections:
            _device_ws_connections.remove(ws)


@router.websocket("/ws/messages")
async def websocket_device_messages(websocket: WebSocket):
    """
    设备消息实时推送 WebSocket 端点

    连接后，客户端会实时收到所有设备的上报和下发消息。

    消息格式：
    {
        "event": "device_message",
        "data": {
            "device_id": "sim_xxx",
            "device_name": "门口屏-001",
            "device_type": "door",
            "topic": "rebootResponseTopic",
            "direction": "publish",
            "payload": {...},
            "timestamp": "2026-08-05T14:32:01"
        }
    }
    """
    await websocket.accept()
    _device_ws_connections.append(websocket)
    logger.info(f"WebSocket 连接已建立，当前连接数: {len(_device_ws_connections)}")

    try:
        # 发送连接确认
        await websocket.send_text(json.dumps({
            "event": "connected",
            "message": "已连接到设备消息流"
        }))

        # 保持连接，等待客户端消息或断开
        while True:
            data = await websocket.receive_text()
            # 可以处理客户端发来的订阅过滤等命令
            try:
                msg = json.loads(data)
                if msg.get("type") == "ping":
                    await websocket.send_text(json.dumps({"event": "pong"}))
            except json.JSONDecodeError:
                pass

    except WebSocketDisconnect:
        logger.info("WebSocket 客户端断开连接")
    except Exception as e:
        logger.error(f"WebSocket 异常: {e}")
    finally:
        if websocket in _device_ws_connections:
            _device_ws_connections.remove(websocket)
        logger.info(f"WebSocket 连接已关闭，当前连接数: {len(_device_ws_connections)}")


# 注册消息推送回调到 service 层
def _register_ws_callback():
    """注册 WebSocket 推送回调到 device_sim_service"""
    from app.services.device_sim_service import set_message_callback

    async def _on_device_message(device_id: str, device_name: str, device_type: str,
                                  topic: str, direction: str, payload: dict):
        """设备消息回调"""
        await _broadcast_device_message({
            "event": "device_message",
            "data": {
                "device_id": device_id,
                "device_name": device_name,
                "device_type": device_type,
                "topic": topic,
                "direction": direction,
                "payload": payload,
                "timestamp": datetime.now().isoformat()
            }
        })

    set_message_callback(_on_device_message)


# 在模块加载时注册回调
_register_ws_callback()