//! 设备上报处理协程
//!
//! 从 Redis LIST `device:reports` 读取设备上报消息，
//! 执行业务逻辑：签名验证、状态持久化、充电事件处理、告警判断等。
//! 需要回复设备的消息通过 `device:commands` 队列发回。

use rand::Rng;
use redis::aio::ConnectionManager;
use sha2::{Digest, Sha256};
use sqlx::MySqlPool;
use std::collections::HashMap;
use std::sync::LazyLock;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;

use crate::protocol::ServerResponse;

/// Redis key 前缀
const REDIS_KEY_AUTH_STR: &str = "auth_str:";
/// 签名截取范围
const SIGN_START: usize = 56;
const SIGN_END: usize = 63;
/// 签名校验窗口：±60 秒
const TIMESTAMP_TOLERANCE_SECS: i64 = 60;

// ---------------------------------------------------------------------------
// 频率限制器
// ---------------------------------------------------------------------------

struct RateLimiter {
    attempts: Mutex<HashMap<String, Vec<Instant>>>,
    max_attempts: usize,
    window: Duration,
}

impl RateLimiter {
    fn new(max_attempts: usize, window: Duration) -> Self {
        Self { attempts: Mutex::new(HashMap::new()), max_attempts, window }
    }

    async fn check(&self, key: &str) -> bool {
        let mut map = self.attempts.lock().await;
        let now = Instant::now();
        let window_start = now - self.window;
        let entry = map.entry(key.to_string()).or_default();
        entry.retain(|t| *t > window_start);
        if entry.len() >= self.max_attempts {
            return false;
        }
        entry.push(now);
        true
    }
}

/// 全局 auth_str 频率限制器（5次/5分钟）
static AUTH_STR_LIMITER: LazyLock<RateLimiter> = LazyLock::new(|| {
    RateLimiter::new(5, Duration::from_secs(300))
});

/// 启动上报处理协程
pub async fn start(redis: ConnectionManager, mysql: MySqlPool) {
    tracing::info!("[report_worker] 启动");

    loop {
        let mut conn = redis.clone();
        let result: Result<(String, String), redis::RedisError> = redis::cmd("BRPOP")
            .arg("device:reports")
            .arg(0)
            .query_async(&mut conn)
            .await;

        match result {
            Ok((_key, msg_json)) => {
                let mysql = mysql.clone();
                let redis = redis.clone();
                tokio::spawn(async move {
                    if let Err(e) = process_report(msg_json, &mysql, &redis).await {
                        tracing::error!("[report_worker] 处理失败: {}", e);
                    }
                });
            }
            Err(e) => {
                tracing::error!("[report_worker] BRPOP 错误: {}", e);
                tokio::time::sleep(Duration::from_secs(1)).await;
            }
        }
    }
}

/// 处理单条设备上报
async fn process_report(
    msg_json: String,
    mysql: &MySqlPool,
    redis: &ConnectionManager,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let msg: serde_json::Value = serde_json::from_str(&msg_json)?;
    let act = msg["act"].as_str().unwrap_or("unknown");
    let dev_id = msg["dev_id"].as_str().unwrap_or("");

    tracing::debug!("[report] 处理 dev_id={} act={}", dev_id, act);

    match act {
        "auth_str" => handle_auth_str(&msg, dev_id, redis).await?,
        "login" => handle_login(&msg, dev_id, redis).await?,
        "status_post" => handle_status_post(&msg, dev_id, mysql, redis).await?,
        "pow_fail" => handle_pow_fail(&msg, dev_id, mysql).await?,
        "bat_in" => handle_bat_in(&msg, dev_id, mysql).await?,
        "bat_out" => handle_bat_out(&msg, dev_id, mysql).await?,
        "off" => handle_device_off(&msg, dev_id, mysql).await?,
        other => {
            tracing::warn!("[report] 未知上报类型 dev_id={} act={}", dev_id, other);
        }
    }

    Ok(())
}

/// 向设备发送响应（通过 Redis commands 队列）
async fn send_response_to_device(
    redis: &ConnectionManager,
    dev_id: &str,
    resp: &ServerResponse,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    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?;
    tracing::debug!("[响应] 已推入队列 dev_id={}", dev_id);
    Ok(())
}

/// 处理 `auth_str`：生成 8 位随机安全码并存入 Redis
async fn handle_auth_str(
    msg: &serde_json::Value,
    dev_id: &str,
    redis: &ConnectionManager,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let msg_id = msg["msg_id"].as_u64().map(|v| v as u32);

    if !AUTH_STR_LIMITER.check(dev_id).await {
        tracing::warn!("[auth_str] 频率超限 dev_id={}", dev_id);
        let resp = ServerResponse::error(msg_id, "请求过于频繁，请稍后再试");
        send_response_to_device(redis, dev_id, &resp).await?;
        return Ok(());
    }

    let auth_str: String = rand::thread_rng()
        .sample_iter(&rand::distributions::Alphanumeric)
        .take(8)
        .map(char::from)
        .collect();

    let key = format!("{REDIS_KEY_AUTH_STR}{dev_id}");
    let mut conn = redis.clone();
    let _: Result<(), _> = redis::cmd("SET")
        .arg(&key)
        .arg(&auth_str)
        .query_async::<()>(&mut conn)
        .await;

    tracing::info!("[auth_str] 已生成安全码 dev_id={}", dev_id);

    let mut resp = ServerResponse::success(msg_id);
    resp.auth_str = Some(auth_str);
    send_response_to_device(redis, dev_id, &resp).await?;

    Ok(())
}

/// 处理 `login`：验证签名
async fn handle_login(
    msg: &serde_json::Value,
    dev_id: &str,
    redis: &ConnectionManager,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let msg_id = msg["msg_id"].as_u64().map(|v| v as u32);
    let timestamp = msg["timestamp"].as_u64().unwrap_or(0);
    let sign = msg["sign"].as_str().unwrap_or("");

    // 校验时间窗口 ±60 秒
    let now = chrono::Utc::now().timestamp();
    let ts = timestamp as i64;
    if (now - ts).abs() > TIMESTAMP_TOLERANCE_SECS {
        tracing::warn!("[login] 时间戳超限 dev_id={} timestamp={}", dev_id, timestamp);
        let resp = ServerResponse::error(msg_id, "时间戳无效");
        send_response_to_device(redis, dev_id, &resp).await?;
        return Ok(());
    }

    // 从 Redis 获取安全码
    let key = format!("{REDIS_KEY_AUTH_STR}{dev_id}");
    let mut conn = redis.clone();
    let auth_str: Option<String> = redis::cmd("GET")
        .arg(&key)
        .query_async(&mut conn)
        .await
        .unwrap_or(None);

    let auth_str = match auth_str {
        Some(s) => s,
        None => {
            let resp = ServerResponse::error(msg_id, "未获取安全码，请先调用 auth_str");
            send_response_to_device(redis, dev_id, &resp).await?;
            return Ok(());
        }
    };

    // 计算期望签名
    let input = format!("{}{}{}", dev_id, auth_str, timestamp);
    let hash = Sha256::digest(input.as_bytes());
    let hex_hash = hex::encode(hash);
    let expected_sign = &hex_hash[SIGN_START..SIGN_END];

    if sign == expected_sign {
        // 标记设备在线（5分钟 TTL）
        let online_key = format!("device:online:{}", dev_id);
        let mut conn = redis.clone();
        let _: Result<(), _> = redis::cmd("SET")
            .arg(&online_key)
            .arg("1")
            .arg("EX")
            .arg(300_u32)
            .query_async::<()>(&mut conn)
            .await;

        tracing::info!("[login] 成功 dev_id={}", dev_id);
        let resp = ServerResponse::success(msg_id);
        send_response_to_device(redis, dev_id, &resp).await?;
    } else {
        tracing::warn!("[login] 签名不匹配 dev_id={}", dev_id);
        let resp = ServerResponse::error(msg_id, "签名错误");
        send_response_to_device(redis, dev_id, &resp).await?;
    }

    Ok(())
}

/// 处理状态上报 — 持久化到 MySQL device_logs
async fn handle_status_post(
    msg: &serde_json::Value,
    dev_id: &str,
    mysql: &MySqlPool,
    redis: &ConnectionManager,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // 存入 Redis 实时状态
    let redis_key = format!("device:status:{}", dev_id);
    let mut conn = redis.clone();
    let _: Result<(), _> = redis::cmd("SET")
        .arg(&redis_key)
        .arg(serde_json::to_string(msg).unwrap_or_default())
        .query_async::<()>(&mut conn)
        .await;

    // 更新设备在线标记（5分钟 TTL）
    let online_key = format!("device:online:{}", dev_id);
    let _: Result<(), _> = redis::cmd("SET")
        .arg(&online_key)
        .arg("1")
        .arg("EX")
        .arg(300_u32)
        .query_async::<()>(&mut conn)
        .await;

    // 持久化到 MySQL
    let content = serde_json::json!({
        "dev_id": dev_id,
        "sub_device_id": msg["sub_device_id"],
        "status_hex": msg["status"],
        "rssi": msg["rssi"],
        "timestamp": msg["timestamp"],
    });

    sqlx::query(
        "INSERT INTO device_logs (cabinet_id, log_type, content) VALUES (
            (SELECT id FROM cabinets WHERE imei = ?), 1, ?
        )",
    )
    .bind(dev_id)
    .bind(content.to_string())
    .execute(mysql)
    .await?;

    tracing::debug!("[status_post] 已持久化 dev_id={}", dev_id);
    Ok(())
}

/// 处理停电上报
async fn handle_pow_fail(
    msg: &serde_json::Value,
    dev_id: &str,
    mysql: &MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let content = serde_json::json!({
        "dev_id": dev_id,
        "timestamp": msg["timestamp"],
        "sub_device_id": msg["sub_device_id"],
    });

    sqlx::query(
        "INSERT INTO device_logs (cabinet_id, log_type, content) VALUES (
            (SELECT id FROM cabinets WHERE imei = ?), 4, ?
        )",
    )
    .bind(dev_id)
    .bind(content.to_string())
    .execute(mysql)
    .await?;

    tracing::info!("[pow_fail] 停电上报 dev_id={}", dev_id);
    Ok(())
}

/// 处理电池插入
async fn handle_bat_in(
    msg: &serde_json::Value,
    dev_id: &str,
    mysql: &MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tracing::info!("[bat_in] 电池插入 dev_id={}", dev_id);

    let content = serde_json::json!({
        "dev_id": dev_id,
        "id": msg["id"],
        "timestamp": msg["timestamp"],
        "bms": msg.get("bms"),
    });

    sqlx::query(
        "INSERT INTO device_logs (cabinet_id, log_type, content) VALUES (
            (SELECT id FROM cabinets WHERE imei = ?), 2, ?
        )",
    )
    .bind(dev_id)
    .bind(content.to_string())
    .execute(mysql)
    .await?;

    Ok(())
}

/// 处理电池拔出
async fn handle_bat_out(
    msg: &serde_json::Value,
    dev_id: &str,
    mysql: &MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tracing::info!("[bat_out] 电池拔出 dev_id={}", dev_id);

    let content = serde_json::json!({
        "dev_id": dev_id,
        "id": msg["id"],
        "timestamp": msg["timestamp"],
    });

    sqlx::query(
        "INSERT INTO device_logs (cabinet_id, log_type, content) VALUES (
            (SELECT id FROM cabinets WHERE imei = ?), 2, ?
        )",
    )
    .bind(dev_id)
    .bind(content.to_string())
    .execute(mysql)
    .await?;

    Ok(())
}

/// 处理设备主动上报 off（充满自动停止）
async fn handle_device_off(
    msg: &serde_json::Value,
    dev_id: &str,
    mysql: &MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tracing::info!("[off] 设备上报停止充电 dev_id={}", dev_id);

    let content = serde_json::json!({
        "dev_id": dev_id,
        "id": msg["id"],
        "msg_id": msg["msg_id"],
        "timestamp": msg["timestamp"],
    });

    sqlx::query(
        "INSERT INTO device_logs (cabinet_id, log_type, content) VALUES (
            (SELECT id FROM cabinets WHERE imei = ?), 2, ?
        )",
    )
    .bind(dev_id)
    .bind(content.to_string())
    .execute(mysql)
    .await?;

    Ok(())
}
