use crate::api::device_auth::{self, AuthOutcome};
use crate::api::error::ApiError;
use crate::api::state::AppState;
use axum::body::Bytes;
use axum::extract::Extension;
use axum::http::HeaderMap;
use serde_json::Value;
use std::sync::Arc;

/// Plain-text version string that the client compares against its cached
/// `sysinfo_ver`. Same value the heartbeat handler echoes via the
/// `sysinfo: true` flag.
pub async fn sysinfo_ver(Extension(state): Extension<Arc<AppState>>) -> String {
    state.cfg.sysinfo_ver.clone()
}

/// Bare-string body: `"SYSINFO_UPDATED"` or `"ID_NOT_FOUND"`. The client at
/// /Users/sn0/Desktop/rustdesk/src/hbbs_http/sync.rs:212 does a literal
/// `==` comparison on these — do not wrap in JSON.
pub async fn sysinfo(
    Extension(state): Extension<Arc<AppState>>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<String, ApiError> {
    // Step 1: signature gate. Verified → trust the id from the signed
    // header. LegacyUnsigned → fall through but enforce that the body id
    // isn't a managed peer (would be downgrade attempt).
    let outcome = device_auth::verify(&state, "POST", "/api/sysinfo", &headers, &body).await?;

    // Step 2: parse body.
    let payload: Value = serde_json::from_slice(&body)
        .map_err(|_| ApiError::BadRequest("invalid json".into()))?;
    let body_id = payload
        .get("id")
        .and_then(|v| v.as_str())
        .unwrap_or_default();
    let uuid = payload
        .get("uuid")
        .and_then(|v| v.as_str())
        .unwrap_or_default();
    if body_id.is_empty() || uuid.is_empty() {
        return Err(ApiError::BadRequest("id and uuid required".into()));
    }

    // Step 3: bind the trusted identity to the body. For signed requests,
    // the body id must match the header id — otherwise the agent is trying
    // to write inventory for someone else.
    let id = match outcome {
        AuthOutcome::Verified { id: signed_id } => {
            if body_id != signed_id {
                return Err(ApiError::Unauthorized);
            }
            signed_id
        }
        AuthOutcome::LegacyUnsigned => {
            device_auth::enforce_managed_for_id(&state, body_id).await?;
            body_id.to_string()
        }
    };

    // Tie sysinfo storage to a real rendezvous-registered peer. Without this
    // gate, any caller could populate device_sysinfo for arbitrary IDs.
    let peer = state
        .db
        .get_peer(&id)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    if peer.is_none() {
        return Ok("ID_NOT_FOUND".to_string());
    }

    let version = parse_version_number(payload.get("version").and_then(|v| v.as_str()));
    state
        .db
        .sysinfo_upsert(&id, uuid, &payload.to_string(), &state.cfg.sysinfo_ver, version)
        .await?;
    Ok("SYSINFO_UPDATED".to_string())
}

fn parse_version_number(s: Option<&str>) -> i64 {
    let Some(s) = s else { return 0 };
    // hbb_common encodes "1.4.2" as 1*1_000_000 + 4*1_000 + 2 = 1_004_002.
    let mut parts = s.split('.').map(|p| p.parse::<i64>().unwrap_or(0));
    let major = parts.next().unwrap_or(0);
    let minor = parts.next().unwrap_or(0);
    let patch = parts.next().unwrap_or(0);
    major * 1_000_000 + minor * 1_000 + patch
}
