//! `POST /api/unattended-password` — agent-side reporting of the per-boot
//! "permanent password" used for unattended access (no logged-in user to
//! click the approval popup). hello-agent generates a random password
//! every time the service starts and posts it here so the admin UI can
//! surface it for support staff.
//!
//! Auth: same per-peer signed-API gate as `/api/sysinfo` and
//! `/api/heartbeat` — see [`crate::api::device_auth`]. Managed peers
//! (`peer.managed = 1`) must carry a valid Ed25519 signature; stock
//! clients keep posting unsigned and the first valid signature TOFU-
//! promotes the peer.

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;

/// Body: `{"id": "...", "uuid": "...", "password": "..."}`
/// Response (bare string, like sysinfo): `"OK"` or `"ID_NOT_FOUND"`.
pub async fn unattended_password(
    Extension(state): Extension<Arc<AppState>>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<String, ApiError> {
    let outcome = device_auth::verify(
        &state,
        "POST",
        "/api/unattended-password",
        &headers,
        &body,
    )
    .await?;

    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();
    let password = payload
        .get("password")
        .and_then(|v| v.as_str())
        .unwrap_or_default();
    if body_id.is_empty() || uuid.is_empty() || password.is_empty() {
        return Err(ApiError::BadRequest(
            "id, uuid, and password are required".into(),
        ));
    }

    // Bind the trusted identity to the body. For signed requests the body
    // id must match the header id, or the agent is trying to overwrite
    // someone else's displayed password. For unsigned requests we just
    // need to ensure the peer isn't already locked down as managed.
    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()
        }
    };

    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());
    }

    state
        .db
        .set_unattended_password(&id, uuid, password)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    Ok("OK".to_string())
}
