//! Append-only record of privileged actions. See migration 029 for why it exists.
//!
//! Design constraints, both learned from the rest of this codebase:
//!
//!  * **Never fail the action.** An audit write that can turn a successful ban into a 500 makes
//!    moderation less reliable than no audit at all. Every failure here is logged and swallowed.
//!  * **Never store a credential.** `reset_pin` is the action most worth recording and the one
//!    whose payload must never be in `detail` — a table that could hand back a guest's PIN would
//!    be a worse privacy problem than the gap it closes.

use serde_json::Value;
use sqlx::PgPool;
use uuid::Uuid;

use crate::models::user::UserRole;

/// Record one privileged action.
///
/// Takes `&PgPool` rather than a transaction on purpose: the audit row is not part of the action's
/// atomicity. If the action commits and the audit write fails we want the action to stand (and a
/// loud log line); if the action rolls back, an orphan audit row saying "someone tried" is more
/// useful than silence.
#[allow(clippy::too_many_arguments)]
pub async fn record(
    pool: &PgPool,
    event_id: Uuid,
    actor_id: Uuid,
    actor_name: Option<&str>,
    actor_role: UserRole,
    action: &str,
    target_id: Option<Uuid>,
    target_name: Option<&str>,
    detail: Option<Value>,
) {
    let result = sqlx::query(
        "INSERT INTO host_action_audit
           (event_id, actor_id, actor_name, actor_role, action, target_id, target_name, detail)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
    )
    .bind(event_id)
    .bind(actor_id)
    .bind(actor_name)
    .bind(format!("{actor_role:?}").to_lowercase())
    .bind(action)
    .bind(target_id)
    .bind(target_name)
    .bind(detail)
    .execute(pool)
    .await;

    match result {
        Ok(_) => {}
        Err(e) => {
            // `error`, not `warn`: losing an audit row is the kind of thing that should show up in
            // whatever is watching the logs, even though it must not fail the request.
            tracing::error!(
                error = ?e, action, %actor_id, ?target_id,
                "failed to write host action audit row"
            );
        }
    }
}
