#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：device_sim.py
模块描述：设备模拟模块 Pydantic 模式定义

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

from datetime import datetime
from typing import Optional, List, Dict
from pydantic import BaseModel, Field, ConfigDict, field_validator


def to_camel(string: str) -> str:
    """Convert snake_case to camelCase"""
    components = string.split('_')
    return components[0] + ''.join(x.title() for x in components[1:])


# ==================== 环境配置 Schema ====================

class EnvConfigBase(BaseModel):
    """环境配置基础模式"""

    name: str = Field(..., min_length=1, max_length=100, description="配置名称")
    broker_host: str = Field(..., min_length=1, max_length=255, description="Broker 地址")
    broker_port: int = Field(default=1883, ge=1, le=65535, description="端口号")
    username: Optional[str] = Field(default=None, max_length=100, description="MQTT 用户名")
    password: Optional[str] = Field(default=None, max_length=500, description="MQTT 密码")
    topic_prefix: str = Field(default="device", max_length=200, description="主题前缀（已弃用，建议使用 topics）")
    topics: Optional[Dict[str, str]] = Field(default=None, description="多主题前缀映射，key为设备类型，value为主题前缀")
    use_tls: bool = Field(default=False, description="是否启用 TLS")
    client_id_prefix: str = Field(default="sim", max_length=100, description="客户端 ID 前缀")
    default_topic_params: Optional[dict] = Field(default=None, description="默认主题参数，如 {'company_id': '001', 'room_ids': ['A101', 'A202']}")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class EnvConfigCreate(EnvConfigBase):
    """创建环境配置请求"""
    pass


class EnvConfigUpdate(BaseModel):
    """更新环境配置请求"""

    name: Optional[str] = Field(default=None, min_length=1, max_length=100, description="配置名称")
    broker_host: Optional[str] = Field(default=None, min_length=1, max_length=255, description="Broker 地址")
    broker_port: Optional[int] = Field(default=None, ge=1, le=65535, description="端口号")
    username: Optional[str] = Field(default=None, max_length=100, description="MQTT 用户名")
    password: Optional[str] = Field(default=None, max_length=500, description="MQTT 密码")
    topic_prefix: Optional[str] = Field(default=None, max_length=200, description="主题前缀（已弃用，建议使用 topics）")
    topics: Optional[Dict[str, str]] = Field(default=None, description="多主题前缀映射")
    use_tls: Optional[bool] = Field(default=None, description="是否启用 TLS")
    client_id_prefix: Optional[str] = Field(default=None, max_length=100, description="客户端 ID 前缀")
    default_topic_params: Optional[dict] = Field(default=None, description="默认主题参数")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class EnvConfigResponse(EnvConfigBase):
    """环境配置响应"""

    id: str = Field(..., description="配置 ID")
    status: str = Field(..., description="连接状态")
    last_connected_at: Optional[datetime] = Field(default=None, description="最后连接时间")
    created_at: datetime = Field(..., description="创建时间")
    updated_at: datetime = Field(..., description="更新时间")

    model_config = ConfigDict(
        from_attributes=True,
        alias_generator=to_camel,
        populate_by_name=True,
    )


class EnvConfigListResponse(BaseModel):
    """环境配置列表响应"""

    items: List[EnvConfigResponse] = Field(default=[], description="环境配置列表")
    total: int = Field(default=0, description="总数")


# ==================== 模拟设备 Schema ====================

class SimulatorBase(BaseModel):
    """模拟设备基础模式"""

    env_config_id: str = Field(..., description="关联环境配置 ID")
    device_type: str = Field(..., pattern="^(door|paperless|central|client)$", description="设备类型")
    device_name: str = Field(..., min_length=1, max_length=200, description="模拟设备名称")
    device_id: str = Field(..., min_length=1, max_length=200, description="模拟设备 ID")
    auto_reconnect: bool = Field(default=True, description="是否自动重连")
    extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性")
    topic_params: Optional[Dict[str, str]] = Field(default=None, description="主题动态参数")
    report_config: Optional[dict] = Field(default=None, description="上报配置，含 interval(秒)/enabled(开关)")

    @field_validator('report_config')
    @classmethod
    def validate_report_config(cls, v):
        """验证上报配置字段"""
        if v is None:
            return None
        if 'interval' in v:
            if not isinstance(v['interval'], int) or v['interval'] < 1:
                raise ValueError('interval must be a positive integer (>= 1)')
        if 'enabled' in v:
            if not isinstance(v['enabled'], bool):
                raise ValueError('enabled must be a boolean')
        return v

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class SimulatorCreate(SimulatorBase):
    """创建模拟设备请求"""
    pass


class SimulatorUpdate(BaseModel):
    """更新模拟设备请求"""

    device_name: Optional[str] = Field(default=None, min_length=1, max_length=200, description="模拟设备名称")
    auto_reconnect: Optional[bool] = Field(default=None, description="是否自动重连")
    extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性")
    topic_params: Optional[Dict[str, str]] = Field(default=None, description="主题动态参数")
    report_config: Optional[dict] = Field(default=None, description="上报配置，含 interval(秒)/enabled(开关)")

    @field_validator('report_config')
    @classmethod
    def validate_report_config(cls, v):
        """验证上报配置字段"""
        if v is None:
            return None
        if 'interval' in v:
            if not isinstance(v['interval'], int) or v['interval'] < 1:
                raise ValueError('interval must be a positive integer (>= 1)')
        if 'enabled' in v:
            if not isinstance(v['enabled'], bool):
                raise ValueError('enabled must be a boolean')
        return v

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class SimulatorResponse(SimulatorBase):
    """模拟设备响应"""

    id: str = Field(..., description="设备 ID")
    status: str = Field(..., description="运行状态")
    last_reported_at: Optional[datetime] = Field(default=None, description="最后上报时间")
    total_reports: int = Field(default=0, description="累计上报次数")
    created_at: datetime = Field(..., description="创建时间")
    updated_at: datetime = Field(..., description="更新时间")

    # 显式声明 metadata 字段，避免 Pydantic 尝试序列化 SQLAlchemy 的 MetaData 对象
    metadata: Optional[dict] = Field(default=None, exclude=True)

    @field_validator('metadata', mode='before')
    @classmethod
    def ignore_sqlalchemy_metadata(cls, v):
        """忽略 SQLAlchemy 的 MetaData 对象，只接受 dict 类型"""
        if not isinstance(v, dict):
            return None
        return v

    model_config = ConfigDict(
        from_attributes=True,
        alias_generator=to_camel,
        populate_by_name=True,
    )


class SimulatorListResponse(BaseModel):
    """模拟设备列表响应"""

    items: List[SimulatorResponse] = Field(default=[], description="模拟设备列表")
    total: int = Field(default=0, description="总数")


class DeviceStatsResponse(BaseModel):
    """设备统计响应"""

    total: int = Field(default=0, description="总设备数")
    running: int = Field(default=0, description="运行中设备数")
    stopped: int = Field(default=0, description="已停止设备数")
    by_type: Dict[str, dict] = Field(default={}, description="按设备类型分组统计: {type: {total, running, stopped}}")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


# ==================== 上报记录 Schema ====================

class ReportLogResponse(BaseModel):
    """上报记录响应"""

    id: str = Field(..., description="记录 ID")
    device_id: str = Field(..., description="关联设备 ID")
    topic: str = Field(..., description="MQTT 主题")
    payload: Optional[dict] = Field(default=None, description="上报数据")
    direction: str = Field(..., description="方向")
    status: str = Field(..., description="状态")
    error_message: Optional[str] = Field(default=None, description="错误信息")
    created_at: datetime = Field(..., description="创建时间")

    model_config = ConfigDict(
        from_attributes=True,
        alias_generator=to_camel,
        populate_by_name=True,
    )


class ReportLogListResponse(BaseModel):
    """上报记录列表响应"""

    items: List[ReportLogResponse] = Field(default=[], description="上报记录列表")
    total: int = Field(default=0, description="总数")


# ==================== 手动上报 Schema ====================

class ManualReportRequest(BaseModel):
    """手动上报请求"""

    topic: str = Field(..., min_length=1, max_length=500, description="上报主题")
    payload: dict = Field(default={}, description="上报数据内容")


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

class TestConnectionResponse(BaseModel):
    """连接测试响应"""

    success: bool = Field(..., description="是否成功")
    message: str = Field(default="", description="结果信息")
    latency_ms: Optional[int] = Field(default=None, alias="latencyMs", description="连接延迟(毫秒)")

    model_config = ConfigDict(
        populate_by_name=True,
    )

# ==================== 主题模板 Schema ====================

class TopicTemplateResponse(BaseModel):
    """主题模板响应"""

    key: str = Field(..., description="模板唯一标识")
    template: str = Field(..., description="主题路径模板")
    label: str = Field(..., description="中文标签")
    params: List[str] = Field(default=[], description="需要填写的动态参数名列表")
    param_labels: Dict[str, str] = Field(default={}, description="参数名 -> 中文标签映射")
    param_defaults: Dict[str, str] = Field(default={}, description="参数名 -> 默认值映射")
    direction: str = Field(..., description="方向：publish(上报) / subscribe(订阅)")
    device_type: Optional[str] = Field(default=None, description="中控设备类型标识")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class TopicTemplateListResponse(BaseModel):
    """主题模板列表响应"""

    device_type: str = Field(..., description="设备类型")
    templates: List[TopicTemplateResponse] = Field(default=[], description="主题模板列表")
    has_real_topics: bool = Field(default=False, description="是否有真实主题模板")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


# ==================== 批量操作 Schema ====================

class BatchOperationRequest(BaseModel):
    """批量操作请求"""

    device_ids: List[str] = Field(..., min_length=1, description="设备 ID 列表")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class BatchAllOperationRequest(BaseModel):
    """按设备类型全量操作请求"""

    device_type: str = Field(..., pattern="^(door|paperless|central|client)$", description="设备类型")
    env_config_id: Optional[str] = Field(default=None, description="环境配置筛选（可选）")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class BatchOperationResponse(BaseModel):
    """批量操作响应"""

    success_count: int = Field(..., description="成功数量")
    failed_count: int = Field(..., description="失败数量")
    failed_ids: List[str] = Field(default=[], description="失败的设备 ID 列表")
    message: str = Field(..., description="操作结果信息")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


# ==================== 批量导入 Schema ====================

class DeviceImportRowResult(BaseModel):
    """单行导入结果"""

    row: int = Field(..., description="行号（从2开始，1是表头）")
    device_name: str = Field(default="", description="设备名称")
    device_id: str = Field(default="", description="设备ID")
    success: bool = Field(..., description="是否成功")
    error: str = Field(default="", description="错误信息")
    created_id: Optional[str] = Field(default=None, description="创建的设备ID（成功时）")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )


class DeviceImportResponse(BaseModel):
    """设备导入响应"""

    total: int = Field(..., description="总行数")
    success_count: int = Field(..., description="成功数量")
    failed_count: int = Field(..., description="失败数量")
    results: List[DeviceImportRowResult] = Field(default=[], description="逐行结果")
    message: str = Field(..., description="汇总信息")

    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
    )
