//! 设备指令下发 — 通过 Redis 队列向设备发送指令
//!
//! API 服务不直接持有 TCP 连接，而是通过 Redis LIST `device:cmd:{imei}`
//! 推送指令，由设备服务消费并发送到设备。指令响应通过 `device:reply:{imei}`
//! 队列返回，API 服务通过 `PendingCommands` 匹配 msg_id 完成异步等待。

use std::collections::HashMap;
use std::sync::Arc;
use axum::extract::{Path, State};
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use tokio::sync::{oneshot, Mutex};

use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};
use crate::protocol::ServerResponse;

/// msg_id → oneshot Sender 映射表
#[derive(Clone)]
pub struct PendingCommands {
    inner: Arc<Mutex<HashMap<u32, oneshot::Sender<Value>>>>,
}

impl PendingCommands {
    pub fn new() -> Self {
        Self { inner: Arc::new(Mutex::new(HashMap::new())) }
    }

    /// 注册等待中的指令
    #[allow(dead_code)]
    pub async fn insert(&self, msg_id: u32, tx: oneshot::Sender<Value>) {
        self.inner.lock().await.insert(msg_id, tx);
    }

    /// 移除并返回指定 msg_id 的 sender（消费一次）
    pub async fn remove(&self, msg_id: u32) -> Option<oneshot::Sender<Value>> {
        self.inner.lock().await.remove(&msg_id)
    }
}

/// 设备服务节点信息
#[derive(Debug, Clone)]
pub struct NodeInfo {
    pub node_id: String,
    pub ip: String,
    pub tcp_port: u16,
    /// RFC3339 格式的最后心跳时间，从 Redis 读取
    pub last_heartbeat: Option<String>,
}

/// 节点注册表（Redis 存储，API 服务从 Redis 读取节点信息）
///
/// 设备服务通过 HSET 写入 `node:{node_id}` hash，
/// API 服务通过此模块读取。Redis 是节点信息的单一数据源。
#[derive(Clone)]
pub struct NodeRegistry {
    redis: redis::aio::ConnectionManager,
}

impl NodeRegistry {
    pub fn new(redis: redis::aio::ConnectionManager) -> Self {
        Self { redis }
    }

    fn node_key(node_id: &str) -> String {
        format!("node:{}", node_id)
    }

    /// 注册节点（写入 Redis HSET）
    pub async fn register(&self, node_id: &str, ip: &str, tcp_port: u16) {
        let key = Self::node_key(node_id);
        let now = chrono::Utc::now().to_rfc3339();
        let mut conn = self.redis.clone();

        let _: Result<(), _> = redis::cmd("HSET")
            .arg(&key)
            .arg("node_id").arg(node_id)
            .arg("ip").arg(ip)
            .arg("tcp_port").arg(tcp_port.to_string())
            .arg("status").arg("online")
            .arg("last_heartbeat").arg(&now)
            .query_async(&mut conn)
            .await;

        // ponytail: HSET 不支持 EX，用 EXPIRE 设置 TTL
        let _: Result<(), _> = redis::cmd("EXPIRE")
            .arg(&key)
            .arg(300_u64)
            .query_async(&mut conn)
            .await;
    }

    /// 移除节点（从 Redis 删除）
    pub async fn remove(&self, node_id: &str) {
        let key = Self::node_key(node_id);
        let mut conn = self.redis.clone();
        let _: Result<(), _> = redis::cmd("DEL")
            .arg(&key)
            .query_async(&mut conn)
            .await;
    }

    /// 更新心跳时间戳
    pub async fn heartbeat(&self, node_id: &str) -> bool {
        let key = Self::node_key(node_id);
        let mut conn = self.redis.clone();
        let result: Result<(), _> = redis::cmd("HSET")
            .arg(&key)
            .arg("last_heartbeat")
            .arg(chrono::Utc::now().to_rfc3339())
            .query_async(&mut conn)
            .await;

        // 心跳同时刷新 TTL
        let _: Result<(), _> = redis::cmd("EXPIRE")
            .arg(&key)
            .arg(300_u64)
            .query_async(&mut conn)
            .await;

        result.is_ok()
    }

    /// 列出所有节点（从 Redis 读取，使用 SCAN 替代 KEYS 避免阻塞）
    pub async fn list(&self) -> Vec<NodeInfo> {
        let mut conn = self.redis.clone();
        let keys = self.scan_keys(&mut conn, "node:*").await;

        let mut nodes = Vec::with_capacity(keys.len());
        for key in keys {
            let mut conn = self.redis.clone();
            let info: HashMap<String, String> = redis::cmd("HGETALL")
                .arg(&key)
                .query_async(&mut conn)
                .await
                .unwrap_or_default();

            let node_id = info.get("node_id").cloned().unwrap_or_default();
            if node_id.is_empty() {
                continue; // ponytail: 跳过空记录
            }

            nodes.push(NodeInfo {
                node_id,
                ip: info.get("ip").cloned().unwrap_or_default(),
                tcp_port: info.get("tcp_port").and_then(|p| p.parse().ok()).unwrap_or(0),
                last_heartbeat: info.get("last_heartbeat").cloned(),
            });
        }
        nodes
    }

    /// 使用 SCAN 命令安全地获取所有匹配模式的键（避免 KEYS 阻塞）
    async fn scan_keys(&self, conn: &mut redis::aio::ConnectionManager, pattern: &str) -> Vec<String> {
        let mut keys = Vec::new();
        let mut cursor = 0i64;
        loop {
            let (next_cursor, batch): (i64, Vec<String>) = redis::cmd("SCAN")
                .arg(cursor)
                .arg("MATCH")
                .arg(pattern)
                .arg("COUNT")
                .arg(100)
                .query_async(conn)
                .await
                .unwrap_or_default();
            keys.extend(batch);
            if next_cursor == 0 {
                break;
            }
            cursor = next_cursor;
        }
        keys
    }

    /// 检查设备是否在线（通过 Redis 中的在线标记）
    pub async fn is_device_online(&self, imei: &str) -> bool {
        let key = format!("device:online:{}", imei);
        let mut conn = self.redis.clone();
        let exists: Result<bool, _> = redis::cmd("EXISTS")
            .arg(&key)
            .query_async(&mut conn)
            .await;
        exists.unwrap_or(false)
    }
}

/// 应用共享状态
#[derive(Clone)]
#[allow(dead_code)]
pub struct AppState {
    pub redis: redis::aio::ConnectionManager,
    pub mysql: sqlx::MySqlPool,
    pub pending_commands: PendingCommands,
    pub nodes: NodeRegistry,
}

/// 指令下发请求体
#[derive(Debug, Deserialize)]
pub struct CommandRequest {
    pub act: String,
    #[serde(default)]
    pub msg_id: u32,
    pub sub_device_id: Option<u8>,
    #[serde(flatten)]
    pub params: Option<Value>,
}

/// 设备指令结构（序列化后推入 Redis）
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DeviceCommand {
    pub act: String,
    pub msg_id: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dev_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sub_device_id: Option<u8>,
    /// 扩展参数（开门指令、充电参数等）
    #[serde(flatten)]
    pub params: Option<Value>,
}

impl DeviceCommand {
    /// 序列化为 JSON 字符串（紧凑格式）。
    pub fn to_json(&self) -> String {
        serde_json::to_string(self).expect("DeviceCommand 序列化不应失败")
    }
}

/// 向设备发送指令（通过 Redis 队列）
pub async fn send_device_command(
    redis: &redis::aio::ConnectionManager,
    _dev_id: &str,
    cmd: &DeviceCommand,
) -> Result<(), String> {
    let json_str = cmd.to_json();
    let mut conn = redis.clone();
    redis::cmd("LPUSH")
        .arg("device:commands")
        .arg(&json_str)
        .query_async::<()>(&mut conn)
        .await
        .map_err(|e| format!("Redis 推入失败: {}", e))?;
    Ok(())
}

/// 向设备发送响应（通过 Redis 队列）
#[allow(dead_code)]
pub async fn send_device_response(
    redis: &redis::aio::ConnectionManager,
    _dev_id: &str,
    resp: &ServerResponse,
) -> Result<(), String> {
    let json_str = resp.to_json();
    let mut conn = redis.clone();
    redis::cmd("LPUSH")
        .arg("device:commands")
        .arg(&json_str)
        .query_async::<()>(&mut conn)
        .await
        .map_err(|e| format!("Redis 推入失败: {}", e))?;
    Ok(())
}

/// POST /api/devices/:dev_id/command — 向指定设备下发指令
pub async fn send_command(
    user: CurrentUser,
    State(state): State<AppState>,
    Path(dev_id): Path<String>,
    Json(req): Json<CommandRequest>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "device:operate")?;

    let cmd = DeviceCommand {
        act: req.act,
        msg_id: req.msg_id,
        dev_id: Some(dev_id.clone()),
        sub_device_id: req.sub_device_id,
        params: req.params,
    };

    send_device_command(&state.redis, &dev_id, &cmd)
        .await
        .map_err(AppError::Internal)?;

    tracing::info!("[command] 已推入队列 dev_id={} act={}", dev_id, cmd.act);
    Ok(Json(json!({ "suc": 1 })))
}

/// GET /api/devices/stats — 在线设备统计
pub async fn device_stats(
    user: CurrentUser,
    State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "device:view")?;
    let nodes = state.nodes.list().await;
    Ok(Json(json!({
        "node_count": nodes.len(),
    })))
}
