//! `pic secrets ls | set | rm | read` — manage Phase-3 group/app secrets.
//!
//! Exactly one of `--group` / `--app` selects the owner (mirroring
//! `pic vars`). Set reads the secret value from stdin (the only safe
//! channel — inline values would leak into shell history). The value is
//! sent as a JSON string; pass `--json` to interpret stdin as raw JSON
//! (numbers, maps, …) instead. `--env` is only meaningful for group
//! owners (app secrets are env-agnostic).

use std::io::Read;

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

use crate::client::{Client, VarOwnerArg};
use crate::config;
use crate::output::{OutputMode, Table};

/// Resolve the `--group`/`--app` pair into exactly one owner (shared XOR
/// message via [`crate::cmds::require_one_owner`]).
fn owner<'a>(group: Option<&'a str>, app: Option<&'a str>) -> Result<VarOwnerArg<'a>> {
    Ok(match crate::cmds::require_one_owner(app, group)? {
        crate::cmds::OwnerRef::App(a) => VarOwnerArg::App(a),
        crate::cmds::OwnerRef::Group(g) => VarOwnerArg::Group(g),
    })
}

pub async fn ls(
    group: Option<&str>,
    app: Option<&str>,
    env: Option<&str>,
    mode: OutputMode,
) -> Result<()> {
    let owner = owner(group, app)?;
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let resp = client.secrets_list(owner, env).await?;
    let mut table = Table::new(["name", "env", "updated_at"]);
    for s in resp.secrets {
        table.row([s.name, s.env, s.updated_at.to_rfc3339()]);
    }
    table.print(mode);
    Ok(())
}

pub async fn set(
    group: Option<&str>,
    app: Option<&str>,
    name: &str,
    env: Option<&str>,
    as_json: bool,
) -> Result<()> {
    let owner = owner(group, app)?;
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let mut buf = String::new();
    std::io::stdin()
        .read_to_string(&mut buf)
        .context("read secret value from stdin")?;
    let trimmed = buf.trim_end_matches(['\n', '\r']);
    if trimmed.is_empty() {
        return Err(anyhow!("empty stdin — secret value must not be empty"));
    }
    let value = if as_json {
        serde_json::from_str(trimmed).map_err(|e| anyhow!("parse stdin as JSON: {e}"))?
    } else {
        serde_json::Value::String(trimmed.to_string())
    };
    client.secrets_set(owner, name, value, env).await?;
    println!("Set secret {name}");
    Ok(())
}

pub async fn rm(
    group: Option<&str>,
    app: Option<&str>,
    name: &str,
    env: Option<&str>,
) -> Result<()> {
    let owner = owner(group, app)?;
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    client.secrets_delete(owner, name, env).await?;
    println!("Deleted secret {name}");
    Ok(())
}

/// `pic secrets read --group <slug> <name>` — fetch and print a secret's
/// PLAINTEXT value. This is the ONLY command that reveals a secret value,
/// and it is gated server-side at the owning group (there is no app-secret
/// equivalent). String values print raw; JSON values print pretty.
pub async fn read(group: &str, name: &str, env: Option<&str>) -> Result<()> {
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    let resp = client.group_secret_read_value(group, name, env).await?;
    match resp.value {
        serde_json::Value::String(s) => println!("{s}"),
        other => println!("{}", serde_json::to_string_pretty(&other)?),
    }
    Ok(())
}
