//! df-relay 单元测试（任务 #12）
//!
//! 覆盖纯逻辑（不依赖 axum server）：
//! - ConnRegistry: add/remove/is_online/route（四种路由规则）
//! - BroadcastMessage 构造器
//! - RelayState async 包装
//! - next_conn_id 单调递增

#![cfg(test)]

use df_relay::{
    next_conn_id, BroadcastMessage, ClientKind, ConnHandle, ConnId, ConnRegistry, MessageKind,
    RelayState,
};
use tokio::sync::mpsc;

// ---------- ConnId 分配 ----------

#[test]
fn next_conn_id_monotonic() {
    let a = next_conn_id();
    let b = next_conn_id();
    let c = next_conn_id();
    assert!(b.0 > a.0, "next_conn_id 应单调递增");
    assert!(c.0 > b.0);
}

#[test]
fn conn_id_nil_is_zero() {
    assert_eq!(ConnId::NIL.0, 0);
    assert_ne!(next_conn_id(), ConnId::NIL, "分配的 id 不应为 NIL(0)");
}

// ---------- ConnRegistry 基础操作 ----------

fn make_handle(id: ConnId, kind: ClientKind, device_id: &str) -> (ConnHandle, mpsc::UnboundedReceiver<BroadcastMessage>) {
    let (tx, rx) = mpsc::unbounded_channel();
    (ConnHandle::new(id, kind, device_id.to_string(), tx), rx)
}

/// 注册连接并返回 receiver（保活防 channel 关闭）。
fn register(reg: &mut ConnRegistry, id: ConnId, kind: ClientKind, device_id: &str) -> mpsc::UnboundedReceiver<BroadcastMessage> {
    let (handle, rx) = make_handle(id, kind, device_id);
    reg.add(handle);
    rx
}

#[test]
fn registry_add_and_lookup() {
    let mut reg = ConnRegistry::new();
    let id = next_conn_id();
    let _rx = register(&mut reg, id, ClientKind::Device, "dev-1");

    assert_eq!(reg.len(), 1);
    assert!(reg.is_device_online("dev-1"));
    assert!(!reg.is_device_online("dev-2"));
}

#[test]
fn registry_remove_returns_device_id() {
    let mut reg = ConnRegistry::new();
    let id = next_conn_id();
    let _rx = register(&mut reg, id, ClientKind::Device, "dev-1");

    let removed = reg.remove(id);
    assert_eq!(removed.as_deref(), Some("dev-1"));
    assert_eq!(reg.len(), 0);
    assert!(!reg.is_device_online("dev-1"));
}

#[test]
fn registry_remove_unknown_returns_none() {
    let mut reg = ConnRegistry::new();
    assert_eq!(reg.remove(ConnId(9999)), None);
}

#[test]
fn registry_supports_multi_conn_per_device() {
    let mut reg = ConnRegistry::new();
    let d1 = next_conn_id();
    let m1 = next_conn_id();
    let _r1 = register(&mut reg, d1, ClientKind::Device, "dev-1");
    let _r2 = register(&mut reg, m1, ClientKind::Miniapp, "dev-1");

    assert_eq!(reg.len(), 2);
    assert!(reg.is_device_online("dev-1"));

    reg.remove(d1);
    assert!(reg.is_device_online("dev-1"));

    reg.remove(m1);
    assert!(!reg.is_device_online("dev-1"));
}

#[test]
fn registry_empty_default() {
    let reg = ConnRegistry::new();
    assert!(reg.is_empty());
    assert_eq!(reg.len(), 0);
}

// ---------- 路由规则 ----------

#[test]
fn route_event_device_to_miniapp_only() {
    let mut reg = ConnRegistry::new();
    let dev = next_conn_id();
    let mini = next_conn_id();
    let other_mini = next_conn_id();
    let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1");
    let _r2 = register(&mut reg, mini, ClientKind::Miniapp, "dev-1");
    let _r3 = register(&mut reg, other_mini, ClientKind::Miniapp, "dev-2");

    let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0);
    let delivered = reg.route(&msg);
    assert_eq!(delivered, 1, "Event 应只投递给 dev-1 的小程序");
}

#[test]
fn route_command_miniapp_to_device_only() {
    let mut reg = ConnRegistry::new();
    let dev = next_conn_id();
    let mini = next_conn_id();
    let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1");
    let _r2 = register(&mut reg, mini, ClientKind::Miniapp, "dev-1");

    let msg = BroadcastMessage {
        device_id: "dev-1".into(),
        kind: MessageKind::Command,
        source: mini,
        from: ClientKind::Miniapp,
        payload: serde_json::json!({}),
        ts: 0,
    };
    let delivered = reg.route(&msg);
    assert_eq!(delivered, 1, "Command 应只投递给 dev-1 的桌面端");
}

#[test]
fn route_does_not_loop_back_to_source() {
    let mut reg = ConnRegistry::new();
    let dev = next_conn_id();
    let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1");

    let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0);
    let delivered = reg.route(&msg);
    assert_eq!(delivered, 0, "不应回环给来源");
}

#[test]
fn route_control_to_all_except_source() {
    let mut reg = ConnRegistry::new();
    let dev = next_conn_id();
    let mini1 = next_conn_id();
    let mini2 = next_conn_id();
    let _r1 = register(&mut reg, dev, ClientKind::Device, "dev-1");
    let _r2 = register(&mut reg, mini1, ClientKind::Miniapp, "dev-1");
    let _r3 = register(&mut reg, mini2, ClientKind::Miniapp, "dev-1");

    let msg = BroadcastMessage {
        device_id: "dev-1".into(),
        kind: MessageKind::Control,
        source: dev,
        from: ClientKind::Device,
        payload: serde_json::json!({}),
        ts: 0,
    };
    let delivered = reg.route(&msg);
    assert_eq!(delivered, 2, "Control 应投递给除来源外的所有同 device 连接");
}

#[test]
fn route_unknown_device_zero() {
    let reg = ConnRegistry::new();
    let msg = BroadcastMessage::from_device("nope", ConnId::NIL, serde_json::json!({}), 0);
    assert_eq!(reg.route(&msg), 0);
}

#[test]
fn route_event_not_delivered_to_device() {
    let mut reg = ConnRegistry::new();
    let dev1 = next_conn_id();
    let dev2 = next_conn_id();
    let _r1 = register(&mut reg, dev1, ClientKind::Device, "dev-1");
    let _r2 = register(&mut reg, dev2, ClientKind::Device, "dev-1");

    let msg = BroadcastMessage::from_device("dev-1", dev1, serde_json::json!({}), 0);
    assert_eq!(reg.route(&msg), 0, "两个 Device 互发 Event 不应投递");
}

// ---------- BroadcastMessage 构造器 ----------

#[test]
fn from_device_sets_event_kind_and_from() {
    let msg = BroadcastMessage::from_device("dev-1", ConnId(42), serde_json::json!({"a": 1}), 12345);
    assert_eq!(msg.device_id, "dev-1");
    assert_eq!(msg.kind, MessageKind::Event);
    assert_eq!(msg.from, ClientKind::Device);
    assert_eq!(msg.source, ConnId(42));
    assert_eq!(msg.ts, 12345);
    assert_eq!(msg.payload, serde_json::json!({"a": 1}));
}

// ---------- RelayState async 包装 ----------

#[tokio::test]
async fn relay_state_add_and_remove_conn() {
    let state = RelayState::new();
    let id = next_conn_id();
    let (handle, _rx) = make_handle(id, ClientKind::Device, "dev-1");
    state.add_conn(handle).await;

    assert!(state.is_device_online("dev-1").await);

    let removed = state.remove_conn(id).await;
    assert_eq!(removed.as_deref(), Some("dev-1"));
    assert!(!state.is_device_online("dev-1").await);
}

#[tokio::test]
async fn relay_state_route_async() {
    let state = RelayState::new();
    let dev = next_conn_id();
    let mini = next_conn_id();
    let (h1, _r1) = make_handle(dev, ClientKind::Device, "dev-1");
    let (h2, _r2) = make_handle(mini, ClientKind::Miniapp, "dev-1");
    state.add_conn(h1).await;
    state.add_conn(h2).await;

    let msg = BroadcastMessage::from_device("dev-1", dev, serde_json::json!({}), 0);
    let n = state.route(&msg).await;
    assert_eq!(n, 1);
}
