//! `pic admins` subcommands: `ls`, `create`, `show`, `set`, `rm`.
//!
//! All endpoints require `Capability::InstanceManageUsers` (owner /
//! admin). Member accounts get a 403.

use std::io::{IsTerminal, Read};

use anyhow::{anyhow, Context, Result};
use picloud_shared::InstanceRole;

use crate::client::{AdminDto, Client, CreateAdminBody, PatchAdminBody};
use crate::config;
use crate::output::{KvBlock, OutputMode, Table};

pub async fn ls(mode: OutputMode) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let admins = client.admins_list().await?;
    let mut table = Table::new([
        "id",
        "username",
        "role",
        "active",
        "email",
        "created_at",
        "last_login_at",
    ]);
    for a in admins {
        table.row([
            a.id.to_string(),
            a.username,
            a.instance_role.as_str().to_string(),
            a.is_active.to_string(),
            a.email.unwrap_or_else(|| "-".into()),
            a.created_at.to_rfc3339(),
            a.last_login_at
                .map(|t| t.to_rfc3339())
                .unwrap_or_else(|| "-".into()),
        ]);
    }
    table.print(mode);
    Ok(())
}

/// Resolve the password the user wants to set:
///   * `Some("-")` → read from stdin (no echo when interactive).
///   * `Some(other)` → **rejected**. Audit 2026-06-11 (H2) — an inline
///     password lands in shell history, `ps aux`, and
///     `/proc/<pid>/cmdline`, readable by any other UID on the host
///     while the process runs.
///   * `None` → require explicit `--password`.
fn resolve_password(password_arg: Option<&str>) -> Result<String> {
    match password_arg {
        None => Err(anyhow!(
            "missing --password; pass --password - to read the password from stdin"
        )),
        Some("-") => read_password_from_stdin(),
        Some(_) => Err(anyhow!(
            "passing the password inline is not allowed — it leaks into shell history, \
             `ps aux`, and /proc/<pid>/cmdline. Use `--password -` to read it from stdin \
             (interactive prompt, or piped: `printf %s \"$PW\" | pic admins create … --password -`)."
        )),
    }
}

fn read_password_from_stdin() -> Result<String> {
    let stdin = std::io::stdin();
    if stdin.is_terminal() {
        // True no-echo prompt. Audit 2026-06-11 (M2) — the previous
        // `read_line` path echoed the password despite a "no echo"
        // claim; `rpassword` is already a dependency via the login flow.
        rpassword::prompt_password("Password: ").context("read password from stdin")
    } else {
        // Piped input — read everything, strip trailing newline.
        let mut buf = String::new();
        stdin
            .lock()
            .read_to_string(&mut buf)
            .context("read password from stdin")?;
        Ok(buf.trim_end_matches(['\n', '\r']).to_string())
    }
}

pub async fn create(
    username: &str,
    password: Option<&str>,
    role: InstanceRole,
    email: Option<&str>,
    mode: OutputMode,
) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let pw = resolve_password(password)?;
    if pw.is_empty() {
        return Err(anyhow!("password must not be empty"));
    }
    let body = CreateAdminBody {
        username,
        password: &pw,
        instance_role: role,
        email,
    };
    let created = client.admins_create(&body).await?;
    print_admin(&created, mode);
    Ok(())
}

pub async fn show(id: &str, mode: OutputMode) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let a = client.admins_get(id).await?;
    print_admin(&a, mode);
    Ok(())
}

#[allow(clippy::too_many_arguments)]
pub async fn set(
    id: &str,
    username: Option<&str>,
    password: Option<&str>,
    active: Option<bool>,
    role: Option<InstanceRole>,
    mode: OutputMode,
) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    // Only resolve the password if --password was passed.
    let pw_owned;
    let password_ref: Option<&str> = if let Some(raw) = password {
        pw_owned = resolve_password(Some(raw))?;
        Some(pw_owned.as_str())
    } else {
        None
    };
    let body = PatchAdminBody {
        username,
        password: password_ref,
        is_active: active,
        instance_role: role,
    };
    let updated = client.admins_patch(id, &body).await?;
    print_admin(&updated, mode);
    Ok(())
}

pub async fn rm(id: &str) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    client.admins_delete(id).await?;
    println!("Deleted admin {id}");
    Ok(())
}

fn print_admin(a: &AdminDto, mode: OutputMode) {
    let mut block = KvBlock::new();
    block
        .field("id", a.id.to_string())
        .field("username", a.username.clone())
        .field("instance_role", a.instance_role.as_str().to_string())
        .field("is_active", a.is_active.to_string())
        .field("email", a.email.clone().unwrap_or_else(|| "-".into()))
        .field("created_at", a.created_at.to_rfc3339())
        .field(
            "last_login_at",
            a.last_login_at
                .map(|t| t.to_rfc3339())
                .unwrap_or_else(|| "-".into()),
        );
    block.print(mode);
}
