//! `pic config --effective` — read-only view of an app's resolved
//! configuration, with secret values masked (§4.6).
//!
//! Two sections:
//!   * `vars` — the resolved (group-inherited) config vars, each `key = value`
//!     annotated with the owner that won (kind + depth) and its scope. Pass
//!     `--explain` to also dump each var's `merged_from` provenance — the
//!     ordered (depth, scope) layers that fed the resolution.
//!   * `secrets` — masked statuses, cross-referenced against the manifest so an
//!     operator can see which declared secrets are still unset and which live
//!     secrets aren't declared. Values are never fetched or shown here; the
//!     server reports only `<set>` / `<unset>` plus the owning layer.
//!
//! The vars + masked-secret owner info comes from the `/config/effective`
//! endpoint; the manifest is only used to flag `declared`/`unset` drift.

use std::collections::BTreeSet;
use std::path::Path;

use anyhow::{bail, Result};

use crate::client::{Client, EffectiveOwnerDto};
use crate::config;
use crate::manifest::Manifest;
use crate::output::{OutputMode, Table};

/// Render an owner as `kind@depth` (e.g. `group@1`, `app@0`).
fn owner_label(owner: &EffectiveOwnerDto) -> String {
    format!("{}@{}", owner.kind, owner.depth)
}

pub async fn run(
    manifest_path: &Path,
    effective: bool,
    explain: bool,
    env: Option<&str>,
    mode: OutputMode,
) -> Result<()> {
    if !effective {
        bail!("`pic config` currently supports only `--effective`");
    }
    let creds = config::resolve()?;
    let client = Client::from_creds(&creds)?;
    // Resolve against the same env overlay as `pic plan`/`apply` so the
    // masked report targets the app those commands would act on — not the
    // base slug — when an overlay re-points slug/secrets.
    let manifest = Manifest::load_with_env(manifest_path, env)?;
    if manifest.is_group() {
        bail!(
            "`pic config --effective` resolves an APP's inherited config; \
             a [group] manifest has no single effective view"
        );
    }

    let eff = client.config_effective(manifest.slug()).await?;

    // --- vars: the resolved view, with winning owner + provenance. ---
    let mut vars_table = Table::new(["key", "value", "owner", "scope"]);
    for (key, var) in &eff.vars {
        vars_table.row([
            key.clone(),
            var.value.to_string(),
            owner_label(&var.owner),
            var.scope.clone(),
        ]);
    }
    vars_table.print(mode);

    if explain {
        // Provenance: the (depth, scope) layers each resolved key merged from.
        let mut prov = Table::new(["key", "depth", "scope"]);
        for (key, var) in &eff.vars {
            for layer in &var.merged_from {
                prov.row([key.clone(), layer.depth.to_string(), layer.scope.clone()]);
            }
        }
        prov.print(mode);
    }

    // --- secrets: masked status, folding manifest drift + owning layer. ---
    let declared: BTreeSet<String> = manifest.secrets.names.iter().cloned().collect();
    let on_server: BTreeSet<String> = eff.secrets.keys().cloned().collect();

    let mut table = Table::new(["secret", "value", "status", "owner", "scope"]);
    for name in declared.union(&on_server) {
        let (value, status) = match (declared.contains(name), on_server.contains(name)) {
            (true, true) => ("<set>", "managed"),
            (true, false) => ("<unset>", "declared, not pushed — `pic secrets set`"),
            (false, true) => ("<set>", "on server, not in manifest"),
            (false, false) => unreachable!("name came from one of the two sets"),
        };
        let (owner, scope) = match eff.secrets.get(name) {
            Some(s) => (owner_label(&s.owner), s.scope.clone()),
            None => ("-".to_string(), "-".to_string()),
        };
        table.row([
            name.clone(),
            value.to_string(),
            status.to_string(),
            owner,
            scope,
        ]);
    }
    table.print(mode);
    Ok(())
}
