mod explain;
pub mod generate;
mod init;
mod module;
pub mod plugin;
mod profile;

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use clap::{CommandFactory, Parser, Subcommand};
use serde::Serialize;

use crate::files::CfgdFileManager;
use crate::packages;
use crate::secrets;
use cfgd_core::composition::{self, CompositionInput, SubscriptionConfig};
use cfgd_core::config::{self, CfgdConfig, MergedProfile, ResolvedProfile};
use cfgd_core::modules;
use cfgd_core::output::Printer;
use cfgd_core::platform::Platform;
use cfgd_core::providers::{
    FileAction, PackageAction, ProviderRegistry, SecretAction, SecretBackend,
};
use cfgd_core::reconciler::{self, PhaseName, ReconcileContext, Reconciler};
use cfgd_core::sources::SourceManager;
use cfgd_core::state::StateStore;

const MSG_NO_CONFIG: &str = "No cfgd.yaml found — run 'cfgd init' first";
const MSG_RUN_APPLY: &str = "Run 'cfgd apply --dry-run' to preview changes, then 'cfgd apply'";
const MSG_NOTHING_TO_DO: &str = "Nothing to do — everything is up to date";

// --- Structured output types ---

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct StatusOutput {
    last_apply: Option<cfgd_core::state::ApplyRecord>,
    drift: Vec<cfgd_core::state::DriftEvent>,
    sources: Vec<cfgd_core::state::ConfigSourceRecord>,
    pending_decisions: Vec<cfgd_core::state::PendingDecision>,
    modules: Vec<ModuleStatusEntry>,
    managed_resources: Vec<cfgd_core::state::ManagedResource>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ModuleStatusEntry {
    name: String,
    packages: usize,
    files: usize,
    status: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct LogOutput {
    entries: Vec<cfgd_core::state::ApplyRecord>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct VerifyOutput {
    results: Vec<cfgd_core::reconciler::VerifyResult>,
    pass_count: usize,
    fail_count: usize,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct RollbackOutput {
    apply_id: i64,
    files_restored: usize,
    files_removed: usize,
    non_file_actions: Vec<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct PlanOutput {
    context: String,
    phases: Vec<PlanPhaseOutput>,
    total_actions: usize,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    warnings: Vec<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct PlanPhaseOutput {
    phase: String,
    actions: Vec<PlanActionOutput>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct PlanActionOutput {
    description: String,
    #[serde(rename = "type")]
    action_type: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorOutput {
    config: DoctorConfigCheck,
    git: bool,
    secrets: DoctorSecretsCheck,
    package_managers: Vec<DoctorManagerCheck>,
    modules: Vec<DoctorModuleCheck>,
    system_configurators: Vec<DoctorConfiguratorCheck>,
}

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorConfigCheck {
    valid: bool,
    path: String,
    name: Option<String>,
    profile: Option<String>,
    error: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorSecretsCheck {
    sops_available: bool,
    sops_version: Option<String>,
    age_key_exists: bool,
    age_key_path: Option<String>,
    sops_config_exists: bool,
    providers: Vec<DoctorProviderCheck>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorProviderCheck {
    name: String,
    available: bool,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorManagerCheck {
    name: String,
    available: bool,
    declared: bool,
    can_bootstrap: bool,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorModuleCheck {
    name: String,
    valid: bool,
    error: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DoctorConfiguratorCheck {
    name: String,
    available: bool,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SourceListEntry {
    name: String,
    url: String,
    priority: u32,
    version: Option<String>,
    status: String,
    last_fetched: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SourceShowOutput {
    name: String,
    url: String,
    branch: String,
    priority: u32,
    accept_recommended: bool,
    profile: Option<String>,
    sync_interval: String,
    auto_apply: bool,
    version_pin: Option<String>,
    state: Option<SourceStateInfo>,
    managed_resources: Vec<SourceResourceEntry>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SourceStateInfo {
    status: String,
    last_fetched: Option<String>,
    last_commit: Option<String>,
    version: Option<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SourceResourceEntry {
    resource_type: String,
    resource_id: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ProfileListEntry {
    pub name: String,
    pub active: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub inherits: Option<String>,
    pub module_count: usize,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ModuleSearchResult {
    pub name: String,
    pub registry: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct RegistryListEntry {
    pub name: String,
    pub url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct KeyListEntry {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fingerprint: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created: Option<String>,
}

fn default_config_file() -> PathBuf {
    cfgd_core::default_config_dir().join("cfgd.yaml")
}

/// No built-in aliases — all aliases come from cfgd.yaml spec.aliases.
/// Default aliases are scaffolded by `cfgd init`.
fn builtin_aliases() -> HashMap<String, String> {
    HashMap::new()
}

/// Expand CLI aliases before clap parsing.
///
/// Finds the first positional argument (non-flag, non-flag-value), checks if it
/// matches an alias, and replaces it with the alias's command tokens. Any remaining
/// arguments after the alias name are appended.
///
/// Returns the potentially-expanded args.
pub fn expand_aliases(args: Vec<String>) -> Vec<String> {
    if args.len() < 2 {
        return args;
    }

    // Collect global flags that appear before the subcommand so we can skip them.
    // We need to find the first positional arg (the subcommand position).
    let mut subcommand_idx = None;
    let mut i = 1; // skip argv[0]
    while i < args.len() {
        let arg = &args[i];
        if arg == "--" {
            break;
        }
        if arg.starts_with('-') {
            // Skip flags and their values. Known global flags that take a value:
            if matches!(arg.as_str(), "--config" | "--profile") {
                i += 1; // skip the value too
            } else if arg.starts_with("--config=") || arg.starts_with("--profile=") {
                // value is inline, no skip needed
            }
            // Boolean flags (--verbose, -v, --quiet, -q, --no-color) are just skipped
        } else {
            subcommand_idx = Some(i);
            break;
        }
        i += 1;
    }

    let subcommand_idx = match subcommand_idx {
        Some(idx) => idx,
        None => return args,
    };

    let candidate = &args[subcommand_idx];

    // Try to load config to get user aliases; fall back to empty if unavailable.
    let config_path = extract_config_path(&args);
    let user_aliases = config_path
        .and_then(|p| {
            if p.exists() {
                cfgd_core::config::load_config(&p).ok()
            } else {
                None
            }
        })
        .map(|c| c.spec.aliases)
        .unwrap_or_default();

    // Merge: user overrides built-in
    let mut aliases = builtin_aliases();
    aliases.extend(user_aliases);

    let expansion = match aliases.get(candidate) {
        Some(cmd) => cmd,
        None => return args,
    };

    // Build expanded args: argv[0] + globals + expanded tokens + remaining args
    let mut result = Vec::with_capacity(args.len() + 4);
    result.extend_from_slice(&args[..subcommand_idx]);
    result.extend(expansion.split_whitespace().map(String::from));
    result.extend_from_slice(&args[subcommand_idx + 1..]);
    result
}

/// Extract the --config path from raw args, or use the default.
fn extract_config_path(args: &[String]) -> Option<PathBuf> {
    for (i, arg) in args.iter().enumerate() {
        if arg == "--config" {
            return args.get(i + 1).map(PathBuf::from);
        }
        if let Some(val) = arg.strip_prefix("--config=") {
            return Some(PathBuf::from(val));
        }
    }
    Some(default_config_file())
}

#[derive(Debug, Clone)]
pub struct OutputFormatArg(pub cfgd_core::output::OutputFormat);

impl std::str::FromStr for OutputFormatArg {
    type Err = String;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        use cfgd_core::output::OutputFormat;
        match s {
            "table" => Ok(Self(OutputFormat::Table)),
            "wide" => Ok(Self(OutputFormat::Wide)),
            "json" => Ok(Self(OutputFormat::Json)),
            "yaml" => Ok(Self(OutputFormat::Yaml)),
            "name" => Ok(Self(OutputFormat::Name)),
            other => {
                if let Some(expr) = other.strip_prefix("jsonpath=") {
                    Ok(Self(OutputFormat::Jsonpath(expr.to_string())))
                } else if let Some(tmpl) = other.strip_prefix("template=") {
                    Ok(Self(OutputFormat::Template(tmpl.to_string())))
                } else if let Some(path) = other.strip_prefix("template-file=") {
                    Ok(Self(OutputFormat::TemplateFile(std::path::PathBuf::from(
                        path,
                    ))))
                } else {
                    Err(format!(
                        "unknown output format '{}'. Valid: table, wide, json, yaml, name, jsonpath=EXPR, template=TMPL, template-file=PATH",
                        other
                    ))
                }
            }
        }
    }
}

impl From<OutputFormatArg> for clap::builder::OsStr {
    fn from(_: OutputFormatArg) -> clap::builder::OsStr {
        clap::builder::OsStr::from("table")
    }
}

#[derive(Parser)]
#[command(
    name = "cfgd",
    version,
    about = "Declarative, GitOps-style machine configuration"
)]
pub struct Cli {
    /// Path to config file
    #[arg(long, global = true, default_value_os_t = default_config_file(), env = "CFGD_CONFIG")]
    pub config: PathBuf,

    /// Profile to use (overrides config file)
    #[arg(long, global = true, env = "CFGD_PROFILE")]
    pub profile: Option<String>,

    /// Verbose output
    #[arg(
        long,
        short,
        global = true,
        env = "CFGD_VERBOSE",
        conflicts_with = "quiet"
    )]
    pub verbose: bool,

    /// Suppress all non-error output
    #[arg(
        long,
        short,
        global = true,
        env = "CFGD_QUIET",
        conflicts_with = "verbose"
    )]
    pub quiet: bool,

    /// Disable colored output
    #[arg(long, global = true)]
    pub no_color: bool,

    /// Output format: table, wide, json, yaml, name, jsonpath=EXPR, template=TMPL, template-file=PATH
    #[arg(long, short = 'o', global = true, default_value = "table")]
    pub output: OutputFormatArg,

    /// JSONPath expression to extract from structured output
    #[arg(long, global = true)]
    pub jsonpath: Option<String>,

    /// Override state directory (default: $CFGD_STATE_DIR or platform data dir)
    #[arg(long, global = true, env = "CFGD_STATE_DIR")]
    pub state_dir: Option<PathBuf>,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Parser)]
pub struct ApplyArgs {
    /// Config source: git URL to clone, or local path to an existing config directory
    #[arg(long)]
    pub from: Option<String>,
    /// Preview changes without applying
    #[arg(long)]
    pub dry_run: bool,
    /// Apply only a specific phase
    #[arg(long)]
    pub phase: Option<String>,
    /// Skip confirmation prompt
    #[arg(long, short, env = "CFGD_YES")]
    pub yes: bool,
    /// Skip specific items by dot-notation path (e.g., packages.brew.ripgrep, system.sysctl)
    #[arg(long)]
    pub skip: Vec<String>,
    /// Apply only items matching dot-notation paths (e.g., packages, files)
    #[arg(long)]
    pub only: Vec<String>,
    /// Apply only the specified module and its dependencies
    #[arg(long)]
    pub module: Option<String>,
    /// Skip all script hooks (pre/post/onChange)
    #[arg(long)]
    pub skip_scripts: bool,
}

#[derive(Parser)]
pub struct PlanArgs {
    /// Plan only a specific phase
    #[arg(long)]
    pub phase: Option<String>,
    /// Skip specific items by dot-notation path (e.g., packages.brew.ripgrep, system.sysctl)
    #[arg(long)]
    pub skip: Vec<String>,
    /// Plan only items matching dot-notation paths (e.g., packages, files)
    #[arg(long)]
    pub only: Vec<String>,
    /// Plan only the specified module and its dependencies
    #[arg(long)]
    pub module: Option<String>,
    /// Skip all script hooks (pre/post/onChange)
    #[arg(long)]
    pub skip_scripts: bool,
    /// Reconciliation context: apply (default) or reconcile
    #[arg(long, default_value = "apply")]
    pub context: String,
}

#[derive(Subcommand)]
pub enum Command {
    /// Initialize a new cfgd configuration repository
    Init {
        /// Directory to initialize (default: current directory)
        #[arg(value_hint = clap::ValueHint::DirPath)]
        path: Option<String>,

        /// Config source: git URL to clone, or local path to an existing config directory
        #[arg(long)]
        from: Option<String>,

        /// Git branch to clone (default: master)
        #[arg(long, default_value = "master")]
        branch: String,

        /// Config name in metadata (default: directory name)
        #[arg(long)]
        name: Option<String>,

        /// Also apply configuration after scaffolding
        #[arg(long)]
        apply: bool,

        /// Preview changes without applying (used with --apply/--apply-profile/--apply-module)
        #[arg(long)]
        dry_run: bool,

        /// Skip all confirmation prompts (used with --apply)
        #[arg(long, short, env = "CFGD_YES")]
        yes: bool,

        /// Install daemon service after init
        #[arg(long)]
        install_daemon: bool,

        /// Theme name (default, dracula, solarized-dark, solarized-light, minimal)
        #[arg(long)]
        theme: Option<String>,

        /// Activate and apply a specific profile (errors if not found)
        #[arg(long)]
        apply_profile: Option<String>,

        /// Apply specific modules (repeatable, errors if not found)
        #[arg(long = "apply-module")]
        apply_modules: Vec<String>,
    },

    /// Apply the configuration (use --dry-run to preview without applying)
    Apply(ApplyArgs),

    /// Preview the reconciliation plan without applying
    Plan(PlanArgs),

    /// Show configuration status and drift
    Status {
        /// Show status for a specific module (no profile required)
        #[arg(long)]
        module: Option<String>,
    },

    /// Show detailed diffs
    Diff {
        /// Show diff for a specific module only
        #[arg(long)]
        module: Option<String>,
    },

    /// Show apply history
    Log {
        /// Number of entries to show
        #[arg(long, short = 'n', default_value = "20")]
        limit: u32,
        /// Show captured script output for a specific apply ID
        #[arg(long)]
        show_output: Option<i64>,
    },

    /// Sync with remote
    Sync,

    /// Pull remote changes
    Pull,

    /// Manage the daemon
    Daemon {
        #[command(subcommand)]
        command: Option<DaemonCommand>,
    },

    /// Manage secrets
    Secret {
        #[command(subcommand)]
        command: SecretCommand,
    },

    /// Manage profiles
    Profile {
        #[command(subcommand)]
        command: ProfileCommand,
    },

    /// Verify all managed resources match desired state
    Verify {
        /// Verify only a specific module (no profile required)
        #[arg(long)]
        module: Option<String>,
    },

    /// Check system health and dependencies
    Doctor,

    /// Manage modules
    Module {
        #[command(subcommand)]
        command: ModuleCommand,
    },

    /// Manage config sources
    Source {
        #[command(subcommand)]
        command: SourceCommand,
    },

    /// Check for and install updates
    Upgrade {
        /// Only check if an update is available (exit 0 = current, exit 1 = update available)
        #[arg(long)]
        check: bool,
    },

    /// Accept or reject pending source decisions
    Decide {
        /// Action: accept or reject
        action: String,

        /// Resource path to decide on (e.g. packages.brew.k9s). Omit for batch operations.
        resource: Option<String>,

        /// Apply decision to all pending items from this source
        #[arg(long)]
        source: Option<String>,

        /// Apply decision to all pending items
        #[arg(long)]
        all: bool,
    },

    /// Show schema and field documentation for cfgd resource types
    Explain {
        /// Resource type or field path (e.g., "module", "profile.spec.packages")
        #[arg(value_hint = clap::ValueHint::Other)]
        resource: Option<String>,

        /// Show all fields expanded recursively
        #[arg(long)]
        recursive: bool,
    },

    /// View or edit the cfgd configuration
    Config {
        #[command(subcommand)]
        command: ConfigCommand,
    },

    /// Manage GitHub Actions workflows for config repo releases
    Workflow {
        #[command(subcommand)]
        command: WorkflowCommand,
    },

    /// Check in with the device gateway and report status
    Checkin {
        /// Device gateway URL
        #[arg(long, env = "CFGD_SERVER_URL")]
        server_url: String,

        /// API key for authentication
        #[arg(long, env = "CFGD_API_KEY")]
        api_key: Option<String>,

        /// Device identifier (defaults to hostname)
        #[arg(long, env = "CFGD_DEVICE_ID")]
        device_id: Option<String>,
    },

    /// Enroll with a device gateway (token or key-based)
    Enroll {
        /// Device gateway URL
        #[arg(long, env = "CFGD_SERVER_URL")]
        server_url: String,

        /// Bootstrap token for token-based enrollment
        #[arg(long, env = "CFGD_BOOTSTRAP_TOKEN")]
        token: Option<String>,

        /// SSH key file for signing (default: auto-detect from agent or ~/.ssh/)
        #[arg(long, conflicts_with = "gpg_key")]
        ssh_key: Option<String>,

        /// GPG key ID for signing
        #[arg(long, conflicts_with = "ssh_key")]
        gpg_key: Option<String>,

        /// Username to enroll as (default: current system user)
        #[arg(long, env = "USER")]
        username: Option<String>,
    },

    /// Generate shell completions
    Completions {
        /// Shell to generate completions for
        #[arg(value_enum)]
        shell: clap_complete::Shell,
    },

    /// AI-guided configuration generation
    Generate(generate::GenerateArgs),

    /// Roll back a previous apply by restoring file backups
    Rollback {
        /// Apply ID to roll back (from 'cfgd log')
        apply_id: i64,

        /// Skip confirmation prompt
        #[arg(long, short, env = "CFGD_YES")]
        yes: bool,
    },

    /// Start MCP server for AI editor integration
    #[command(name = "mcp-server")]
    McpServer,

    /// Compliance status and evidence export
    Compliance {
        #[command(subcommand)]
        command: Option<ComplianceCommand>,
    },
}

#[derive(Subcommand)]
pub enum ComplianceCommand {
    /// Export compliance snapshot to file or stdout
    Export,
    /// Show compliance snapshot history
    History {
        /// Show only snapshots since this duration ago (e.g. 7d, 24h, 30m)
        #[arg(long)]
        since: Option<String>,
    },
    /// Show diff between two snapshots
    Diff {
        /// First snapshot ID
        id1: i64,
        /// Second snapshot ID
        id2: i64,
    },
}

#[derive(Parser)]
pub struct SourceAddArgs {
    /// Git URL of the source
    pub url: String,
    /// Name for this source (default: inferred from URL)
    #[arg(long)]
    pub name: Option<String>,
    /// Git branch (default: master)
    #[arg(long)]
    pub branch: Option<String>,
    /// Profile to subscribe to
    #[arg(long)]
    pub profile: Option<String>,
    /// Accept recommended items
    #[arg(long)]
    pub accept_recommended: bool,
    /// Priority for conflict resolution (default: 500, local config: 1000)
    #[arg(long)]
    pub priority: Option<u32>,
    /// Opt-in to specific items (repeatable)
    #[arg(long = "opt-in")]
    pub opt_in: Vec<String>,
    /// Sync interval (e.g., "30m", "1h", "6h")
    #[arg(long)]
    pub sync_interval: Option<String>,
    /// Automatically apply changes on sync
    #[arg(long)]
    pub auto_apply: bool,
    /// Pin to a semver version range (e.g., "~1.0", ">=2.0")
    #[arg(long)]
    pub pin_version: Option<String>,
    /// Skip confirmation prompt
    #[arg(long, short, env = "CFGD_YES")]
    pub yes: bool,
}

#[derive(Subcommand)]
pub enum SourceCommand {
    /// Subscribe to a config source
    Add(Box<SourceAddArgs>),

    /// List subscribed sources
    #[command(alias = "ls")]
    List,

    /// Show details of a source
    Show {
        /// Source name
        name: String,
    },

    /// Remove a source subscription
    Remove {
        /// Source name
        name: String,

        /// Keep all resources from this source as local
        #[arg(long)]
        keep_all: bool,

        /// Remove all resources from this source
        #[arg(long)]
        remove_all: bool,

        /// Skip confirmation prompt (defaults to --keep-all behavior)
        #[arg(long, short, env = "CFGD_YES")]
        yes: bool,
    },

    /// Update sources (fetch latest)
    Update {
        /// Specific source to update (default: all)
        name: Option<String>,
    },

    /// Override a source's recommendation
    Override {
        /// Source name
        source: String,

        /// Action: set or reject
        action: String,

        /// Resource path (e.g., env.EDITOR, packages.brew.formulae)
        path: String,

        /// Value (for set action)
        value: Option<String>,
    },

    /// Set or view the priority of a source
    Priority {
        /// Source name
        name: String,

        /// New priority value (omit to view current)
        value: Option<u32>,
    },

    /// Replace one source with another
    Replace {
        /// Source to replace
        old_name: String,

        /// Git URL of the new source
        new_url: String,
    },

    /// Open cfgd-source.yaml in $EDITOR
    Edit,

    /// Create a new cfgd-source.yaml in the current directory
    Create {
        /// Source name
        name: Option<String>,

        /// Description
        #[arg(long)]
        description: Option<String>,

        /// Version string
        #[arg(long)]
        version: Option<String>,
    },
}

#[derive(Subcommand)]
pub enum DaemonCommand {
    /// Run daemon in foreground (default when no subcommand given)
    Run,
    /// Install as a system service (launchd on macOS, systemd on Linux, Windows Service on Windows)
    Install,
    /// Uninstall the system service
    Uninstall,
    /// Show daemon status
    Status,
    /// Run as a Windows Service (called by SCM, not directly by users)
    #[clap(hide = true)]
    Service,
}

#[derive(Subcommand)]
pub enum SecretCommand {
    /// Encrypt a file
    Encrypt {
        /// File to encrypt
        file: PathBuf,
    },
    /// Decrypt a file
    Decrypt {
        /// File to decrypt
        file: PathBuf,
    },
    /// Edit an encrypted file
    Edit {
        /// File to edit
        file: PathBuf,
    },
    /// Initialize age key and .sops.yaml
    Init,
}

#[derive(Subcommand)]
pub enum ConfigCommand {
    /// Show the current cfgd configuration
    Show,
    /// Open cfgd.yaml in $EDITOR
    Edit,
    /// Get a config value by dotted key path (e.g., theme, daemon.reconcile.interval)
    Get {
        /// Dotted key path using YAML field names
        key: String,
    },
    /// Set a config value by dotted key path
    Set {
        /// Dotted key path using YAML field names
        key: String,
        /// Value to set
        value: String,
    },
    /// Remove a config value (resets to default)
    Unset {
        /// Dotted key path to remove
        key: String,
    },
}

#[derive(Subcommand)]
pub enum WorkflowCommand {
    /// Generate or regenerate GitHub Actions workflows for releases
    Generate {
        /// Overwrite existing workflow files
        #[arg(long)]
        force: bool,
    },
}

#[derive(Parser)]
pub struct ProfileCreateArgs {
    /// Profile name
    pub name: String,
    /// Inherit from other profiles (repeatable)
    #[arg(long = "inherit")]
    pub inherits: Vec<String>,
    /// Modules to include (repeatable)
    #[arg(long = "module")]
    pub modules: Vec<String>,
    /// Packages to include (repeatable, e.g. --package curl or --package brew:curl)
    #[arg(long = "package")]
    pub packages: Vec<String>,
    /// Environment variables as key=value (repeatable)
    #[arg(long = "env")]
    pub env: Vec<String>,
    /// Shell aliases as name=command (repeatable)
    #[arg(long = "alias")]
    pub aliases: Vec<String>,
    /// System settings as key=value (repeatable)
    #[arg(long = "system")]
    pub system: Vec<String>,
    /// Files to manage (repeatable). Use <path> to adopt in place, or <source>:<target> for explicit mapping.
    #[arg(long = "file")]
    pub files: Vec<String>,
    /// Mark all --file entries as private (local-only, excluded from git).
    #[arg(long = "private-files")]
    pub private: bool,
    /// Secrets as source:target (repeatable, e.g. --secret secrets/api-key.enc:~/.config/app/key)
    #[arg(long = "secret")]
    pub secrets: Vec<String>,
    /// Pre-apply scripts (repeatable)
    #[arg(long = "pre-apply")]
    pub pre_apply: Vec<String>,
    /// Post-apply scripts (repeatable)
    #[arg(long = "post-apply")]
    pub post_apply: Vec<String>,
    /// Pre-reconcile scripts (repeatable)
    #[arg(long = "pre-reconcile")]
    pub pre_reconcile: Vec<String>,
    /// Post-reconcile scripts (repeatable)
    #[arg(long = "post-reconcile")]
    pub post_reconcile: Vec<String>,
    /// On-change scripts (repeatable)
    #[arg(long = "on-change")]
    pub on_change: Vec<String>,
    /// On-drift scripts (repeatable)
    #[arg(long = "on-drift")]
    pub on_drift: Vec<String>,
}

#[derive(Parser)]
pub struct ProfileUpdateArgs {
    /// Profile name (default: active profile)
    pub name: Option<String>,
    /// Inherited profiles (repeatable, prefix with - to remove)
    #[arg(long = "inherit", allow_hyphen_values = true)]
    pub inherits: Vec<String>,
    /// Modules (repeatable, prefix with - to remove)
    #[arg(long = "module", allow_hyphen_values = true)]
    pub modules: Vec<String>,
    /// Packages (repeatable, prefix with - to remove, e.g. --package brew:jq --package -brew:old)
    #[arg(long = "package", allow_hyphen_values = true)]
    pub packages: Vec<String>,
    /// Files (repeatable, prefix with - to remove by target path)
    #[arg(long = "file", allow_hyphen_values = true)]
    pub files: Vec<String>,
    /// Env vars as KEY=VALUE (repeatable, prefix with - to remove by key)
    #[arg(long = "env", allow_hyphen_values = true)]
    pub env: Vec<String>,
    /// Shell aliases as name=command (repeatable, prefix with - to remove by name)
    #[arg(long = "alias", allow_hyphen_values = true)]
    pub aliases: Vec<String>,
    /// System settings as key=value (repeatable, prefix with - to remove by key)
    #[arg(long = "system", allow_hyphen_values = true)]
    pub system: Vec<String>,
    /// Secrets as source:target (repeatable, prefix with - to remove by target)
    #[arg(long = "secret", allow_hyphen_values = true)]
    pub secrets: Vec<String>,
    /// Pre-apply scripts (repeatable, prefix with - to remove)
    #[arg(long = "pre-apply", allow_hyphen_values = true)]
    pub pre_apply: Vec<String>,
    /// Post-apply scripts (repeatable, prefix with - to remove)
    #[arg(long = "post-apply", allow_hyphen_values = true)]
    pub post_apply: Vec<String>,
    /// Pre-reconcile scripts (repeatable, prefix with - to remove)
    #[arg(long = "pre-reconcile", allow_hyphen_values = true)]
    pub pre_reconcile: Vec<String>,
    /// Post-reconcile scripts (repeatable, prefix with - to remove)
    #[arg(long = "post-reconcile", allow_hyphen_values = true)]
    pub post_reconcile: Vec<String>,
    /// On-change scripts (repeatable, prefix with - to remove)
    #[arg(long = "on-change", allow_hyphen_values = true)]
    pub on_change: Vec<String>,
    /// On-drift scripts (repeatable, prefix with - to remove)
    #[arg(long = "on-drift", allow_hyphen_values = true)]
    pub on_drift: Vec<String>,
    /// Mark all --file entries as private (local-only, excluded from git).
    #[arg(long = "private-files")]
    pub private: bool,
}

#[derive(Subcommand)]
pub enum ProfileCommand {
    /// List available profiles
    #[command(alias = "ls")]
    List,
    /// Switch to a different profile (alias: use)
    #[command(alias = "use")]
    Switch {
        /// Profile name
        name: String,
    },
    /// Show the resolved profile
    Show {
        /// Profile name (default: active profile)
        name: Option<String>,
    },
    /// Create a new profile
    Create(Box<ProfileCreateArgs>),
    /// Modify an existing profile
    Update(Box<ProfileUpdateArgs>),
    /// Open a profile in $EDITOR
    Edit {
        /// Profile name
        name: String,
    },
    /// Delete a profile
    Delete {
        /// Profile name
        name: String,
        /// Skip confirmation prompt
        #[arg(short, long, env = "CFGD_YES")]
        yes: bool,
    },
}

#[derive(Parser)]
pub struct ModuleCreateArgs {
    /// Module name
    pub name: String,
    /// Module description
    #[arg(long)]
    pub description: Option<String>,
    /// Dependencies on other modules (repeatable)
    #[arg(long = "depends")]
    pub depends: Vec<String>,
    /// Packages to include (repeatable)
    #[arg(long = "package")]
    pub packages: Vec<String>,
    /// Files to import (repeatable). Use <path> to adopt in place, or <source>:<target> for explicit mapping.
    #[arg(long = "file")]
    pub files: Vec<String>,
    /// Mark all --file entries as private (local-only, excluded from git).
    #[arg(long = "private-files")]
    pub private: bool,
    /// Environment variables as KEY=VALUE (repeatable)
    #[arg(long = "env")]
    pub env: Vec<String>,
    /// Shell aliases as name=command (repeatable)
    #[arg(long = "alias")]
    pub aliases: Vec<String>,
    /// Post-apply scripts (repeatable)
    #[arg(long = "post-apply")]
    pub post_apply: Vec<String>,
    /// Helm-style overrides: package.<name>.<field>=<value>
    #[arg(long = "set")]
    pub sets: Vec<String>,
    /// Apply the module immediately after creating it
    #[arg(long)]
    pub apply: bool,
    /// Skip confirmation prompts (used with --apply)
    #[arg(long, short, env = "CFGD_YES")]
    pub yes: bool,
}

#[derive(Parser)]
pub struct ModuleUpdateArgs {
    /// Module name
    pub name: String,
    /// Packages (repeatable, prefix with - to remove)
    #[arg(long = "package", allow_hyphen_values = true)]
    pub packages: Vec<String>,
    /// Files (repeatable, prefix with - to remove by target path)
    #[arg(long = "file", allow_hyphen_values = true)]
    pub files: Vec<String>,
    /// Env vars as KEY=VALUE (repeatable, prefix with - to remove by key)
    #[arg(long = "env", allow_hyphen_values = true)]
    pub env: Vec<String>,
    /// Shell aliases as name=command (repeatable, prefix with - to remove by name)
    #[arg(long = "alias", allow_hyphen_values = true)]
    pub aliases: Vec<String>,
    /// Dependencies (repeatable, prefix with - to remove)
    #[arg(long = "depends", allow_hyphen_values = true)]
    pub depends: Vec<String>,
    /// Post-apply scripts (repeatable, prefix with - to remove)
    #[arg(long = "post-apply", allow_hyphen_values = true)]
    pub post_apply: Vec<String>,
    /// Mark all --file entries as private (local-only, excluded from git).
    #[arg(long = "private-files")]
    pub private: bool,
    /// Set description
    #[arg(long)]
    pub description: Option<String>,
    /// Helm-style overrides: package.<name>.<field>=<value>
    #[arg(long = "set")]
    pub sets: Vec<String>,
}

#[derive(Subcommand)]
pub enum ModuleCommand {
    /// List available modules and their status
    #[command(alias = "ls")]
    List,
    /// Show module details: packages, files, deps, resolved managers
    Show {
        /// Module name
        name: String,
        /// Show full env variable values (default: masked)
        #[arg(long)]
        show_values: bool,
    },
    /// Create a new local module
    Create(Box<ModuleCreateArgs>),
    /// Modify an existing local module (add/remove packages, files, deps; --set overrides)
    Update(Box<ModuleUpdateArgs>),
    /// Open a module's module.yaml in $EDITOR
    Edit {
        /// Module name
        name: String,
    },
    /// Delete a local module
    Delete {
        /// Module name
        name: String,
        /// Skip confirmation prompt
        #[arg(short, long, env = "CFGD_YES")]
        yes: bool,
        /// Also remove files deployed by this module to target locations
        #[arg(long)]
        purge: bool,
    },
    /// Create a new local module (alias for 'create')
    #[command(hide = true)]
    Add(Box<ModuleCreateArgs>),
    /// Upgrade a remote module to a new version
    Upgrade {
        /// Module name (must be a locked remote module)
        name: String,
        /// New ref to pin to (tag or commit SHA)
        #[arg(long)]
        ref_: Option<String>,
        /// Skip confirmation prompt (for non-interactive use)
        #[arg(short, long, env = "CFGD_YES")]
        yes: bool,
        /// Allow unsigned modules even when require-signatures is enabled
        #[arg(long)]
        allow_unsigned: bool,
    },
    /// Search module registries for available modules
    Search {
        /// Search query
        query: String,
    },
    /// Manage module registries (searchable indexes of reusable modules)
    Registry {
        #[command(subcommand)]
        command: ModuleRegistryCommand,
    },
    /// Export a module to another format
    Export {
        /// Module name
        name: String,
        /// Export format
        #[arg(long, value_enum)]
        format: ExportFormat,
        /// Directory to write exported files (default: current directory)
        #[arg(long)]
        dir: Option<String>,
    },
    /// Push a module directory to an OCI registry as an artifact
    Push {
        /// Path to the module directory (must contain module.yaml)
        dir: String,
        /// OCI artifact reference (e.g. ghcr.io/myorg/mymodule:v1.0.0)
        #[arg(long)]
        artifact: String,
        /// Platform annotation (default: auto-detected from OS/arch)
        #[arg(long)]
        platform: Option<String>,
        /// After push, apply a Module CRD to the cluster referencing the artifact
        #[arg(long)]
        apply: bool,
        /// Sign the artifact with cosign after push
        #[arg(long)]
        sign: bool,
        /// Path to cosign private key (omit for keyless signing via Fulcio/Rekor)
        #[arg(long)]
        key: Option<String>,
        /// Attach SLSA provenance attestation after push
        #[arg(long)]
        attest: bool,
    },
    /// Pull a module from an OCI registry
    Pull {
        /// OCI artifact reference (e.g. ghcr.io/myorg/mymodule:v1.0.0)
        #[arg(name = "ref")]
        artifact_ref: String,
        /// Directory to extract the module into
        #[arg(long)]
        dir: String,
        /// Require a cosign signature on the artifact
        #[arg(long)]
        require_signature: bool,
        /// Verify SLSA provenance attestation on the artifact
        #[arg(long)]
        verify_attestation: bool,
        /// Path to cosign public key for verification (omit for keyless)
        #[arg(long)]
        key: Option<String>,
        /// Certificate identity regexp for keyless verification
        #[arg(long)]
        certificate_identity: Option<String>,
        /// Certificate OIDC issuer regexp for keyless verification
        #[arg(long)]
        certificate_oidc_issuer: Option<String>,
    },
    /// Build a module into an OCI-ready artifact using Docker/Podman
    Build {
        /// Path to the module directory (must contain module.yaml)
        dir: String,
        /// Target platform(s), comma-separated (e.g. linux/amd64,linux/arm64)
        #[arg(long)]
        target: Option<String>,
        /// Base container image (default: ubuntu:22.04)
        #[arg(long)]
        base_image: Option<String>,
        /// OCI artifact reference — if provided, push the built artifact
        #[arg(long)]
        artifact: Option<String>,
        /// Sign the artifact after push (requires --artifact and cosign)
        #[arg(long)]
        sign: bool,
        /// Path to cosign private key for signing
        #[arg(long)]
        key: Option<String>,
    },
    /// Manage cosign signing keys for module artifacts
    Keys {
        #[command(subcommand)]
        command: ModuleKeysCommand,
    },
}

#[derive(Subcommand, Clone)]
pub enum ModuleKeysCommand {
    /// Generate a new cosign key pair
    Generate {
        /// Output directory for the key pair (default: current directory)
        #[arg(long, short)]
        dir: Option<String>,
    },
    /// List known signing keys
    #[command(alias = "ls")]
    List,
    /// Rotate signing keys: generate a new pair and re-sign specified artifacts
    Rotate {
        /// Directory containing the current cosign.key to replace
        #[arg(long, short)]
        dir: Option<String>,
        /// OCI artifact references to re-sign with the new key
        #[arg(long)]
        artifacts: Vec<String>,
    },
}

#[derive(Clone, clap::ValueEnum)]
pub enum ExportFormat {
    /// DevContainer Feature (install.sh + devcontainer-feature.json)
    Devcontainer,
}

#[derive(Subcommand)]
pub enum ModuleRegistryCommand {
    /// Add a module registry
    Add {
        /// Git URL of the registry repo (GitHub only)
        url: String,
        /// Custom name/alias (defaults to GitHub org name)
        #[arg(long)]
        name: Option<String>,
    },
    /// Remove a module registry
    Remove {
        /// Registry name
        name: String,
    },
    /// Rename a module registry (updates config references)
    Rename {
        /// Current registry name
        name: String,
        /// New name
        new_name: String,
    },
    /// List configured module registries
    #[command(alias = "ls")]
    List,
}

/// Execute the given CLI command. Returns Ok(()) on success.
pub fn execute(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    match &cli.command {
        Command::Apply(args) => cmd_apply(cli, printer, args),
        Command::Plan(args) => cmd_plan(cli, printer, args),
        Command::Status { module } => cmd_status(cli, printer, module.as_deref()),
        Command::Diff { module } => cmd_diff(cli, printer, module.as_deref()),
        Command::Log { limit, show_output } => {
            cmd_log(printer, *limit, *show_output, cli.state_dir.as_deref())
        }
        Command::Verify { module } => cmd_verify(cli, printer, module.as_deref()),
        Command::Profile { command } => match command {
            ProfileCommand::Show { name } => {
                profile::cmd_profile_show(cli, printer, name.as_deref())
            }
            ProfileCommand::List => profile::cmd_profile_list(cli, printer),
            ProfileCommand::Switch { name } => profile::cmd_profile_switch(cli, name, printer),
            ProfileCommand::Create(args) => profile::cmd_profile_create(cli, printer, args),
            ProfileCommand::Update(args) => {
                let profile_name = resolve_profile_name(cli, args.name.as_deref())?;
                profile::cmd_profile_update(cli, printer, &profile_name, args)
            }
            ProfileCommand::Edit { name } => profile::cmd_profile_edit(cli, printer, name),
            ProfileCommand::Delete { name, yes } => {
                profile::cmd_profile_delete(cli, printer, name, *yes)
            }
        },
        Command::Doctor => cmd_doctor(cli, printer),
        Command::Init {
            path,
            from,
            branch,
            name,
            apply,
            dry_run,
            yes,
            install_daemon,
            theme,
            apply_profile,
            apply_modules,
        } => init::cmd_init(
            printer,
            &init::InitArgs {
                path: path.as_deref(),
                from: from.as_deref(),
                branch,
                name: name.as_deref(),
                apply: *apply,
                dry_run: *dry_run,
                yes: *yes,
                install_daemon: *install_daemon,
                theme: theme.as_deref(),
                apply_profile: apply_profile.as_deref(),
                apply_modules,
            },
        ),
        Command::Module { command } => match command {
            ModuleCommand::List => module::cmd_module_list(cli, printer),
            ModuleCommand::Show { name, show_values } => {
                module::cmd_module_show(cli, printer, name, *show_values)
            }
            ModuleCommand::Create(args) => module::cmd_module_create(cli, printer, args),
            ModuleCommand::Update(args) => module::cmd_module_update_local(cli, printer, args),
            ModuleCommand::Edit { name } => module::cmd_module_edit(cli, printer, name),
            ModuleCommand::Delete { name, yes, purge } => {
                module::cmd_module_delete(cli, printer, name, *yes, *purge)
            }
            ModuleCommand::Add(args) => module::cmd_module_create(cli, printer, args),
            ModuleCommand::Upgrade {
                name,
                ref_,
                yes,
                allow_unsigned,
            } => module::cmd_module_upgrade(
                cli,
                printer,
                name,
                ref_.as_deref(),
                *yes,
                *allow_unsigned,
            ),
            ModuleCommand::Search { query } => module::cmd_module_search(cli, printer, query),
            ModuleCommand::Registry { command } => match command {
                ModuleRegistryCommand::Add { url, name } => {
                    module::cmd_module_registry_add(cli, printer, url, name.as_deref())
                }
                ModuleRegistryCommand::Remove { name } => {
                    module::cmd_module_registry_remove(cli, printer, name)
                }
                ModuleRegistryCommand::Rename { name, new_name } => {
                    module::cmd_module_registry_rename(cli, printer, name, new_name)
                }
                ModuleRegistryCommand::List => module::cmd_module_registry_list(cli, printer),
            },
            ModuleCommand::Export { name, format, dir } => {
                module::cmd_module_export(cli, printer, name, format, dir.as_deref())
            }
            ModuleCommand::Push {
                dir,
                artifact,
                platform,
                apply,
                sign,
                key,
                attest,
            } => module::cmd_module_push(
                printer,
                dir,
                artifact,
                module::PushOptions {
                    platform: platform.as_deref(),
                    apply: *apply,
                    sign: *sign,
                    key: key.as_deref(),
                    attest: *attest,
                },
            ),
            ModuleCommand::Pull {
                artifact_ref,
                dir,
                require_signature,
                verify_attestation,
                key,
                certificate_identity,
                certificate_oidc_issuer,
            } => module::cmd_module_pull(
                printer,
                artifact_ref,
                dir,
                *require_signature,
                *verify_attestation,
                cfgd_core::oci::VerifyOptions {
                    key: key.as_deref(),
                    identity: certificate_identity.as_deref(),
                    issuer: certificate_oidc_issuer.as_deref(),
                },
            ),
            ModuleCommand::Build {
                dir,
                target,
                base_image,
                artifact,
                sign,
                key,
            } => module::cmd_module_build(
                printer,
                dir,
                target.as_deref(),
                base_image.as_deref(),
                artifact.as_deref(),
                *sign,
                key.as_deref(),
            ),
            ModuleCommand::Keys { command } => match command {
                ModuleKeysCommand::Generate { dir } => {
                    module::cmd_module_keys_generate(printer, dir.as_deref())
                }
                ModuleKeysCommand::List => module::cmd_module_keys_list(printer),
                ModuleKeysCommand::Rotate { dir, artifacts } => {
                    module::cmd_module_keys_rotate(printer, dir.as_deref(), artifacts)
                }
            },
        },
        Command::Sync => cmd_sync(cli, printer),
        Command::Pull => cmd_pull(cli, printer),
        Command::Daemon { command } => cmd_daemon(cli, printer, command.as_ref()),
        Command::Secret { command } => match command {
            SecretCommand::Encrypt { file } => cmd_secret_encrypt(cli, printer, file),
            SecretCommand::Decrypt { file } => cmd_secret_decrypt(cli, printer, file),
            SecretCommand::Edit { file } => cmd_secret_edit(cli, printer, file),
            SecretCommand::Init => cmd_secret_init(cli, printer),
        },
        Command::Source { command } => match command {
            SourceCommand::Add(args) => cmd_source_add(cli, printer, args),
            SourceCommand::Priority { name, value } => {
                cmd_source_priority(cli, printer, name, *value)
            }
            SourceCommand::List => cmd_source_list(cli, printer),
            SourceCommand::Show { name } => cmd_source_show(cli, printer, name),
            SourceCommand::Remove {
                name,
                keep_all,
                remove_all,
                yes,
            } => cmd_source_remove(
                cli,
                printer,
                name,
                *keep_all || (*yes && !*remove_all),
                *remove_all,
            ),
            SourceCommand::Update { name } => cmd_source_update(cli, printer, name.as_deref()),
            SourceCommand::Override {
                source,
                action,
                path,
                value,
            } => cmd_source_override(cli, printer, source, action, path, value.as_deref()),
            SourceCommand::Replace { old_name, new_url } => {
                cmd_source_replace(cli, printer, old_name, new_url)
            }
            SourceCommand::Edit => cmd_source_edit(cli, printer),
            SourceCommand::Create {
                name,
                description,
                version,
            } => cmd_source_create(
                cli,
                printer,
                name.as_deref(),
                description.as_deref(),
                version.as_deref(),
            ),
        },
        Command::Explain {
            resource,
            recursive,
        } => explain::cmd_explain(printer, resource.as_deref(), *recursive),
        Command::Upgrade { check } => cmd_upgrade(printer, *check),
        Command::Decide {
            action,
            resource,
            source,
            all,
        } => cmd_decide(
            printer,
            action,
            resource.as_deref(),
            source.as_deref(),
            *all,
            cli.state_dir.as_deref(),
        ),
        Command::Config { command } => match command {
            ConfigCommand::Show => cmd_config_show(cli, printer),
            ConfigCommand::Edit => cmd_config_edit(cli, printer),
            ConfigCommand::Get { key } => cmd_config_get(cli, printer, key),
            ConfigCommand::Set { key, value } => cmd_config_set(cli, printer, key, value),
            ConfigCommand::Unset { key } => cmd_config_unset(cli, printer, key),
        },
        Command::Workflow { command } => match command {
            WorkflowCommand::Generate { force } => cmd_workflow_generate(cli, printer, *force),
        },
        Command::Checkin {
            server_url,
            api_key,
            device_id,
        } => cmd_checkin(
            cli,
            printer,
            server_url,
            api_key.as_deref(),
            device_id.as_deref(),
        ),
        Command::Enroll {
            server_url,
            token,
            ssh_key,
            gpg_key,
            username,
        } => init::cmd_enroll(
            printer,
            server_url,
            token.as_deref(),
            ssh_key.as_deref(),
            gpg_key.as_deref(),
            username.as_deref(),
        ),
        Command::Completions { shell } => {
            clap_complete::generate(*shell, &mut Cli::command(), "cfgd", &mut std::io::stdout());
            Ok(())
        }
        Command::Generate(args) => generate::cmd_generate(cli, printer, args),
        Command::Rollback { apply_id, yes } => {
            cmd_rollback(printer, *apply_id, *yes, cli.state_dir.as_deref())
        }
        Command::McpServer => crate::mcp::server::run_mcp_server(&cli.config),
        Command::Compliance { command } => match command {
            None => cmd_compliance_snapshot(cli, printer),
            Some(ComplianceCommand::Export) => cmd_compliance_export(cli, printer),
            Some(ComplianceCommand::History { since }) => {
                cmd_compliance_history(cli, printer, since.as_deref())
            }
            Some(ComplianceCommand::Diff { id1, id2 }) => {
                cmd_compliance_diff(cli, printer, *id1, *id2)
            }
        },
    }
}

fn load_config_and_profile(
    cli: &Cli,
    printer: &Printer,
) -> anyhow::Result<(CfgdConfig, ResolvedProfile)> {
    let cfg = config::load_config(&cli.config)?;
    let profile_name = match cli.profile.as_deref() {
        Some(p) => p,
        None => cfg.active_profile()?,
    };

    printer.key_value("Config", &cli.config.display().to_string());
    printer.key_value("Profile", profile_name);

    let resolved = config::resolve_profile(profile_name, &profiles_dir(cli))?;
    Ok((cfg, resolved))
}

/// Parse a `--package` flag value. If it contains `:` and the prefix is a known
/// package manager name, split into (Some(manager), package). Otherwise treat
/// the entire string as a bare package name.
pub(super) fn parse_package_flag(s: &str, known_managers: &[&str]) -> (Option<String>, String) {
    if let Some((prefix, suffix)) = s.split_once(':')
        && !prefix.is_empty()
        && !suffix.is_empty()
        && known_managers.contains(&prefix)
    {
        return (Some(prefix.to_string()), suffix.to_string());
    }
    (None, s.to_string())
}

/// Build an empty ResolvedProfile for module-only operations that don't need
/// a real profile (status --module, verify --module, apply --module without profile).
fn empty_resolved_profile(module_name: &str) -> ResolvedProfile {
    ResolvedProfile {
        layers: Vec::new(),
        merged: MergedProfile {
            modules: vec![module_name.to_string()],
            ..Default::default()
        },
    }
}

/// Collect known package manager names from the registry.
pub(super) fn known_manager_names() -> Vec<String> {
    packages::all_package_managers()
        .iter()
        .map(|m| m.name().to_string())
        .collect()
}

/// Parse a `--file` value into (source_path, target_path).
/// - `<path>` without `:` → adopt in place: source=path, target=path
/// - `<source>:<target>` → explicit mapping
fn parse_file_spec(spec: &str) -> anyhow::Result<(PathBuf, PathBuf)> {
    // On Windows, paths like C:\foo contain colons that are NOT source:target separators.
    // A drive letter is a single ASCII letter followed by `:` and `\` or `/`.
    // We skip the first colon if it's part of a drive letter prefix.
    let split_pos = spec.char_indices().find_map(|(i, c)| {
        if c == ':' {
            // Skip if this colon is at position 1 and preceded by a single ASCII letter
            // (i.e., a Windows drive letter like C: or D:)
            if i == 1 && spec.as_bytes()[0].is_ascii_alphabetic() {
                return None;
            }
            Some(i)
        } else {
            None
        }
    });

    if let Some(pos) = split_pos {
        let source = &spec[..pos];
        let target = &spec[pos + 1..];
        // Target may also start with a drive letter — handle C:\path after the separator
        if source.is_empty() {
            anyhow::bail!("empty source in file spec: {}", spec);
        }
        if target.is_empty() {
            anyhow::bail!("empty target in file spec: {}", spec);
        }
        Ok((
            cfgd_core::expand_tilde(Path::new(source)),
            cfgd_core::expand_tilde(Path::new(target)),
        ))
    } else {
        let expanded = cfgd_core::expand_tilde(Path::new(spec));
        Ok((expanded.clone(), expanded))
    }
}

/// Adopt files: copy into `repo_dir`, symlink back from source location.
/// Returns `(basename, deploy_target)` pairs — basename is the filename in the repo,
/// deploy_target is where the file should be deployed on the machine.
fn copy_files_to_dir(
    file_specs: &[String],
    repo_dir: &Path,
) -> anyhow::Result<Vec<(String, PathBuf)>> {
    let mut results = Vec::new();
    for spec in file_specs {
        let (source, target) = parse_file_spec(spec)?;
        if !source.exists() {
            anyhow::bail!("File not found: {}", source.display());
        }

        // Reject sources in system directories to prevent path traversal attacks.
        // module create --file copies the source then replaces it with a symlink,
        // so importing /etc/passwd would delete it and replace with a symlink.
        let canonical_source = source
            .canonicalize()
            .unwrap_or_else(|_| source.to_path_buf());
        // These prefixes are checked against both the original and canonical path.
        // /var is omitted here because on macOS /var/folders is the user temp
        // directory — tempfile crates produce paths under /var/folders/… which
        // must remain importable.  /var on Linux is covered via canonical_source
        // (Linux does not redirect /var, so original == canonical there).
        let forbidden_prefixes: &[&str] = &[
            "/etc",
            "/usr",
            "/bin",
            "/sbin",
            "/boot",
            "/sys",
            "/proc",
            "/lib",
            "/lib64",
            "/dev",
            "/snap",
            // macOS symlinks /etc → /private/etc; check canonical to catch traversal.
            "/private/etc",
        ];
        for prefix in forbidden_prefixes {
            if source.starts_with(prefix) || canonical_source.starts_with(prefix) {
                anyhow::bail!(
                    "Refusing to import '{}': source is in system directory {}",
                    source.display(),
                    prefix
                );
            }
        }
        // Check /var against the canonical path only. On Linux canonical == original
        // so this catches system /var correctly. On macOS /var symlinks to
        // /private/var, so temp files (/var/folders/…) canonicalize to
        // /private/var/folders/… which does not start with /var — safe to allow.
        if canonical_source.starts_with("/var") {
            anyhow::bail!(
                "Refusing to import '{}': source is in system directory /var",
                source.display()
            );
        }

        std::fs::create_dir_all(repo_dir)?;
        let file_name = source
            .file_name()
            .ok_or_else(|| anyhow::anyhow!("Invalid file path: {}", source.display()))?;
        let dest = repo_dir.join(file_name);
        if source.is_dir() {
            cfgd_core::copy_dir_recursive(&source, &dest)?;
        } else {
            std::fs::copy(&source, &dest)?;
        }
        // Symlink back from source location to repo copy so the user's
        // dotfile now points into the cfgd-managed directory.
        if source.exists() && !source.is_symlink() {
            if source.is_dir() {
                std::fs::remove_dir_all(&source)?;
            } else {
                std::fs::remove_file(&source)?;
            }
            cfgd_core::create_symlink(&dest, &source)?;
        }
        results.push((file_name.to_string_lossy().to_string(), target));
    }
    Ok(results)
}

/// Add a path to `.gitignore` in `config_dir` if not already present.
fn add_to_gitignore(config_dir: &Path, path: &str) -> anyhow::Result<()> {
    let gitignore = config_dir.join(".gitignore");
    let existing = if gitignore.exists() {
        std::fs::read_to_string(&gitignore)?
    } else {
        String::new()
    };
    // Check if already listed (exact line match)
    if existing.lines().any(|line| line.trim() == path) {
        return Ok(());
    }
    let mut content = existing;
    if !content.is_empty() && !content.ends_with('\n') {
        content.push('\n');
    }
    content.push_str(path);
    content.push('\n');
    cfgd_core::atomic_write_str(&gitignore, &content)?;
    Ok(())
}

/// Extract secret backend name and age key path from config.
/// Returns ("sops", None) as defaults when no secrets config is present.
fn secret_backend_from_config(cfg: Option<&CfgdConfig>) -> (String, Option<PathBuf>) {
    if let Some(cfg) = cfg
        && let Some(ref secrets_cfg) = cfg.spec.secrets
    {
        let name = secrets_cfg.backend.as_str().to_string();
        let key = secrets_cfg.sops.as_ref().and_then(|s| s.age_key.clone());
        (name, key)
    } else {
        ("sops".to_string(), None)
    }
}

fn build_registry() -> ProviderRegistry {
    build_registry_with_config(None)
}

/// DaemonHooks implementation for the workstation binary.
/// Provides concrete provider wiring so cfgd-core's daemon can plan packages/files.
struct WorkstationDaemonHooks;

impl cfgd_core::daemon::DaemonHooks for WorkstationDaemonHooks {
    fn build_registry(&self, config: &CfgdConfig) -> ProviderRegistry {
        build_registry_with_config(Some(config))
    }

    fn plan_files(
        &self,
        config_dir: &std::path::Path,
        resolved: &ResolvedProfile,
    ) -> cfgd_core::errors::Result<Vec<FileAction>> {
        let mut fm = CfgdFileManager::new(config_dir, resolved)?;
        let cfg = config::load_config(&config_dir.join("cfgd.yaml"))?;
        fm.set_global_strategy(cfg.spec.file_strategy);
        let (backend_name, age_key_path) = secret_backend_from_config(Some(&cfg));
        let backend = secrets::build_secret_backend(&backend_name, age_key_path, Some(config_dir));
        let providers = secrets::build_secret_providers();
        fm.set_secret_providers(Some(backend), providers);
        fm.plan(&resolved.merged)
    }

    fn plan_packages(
        &self,
        profile: &cfgd_core::config::MergedProfile,
        managers: &[&dyn cfgd_core::providers::PackageManager],
    ) -> cfgd_core::errors::Result<Vec<cfgd_core::providers::PackageAction>> {
        packages::plan_packages(profile, managers)
    }

    fn extend_registry_custom_managers(
        &self,
        registry: &mut ProviderRegistry,
        packages: &cfgd_core::config::PackagesSpec,
    ) {
        registry
            .package_managers
            .extend(crate::packages::custom_managers(&packages.custom));
    }

    fn expand_tilde(&self, path: &std::path::Path) -> std::path::PathBuf {
        cfgd_core::expand_tilde(path)
    }
}

fn build_registry_with_profile(spec: &cfgd_core::config::PackagesSpec) -> ProviderRegistry {
    build_registry_with_config_and_packages(None, Some(spec))
}

fn build_registry_with_config(cfg: Option<&CfgdConfig>) -> ProviderRegistry {
    build_registry_with_config_and_packages(cfg, None)
}

fn build_registry_with_config_and_packages(
    cfg: Option<&CfgdConfig>,
    packages: Option<&cfgd_core::config::PackagesSpec>,
) -> ProviderRegistry {
    let mut registry = ProviderRegistry::new();
    registry.package_managers = packages::all_package_managers();

    // Register system configurators based on OS
    use crate::system::*;

    // ShellConfigurator: `chsh` on Unix, Windows Terminal settings.json on Windows
    if cfg!(unix) || cfg!(windows) {
        registry
            .system_configurators
            .push(Box::new(ShellConfigurator));
    }

    if cfg!(target_os = "macos") {
        registry
            .system_configurators
            .push(Box::new(MacosDefaultsConfigurator));
        registry
            .system_configurators
            .push(Box::new(LaunchAgentConfigurator));
    }

    if cfg!(target_os = "linux") {
        registry
            .system_configurators
            .push(Box::new(SystemdUnitConfigurator));
        // Linux desktop configurators — each checks CLI availability at runtime via is_available()
        registry
            .system_configurators
            .push(Box::new(GsettingsConfigurator));
        registry
            .system_configurators
            .push(Box::new(KdeConfigConfigurator));
        registry
            .system_configurators
            .push(Box::new(XfconfConfigurator));
    }

    // Environment configurator is available on Unix and Windows
    if cfg!(unix) || cfg!(windows) {
        registry
            .system_configurators
            .push(Box::new(EnvironmentConfigurator));
    }

    // Windows registry configurator
    if cfg!(windows) {
        registry
            .system_configurators
            .push(Box::new(WindowsRegistryConfigurator));
    }

    // Windows service configurator
    if cfg!(windows) {
        registry
            .system_configurators
            .push(Box::new(WindowsServiceConfigurator));
    }

    // SSH key configurator — available unconditionally (ssh-keygen on all platforms)
    registry
        .system_configurators
        .push(Box::new(SshKeysConfigurator));

    // GPG key configurator — available on any platform where gpg is installed
    if cfgd_core::command_available("gpg") {
        registry
            .system_configurators
            .push(Box::new(GpgKeysConfigurator));
    }

    // Git configurator — cross-platform, gated on git being available at runtime
    if cfgd_core::command_available("git") {
        registry
            .system_configurators
            .push(Box::new(GitConfigurator));
    }

    // Node/infrastructure system configurators (Linux-only, gated at compile time)
    #[cfg(unix)]
    {
        registry
            .system_configurators
            .push(Box::new(SysctlConfigurator));
        registry
            .system_configurators
            .push(Box::new(KernelModuleConfigurator));
        registry
            .system_configurators
            .push(Box::new(ContainerdConfigurator));
        registry
            .system_configurators
            .push(Box::new(KubeletConfigurator));
        registry
            .system_configurators
            .push(Box::new(AppArmorConfigurator));
        registry
            .system_configurators
            .push(Box::new(SeccompConfigurator));
        registry
            .system_configurators
            .push(Box::new(CertificateConfigurator));
    }

    // Register secret backend and providers
    let (backend_name, age_key_path) = secret_backend_from_config(cfg);
    registry.secret_backend = Some(secrets::build_secret_backend(
        &backend_name,
        age_key_path,
        None,
    ));
    registry.secret_providers = secrets::build_secret_providers();

    // Set global file strategy from config
    if let Some(cfg) = cfg {
        registry.default_file_strategy = cfg.spec.file_strategy;
    }

    // Extend with custom package managers from profile packages spec
    if let Some(spec) = packages {
        registry
            .package_managers
            .extend(packages::custom_managers(&spec.custom));
    }

    registry
}

#[cfg(unix)]
fn print_daemon_install_success(printer: &Printer) {
    if cfg!(target_os = "macos") {
        printer.success("Installed launchd service: com.cfgd.daemon");
        printer.info("Load with: launchctl load ~/Library/LaunchAgents/com.cfgd.daemon.plist");
    } else {
        printer.success("Installed systemd user service: cfgd.service");
        printer.info("Enable with: systemctl --user enable --now cfgd.service");
    }
}

fn open_state_store(state_dir: Option<&Path>) -> anyhow::Result<StateStore> {
    if let Some(dir) = state_dir {
        std::fs::create_dir_all(dir)?;
        Ok(StateStore::open(&dir.join("cfgd.db"))?)
    } else {
        Ok(StateStore::open_default()?)
    }
}

/// Display apply result summary via Printer. Returns the status for caller control flow.
fn print_apply_result(
    result: &cfgd_core::reconciler::ApplyResult,
    printer: &Printer,
) -> cfgd_core::state::ApplyStatus {
    match result.status {
        cfgd_core::state::ApplyStatus::Success => {
            printer.success(&format!(
                "Apply complete — {} action(s) succeeded",
                result.succeeded()
            ));
        }
        cfgd_core::state::ApplyStatus::Partial => {
            printer.warning(&format!(
                "Apply partially complete — {} succeeded, {} failed",
                result.succeeded(),
                result.failed()
            ));
        }
        cfgd_core::state::ApplyStatus::Failed => {
            printer.error(&format!(
                "Apply failed — {} action(s) failed",
                result.failed()
            ));
        }
        cfgd_core::state::ApplyStatus::InProgress => {
            printer.warning("Apply still in progress (unexpected state)");
        }
    }
    result.status.clone()
}

fn cmd_apply(cli: &Cli, printer: &Printer, args: &ApplyArgs) -> anyhow::Result<()> {
    // --from: clone from git source or use local path as config directory.
    // When --config points to a non-default path, use its parent as the clone target
    // so the cloned config ends up where the user expects.
    if let Some(from) = &args.from {
        let cli_config_dir = cli.config.parent().map(|p| p.to_path_buf());
        let default_dir = cfgd_core::default_config_dir();
        let target = if let Some(ref dir) = cli_config_dir {
            if *dir != default_dir && !cli.config.exists() {
                Some(dir.as_path())
            } else {
                None
            }
        } else {
            None
        };
        init::resolve_from(from, target, "master", printer)?;
    }

    let dry_run = args.dry_run;
    let phase = args.phase.as_deref();
    let yes = args.yes;
    let skip = &args.skip;
    let only = &args.only;
    let module_filter = args.module.as_deref();
    if dry_run {
        printer.header("Plan");
    } else {
        printer.header("Apply");
    }

    let config_dir = config_dir(cli);
    let state = open_state_store(cli.state_dir.as_deref())?;

    // When --module is set, try loading profile but fall back to empty if none configured
    let (cfg, resolved) = if let Some(mod_name) = module_filter {
        match load_config_and_profile(cli, printer) {
            Ok(pair) => pair,
            Err(e) => {
                tracing::debug!("profile load failed, using module-only mode: {}", e);
                let cfg =
                    config::load_config(&cli.config).unwrap_or_else(|_| config::minimal_config());
                let resolved = empty_resolved_profile(mod_name);
                printer.key_value("Config", &cli.config.display().to_string());
                printer.key_value("Profile", "(module-only)");
                (cfg, resolved)
            }
        }
    } else {
        load_config_and_profile(cli, printer)?
    };

    let mut registry = build_registry_with_config(Some(&cfg));

    // Validate phase name if provided
    let phase_filter = if let Some(p) = phase {
        match p.parse::<PhaseName>() {
            Ok(pn) => Some(pn),
            Err(_) => {
                anyhow::bail!(
                    "Unknown phase '{}'. Valid phases: pre-scripts, env, modules, system, packages, files, secrets, post-scripts",
                    p
                );
            }
        }
    } else {
        None
    };

    // Compose with sources if configured
    let (source_env, source_commits) = if !cfg.spec.sources.is_empty() {
        let composition_result = compose_with_sources(cli, &cfg, &resolved, printer)?;
        let se = composition_result.source_env;
        let sc = composition_result.source_commits;
        ((Some(composition_result.resolved), se), sc)
    } else {
        (
            (None, std::collections::HashMap::new()),
            std::collections::HashMap::new(),
        )
    };
    let mut effective_resolved = source_env.0.unwrap_or(resolved);
    let source_env = source_env.1;

    // Resolve manifest files (Brewfile, package.json, etc.) into package lists
    packages::resolve_manifest_packages(&mut effective_resolved.merged.packages, &config_dir)?;

    // Extend registry with custom package managers from config
    registry.package_managers.extend(packages::custom_managers(
        &effective_resolved.merged.packages.custom,
    ));

    // Resolve modules
    let module_names = if let Some(mod_name) = module_filter {
        vec![mod_name.to_string()]
    } else {
        effective_resolved.merged.modules.clone()
    };

    let resolved_modules = if !module_names.is_empty() {
        let platform = Platform::detect();
        let mgr_map = managers_map(&registry);
        let cache_base = modules::default_module_cache_dir()?;
        match modules::resolve_modules(
            &module_names,
            &config_dir,
            &cache_base,
            &platform,
            &mgr_map,
            printer,
        ) {
            Ok(mods) => mods,
            Err(e) if module_filter.is_some() => {
                tracing::debug!("module filter '{}' not found: {}", module_names[0], e);
                Vec::new()
            }
            Err(e) => return Err(e.into()),
        }
    } else {
        Vec::new()
    };

    // If --module is set, skip profile-level packages/files
    let module_only = module_filter.is_some();

    // In dry-run mode we don't need secret providers wired up — just plan files for display.
    // In apply mode we wire up the full file manager with secret providers.
    let (pkg_actions, file_actions, dry_run_fm) = if module_only {
        (Vec::new(), Vec::new(), None)
    } else {
        let all_managers: Vec<&dyn cfgd_core::providers::PackageManager> = registry
            .package_managers
            .iter()
            .map(|m| m.as_ref())
            .collect();
        let pkg = packages::plan_packages(&effective_resolved.merged, &all_managers)?;

        let mut fm = CfgdFileManager::new(&config_dir, &effective_resolved)?;
        fm.set_global_strategy(cfg.spec.file_strategy);
        if !source_env.is_empty() {
            fm.set_source_env(&source_env);
        }

        if !dry_run {
            let (backend_name, age_key_path) = secret_backend_from_config(Some(&cfg));
            fm.set_secret_providers(
                Some(secrets::build_secret_backend(
                    &backend_name,
                    age_key_path,
                    Some(&config_dir),
                )),
                secrets::build_secret_providers(),
            );
        }

        let fa = fm.plan(&effective_resolved.merged)?;

        if dry_run {
            // Keep fm around for diff display but don't register it
            (pkg, fa, Some(fm))
        } else {
            // Register the file manager so the reconciler delegates through the trait
            registry.file_manager = Some(Box::new(fm));
            (pkg, fa, None)
        }
    };

    let reconciler = Reconciler::new(&registry, &state);
    let mut plan = reconciler.plan(
        &effective_resolved,
        file_actions,
        pkg_actions,
        resolved_modules.clone(),
        ReconcileContext::Apply,
    )?;

    // Apply --skip / --only filters
    filter_plan(&mut plan, skip, only);

    // Strip script phases when --skip-scripts is set
    if args.skip_scripts {
        strip_scripts_from_plan(&mut plan);
    }

    if dry_run {
        display_plan_preview(
            &plan,
            printer,
            &state,
            "apply",
            phase_filter.as_ref(),
            dry_run_fm.as_ref(),
        );
        return Ok(());
    }

    // --- Apply mode ---

    // Handle unmanaged file targets: if a target exists as a non-cfgd file, prompt to
    // adopt (proceed), backup (rename to .cfgd-backup), or skip.
    handle_unmanaged_file_targets(&mut plan, &config_dir, &state, printer, yes)?;

    // Check if filtered plan has actions
    let has_actions = if let Some(ref pf) = phase_filter {
        plan.phases
            .iter()
            .any(|p| &p.name == pf && !p.actions.is_empty())
    } else {
        !plan.is_empty()
    };

    if !has_actions {
        printer.success(MSG_NOTHING_TO_DO);
        return Ok(());
    }

    // Show what will change
    printer.newline();
    for phase_item in &plan.phases {
        if let Some(ref pf) = phase_filter
            && &phase_item.name != pf
        {
            continue;
        }
        let items = reconciler::format_plan_items(phase_item);
        if !items.is_empty() {
            printer.plan_phase(phase_item.name.display_name(), &items);
        }
    }

    for w in &plan.warnings {
        printer.warning(w);
    }

    // Confirm
    if !yes {
        printer.newline();
        let confirmed = printer
            .prompt_confirm("Apply these changes?")
            .unwrap_or(false);
        if !confirmed {
            printer.info("Aborted");
            return Ok(());
        }
    }

    printer.newline();

    // Acquire apply lock to prevent concurrent applies
    let lock_state_dir;
    let apply_lock_dir: &std::path::Path = if let Some(dir) = cli.state_dir.as_deref() {
        dir
    } else {
        lock_state_dir = cfgd_core::state::default_state_dir()
            .map_err(|e| anyhow::anyhow!("cannot determine state directory: {}", e))?;
        &lock_state_dir
    };
    let _apply_lock = cfgd_core::acquire_apply_lock(apply_lock_dir)?;

    // Apply
    let result = reconciler.apply(
        &plan,
        &effective_resolved,
        &config_dir,
        printer,
        phase_filter.as_ref(),
        &resolved_modules,
        ReconcileContext::Apply,
        args.skip_scripts,
    )?;

    printer.newline();
    print_apply_result(&result, printer);

    // Link source commits to this apply for provenance tracking
    if !source_commits.is_empty() {
        for (source_name, commit_hash) in &source_commits {
            if let Err(e) = state.record_source_apply(source_name, result.apply_id, commit_hash) {
                tracing::warn!(
                    source = %source_name,
                    commit = %commit_hash,
                    error = %e,
                    "failed to record source apply"
                );
            }
        }
    }

    // Prune old backups — keep last 10 applies' worth
    if let Ok(state) = open_state_store(cli.state_dir.as_deref()) {
        let _ = state.prune_old_backups(10);
    }

    Ok(())
}

/// Derive a short action type string from a reconciler Action.
fn action_type_str(action: &reconciler::Action) -> &'static str {
    match action {
        reconciler::Action::File(fa) => match fa {
            FileAction::Create { .. } => "create",
            FileAction::Update { .. } => "update",
            FileAction::Delete { .. } => "delete",
            FileAction::SetPermissions { .. } => "chmod",
            FileAction::Skip { .. } => "skip",
        },
        reconciler::Action::Package(pa) => match pa {
            PackageAction::Bootstrap { .. } => "bootstrap",
            PackageAction::Install { .. } => "install",
            PackageAction::Uninstall { .. } => "uninstall",
            PackageAction::Skip { .. } => "skip",
        },
        reconciler::Action::Secret(sa) => match sa {
            SecretAction::Decrypt { .. } => "decrypt",
            SecretAction::Resolve { .. } => "resolve",
            SecretAction::ResolveEnv { .. } => "resolve-env",
            SecretAction::Skip { .. } => "skip",
        },
        reconciler::Action::System(sa) => match sa {
            reconciler::SystemAction::SetValue { .. } => "set",
            reconciler::SystemAction::Skip { .. } => "skip",
        },
        reconciler::Action::Script(_) => "run",
        reconciler::Action::Module(ma) => match &ma.kind {
            reconciler::ModuleActionKind::InstallPackages { .. } => "install",
            reconciler::ModuleActionKind::DeployFiles { .. } => "deploy",
            reconciler::ModuleActionKind::RunScript { .. } => "run",
            reconciler::ModuleActionKind::Skip { .. } => "skip",
        },
        reconciler::Action::Env(ea) => match ea {
            reconciler::EnvAction::WriteEnvFile { .. } => "write",
            reconciler::EnvAction::InjectSourceLine { .. } => "inject",
        },
    }
}

/// Build a PlanOutput from a reconciler Plan, applying an optional phase filter.
fn build_plan_output(
    plan: &reconciler::Plan,
    context_name: &str,
    phase_filter: Option<&PhaseName>,
) -> PlanOutput {
    let mut phases = Vec::new();
    for phase_item in &plan.phases {
        if let Some(pf) = phase_filter
            && &phase_item.name != pf
        {
            continue;
        }
        let items = reconciler::format_plan_items(phase_item);
        let actions: Vec<PlanActionOutput> = phase_item
            .actions
            .iter()
            .zip(items.iter())
            .map(|(action, desc)| PlanActionOutput {
                description: desc.clone(),
                action_type: action_type_str(action).to_string(),
            })
            .collect();
        phases.push(PlanPhaseOutput {
            phase: phase_item.name.display_name().to_string(),
            actions,
        });
    }
    let total_actions = phases.iter().map(|p| p.actions.len()).sum();
    PlanOutput {
        context: context_name.to_string(),
        phases,
        total_actions,
        warnings: plan.warnings.clone(),
    }
}

/// Strip all script-related actions from a plan.
/// Removes PreScripts/PostScripts phases, module-level RunScript actions,
/// and script-based package installs (manager: "script").
fn strip_scripts_from_plan(plan: &mut reconciler::Plan) {
    plan.phases
        .retain(|p| !matches!(p.name, PhaseName::PreScripts | PhaseName::PostScripts));
    for phase in &mut plan.phases {
        if phase.name == PhaseName::Modules {
            phase.actions.retain(|a| match a {
                reconciler::Action::Module(reconciler::ModuleAction {
                    kind: reconciler::ModuleActionKind::RunScript { .. },
                    ..
                }) => false,
                reconciler::Action::Module(reconciler::ModuleAction {
                    kind: reconciler::ModuleActionKind::InstallPackages { resolved },
                    ..
                }) => resolved.first().is_none_or(|p| p.manager != "script"),
                _ => true,
            });
        }
    }
}

/// Display a reconciliation plan in table mode.
/// Used by both `cmd_plan` and `cmd_apply --dry-run`.
pub(super) fn display_plan_table(
    plan: &reconciler::Plan,
    printer: &Printer,
    phase_filter: Option<&PhaseName>,
) {
    for phase_item in &plan.phases {
        if let Some(pf) = phase_filter
            && &phase_item.name != pf
        {
            continue;
        }
        let items = reconciler::format_plan_items(phase_item);
        printer.plan_phase(phase_item.name.display_name(), &items);
    }
}

/// Display the full plan output: pending decisions, structured/table output,
/// file diffs, warnings, and summary line.
/// Used by both `cmd_plan` and `cmd_apply --dry-run`.
fn display_plan_preview(
    plan: &reconciler::Plan,
    printer: &Printer,
    state: &cfgd_core::state::StateStore,
    context: &str,
    phase_filter: Option<&PhaseName>,
    dry_run_fm: Option<&CfgdFileManager>,
) {
    // Show pending decisions (not included in this plan)
    if let Ok(pending) = state.pending_decisions()
        && !pending.is_empty()
    {
        printer.newline();
        printer.subheader("Pending Decisions (not included in this plan)");
        for d in &pending {
            printer.info(&format!(
                "  {} {} — {} by {} (run `cfgd decide accept/reject`)",
                d.tier, d.resource, d.action, d.source,
            ));
        }
    }

    // Build structured output
    let plan_output = build_plan_output(plan, context, phase_filter);

    if printer.write_structured(&plan_output) {
        return;
    }

    // Table mode display
    printer.newline();
    display_plan_table(plan, printer, phase_filter);

    // Show diffs for file updates
    if let Some(fm) = dry_run_fm {
        for phase_item in &plan.phases {
            if phase_item.name != PhaseName::Files {
                continue;
            }
            for action in &phase_item.actions {
                if let reconciler::Action::File(FileAction::Update { source, target, .. }) = action
                    && let Ok(target_content) = std::fs::read_to_string(target)
                {
                    let source_content = if crate::files::is_tera_template(source) {
                        fm.render_template_for_display(source).unwrap_or_default()
                    } else {
                        std::fs::read_to_string(source).unwrap_or_default()
                    };
                    printer.newline();
                    printer.subheader(&format!("{}", target.display()));
                    printer.diff(&target_content, &source_content);
                }
            }
        }
    }

    for w in &plan.warnings {
        printer.warning(w);
    }

    printer.newline();
    if plan_output.total_actions == 0 {
        printer.success(MSG_NOTHING_TO_DO);
    } else {
        printer.info(&format!("{} action(s) planned", plan_output.total_actions));
    }
}

fn cmd_plan(cli: &Cli, printer: &Printer, args: &PlanArgs) -> anyhow::Result<()> {
    // Parse --context
    let reconcile_context = match args.context.as_str() {
        "apply" => ReconcileContext::Apply,
        "reconcile" => ReconcileContext::Reconcile,
        other => {
            anyhow::bail!(
                "Unknown context '{}'. Valid values: apply, reconcile",
                other
            );
        }
    };

    printer.header("Plan");

    let config_dir = config_dir(cli);
    let state = open_state_store(cli.state_dir.as_deref())?;
    let module_filter = args.module.as_deref();

    // Load config and profile — same pattern as cmd_apply
    let (cfg, resolved) = if let Some(mod_name) = module_filter {
        match load_config_and_profile(cli, printer) {
            Ok(pair) => pair,
            Err(e) => {
                tracing::debug!("profile load failed, using module-only mode: {}", e);
                let cfg =
                    config::load_config(&cli.config).unwrap_or_else(|_| config::minimal_config());
                let resolved = empty_resolved_profile(mod_name);
                printer.key_value("Config", &cli.config.display().to_string());
                printer.key_value("Profile", "(module-only)");
                (cfg, resolved)
            }
        }
    } else {
        load_config_and_profile(cli, printer)?
    };

    let mut registry = build_registry_with_config(Some(&cfg));

    // Validate phase name if provided
    let phase_filter = if let Some(ref p) = args.phase {
        match p.parse::<PhaseName>() {
            Ok(pn) => Some(pn),
            Err(_) => {
                anyhow::bail!(
                    "Unknown phase '{}'. Valid phases: pre-scripts, env, modules, system, packages, files, secrets, post-scripts",
                    p
                );
            }
        }
    } else {
        None
    };

    // Compose with sources if configured
    let source_env = if !cfg.spec.sources.is_empty() {
        let composition_result = compose_with_sources(cli, &cfg, &resolved, printer)?;
        let se = composition_result.source_env;
        (Some(composition_result.resolved), se)
    } else {
        (None, std::collections::HashMap::new())
    };
    let mut effective_resolved = source_env.0.unwrap_or(resolved);
    let source_env = source_env.1;

    // Resolve manifest files (Brewfile, package.json, etc.) into package lists
    packages::resolve_manifest_packages(&mut effective_resolved.merged.packages, &config_dir)?;

    // Extend registry with custom package managers from config
    registry.package_managers.extend(packages::custom_managers(
        &effective_resolved.merged.packages.custom,
    ));

    // Resolve modules
    let module_names = if let Some(mod_name) = module_filter {
        vec![mod_name.to_string()]
    } else {
        effective_resolved.merged.modules.clone()
    };

    let resolved_modules = if !module_names.is_empty() {
        let platform = Platform::detect();
        let mgr_map = managers_map(&registry);
        let cache_base = modules::default_module_cache_dir()?;
        match modules::resolve_modules(
            &module_names,
            &config_dir,
            &cache_base,
            &platform,
            &mgr_map,
            printer,
        ) {
            Ok(mods) => mods,
            Err(e) if module_filter.is_some() => {
                tracing::debug!("module filter '{}' not found: {}", module_names[0], e);
                Vec::new()
            }
            Err(e) => return Err(e.into()),
        }
    } else {
        Vec::new()
    };

    let module_only = module_filter.is_some();

    // Plan-only mode: no secret providers needed
    let (pkg_actions, file_actions, dry_run_fm) = if module_only {
        (Vec::new(), Vec::new(), None)
    } else {
        let all_managers: Vec<&dyn cfgd_core::providers::PackageManager> = registry
            .package_managers
            .iter()
            .map(|m| m.as_ref())
            .collect();
        let pkg = packages::plan_packages(&effective_resolved.merged, &all_managers)?;

        let mut fm = CfgdFileManager::new(&config_dir, &effective_resolved)?;
        fm.set_global_strategy(cfg.spec.file_strategy);
        if !source_env.is_empty() {
            fm.set_source_env(&source_env);
        }

        let fa = fm.plan(&effective_resolved.merged)?;
        (pkg, fa, Some(fm))
    };

    let reconciler = Reconciler::new(&registry, &state);
    let mut plan = reconciler.plan(
        &effective_resolved,
        file_actions,
        pkg_actions,
        resolved_modules,
        reconcile_context,
    )?;

    // Apply --skip / --only filters
    filter_plan(&mut plan, &args.skip, &args.only);

    // Strip script phases when --skip-scripts is set
    if args.skip_scripts {
        strip_scripts_from_plan(&mut plan);
    }

    display_plan_preview(
        &plan,
        printer,
        &state,
        &args.context,
        phase_filter.as_ref(),
        dry_run_fm.as_ref(),
    );

    Ok(())
}

fn cmd_status(cli: &Cli, printer: &Printer, module_filter: Option<&str>) -> anyhow::Result<()> {
    if let Some(mod_name) = module_filter {
        return cmd_status_module(cli, printer, mod_name);
    }

    let (cfg, resolved) = load_config_and_profile(cli, printer)?;
    let state = open_state_store(cli.state_dir.as_deref())?;

    let last_apply = state.last_apply()?;
    let drift_events = state.unresolved_drift()?;
    let source_records = if !cfg.spec.sources.is_empty() {
        state.config_sources()?
    } else {
        vec![]
    };
    let pending = state.pending_decisions()?;
    let resources = state.managed_resources()?;

    // Build module status entries
    let config_dir = config_dir(cli);
    let cache_base = modules::default_module_cache_dir().unwrap_or_default();
    let all_modules =
        modules::load_all_modules(&config_dir, &cache_base, printer).unwrap_or_default();
    let state_map = module_state_map(&state);
    let module_entries: Vec<ModuleStatusEntry> = resolved
        .merged
        .modules
        .iter()
        .map(|mod_ref| {
            let mod_name = modules::resolve_profile_module_name(mod_ref);
            let (pkg_count, file_count) = all_modules
                .get(mod_name)
                .map(|m| (m.spec.packages.len(), m.spec.files.len()))
                .unwrap_or((0, 0));
            let status = state_map
                .get(mod_name)
                .map(|s| s.status.clone())
                .unwrap_or_else(|| "not applied".into());
            ModuleStatusEntry {
                name: mod_ref.clone(),
                packages: pkg_count,
                files: file_count,
                status,
            }
        })
        .collect();

    if printer.is_structured() {
        printer.write_structured(&StatusOutput {
            last_apply,
            drift: drift_events,
            sources: source_records,
            pending_decisions: pending,
            modules: module_entries,
            managed_resources: resources,
        });
        return Ok(());
    }

    printer.header("Status");
    printer.newline();

    // Last apply
    if let Some(last) = &last_apply {
        printer.subheader("Last Apply");
        printer.key_value("Time", &last.timestamp);
        printer.key_value("Profile", &last.profile);
        printer.key_value(
            "Status",
            match last.status {
                cfgd_core::state::ApplyStatus::Success => "success",
                cfgd_core::state::ApplyStatus::Partial => "partial",
                cfgd_core::state::ApplyStatus::Failed => "failed",
                cfgd_core::state::ApplyStatus::InProgress => "in_progress",
            },
        );
        if let Some(ref summary) = last.summary {
            printer.key_value("Summary", summary);
        }
    } else {
        printer.info("No applies recorded yet");
    }

    // Drift summary
    printer.newline();
    printer.subheader("Drift");
    if drift_events.is_empty() {
        printer.success("No drift detected");
    } else {
        for event in &drift_events {
            let source_info = if event.source != "local" {
                format!(" [{}]", event.source)
            } else {
                String::new()
            };
            printer.warning(&format!(
                "{} {} — want: {}, have: {}{}",
                event.resource_type,
                event.resource_id,
                event.expected.as_deref().unwrap_or("?"),
                event.actual.as_deref().unwrap_or("?"),
                source_info,
            ));
        }
    }

    // Config sources
    if !cfg.spec.sources.is_empty() {
        printer.newline();
        printer.subheader("Config Sources");
        if source_records.is_empty() {
            for source in &cfg.spec.sources {
                printer.key_value(&source.name, "not yet fetched");
            }
        } else {
            let rows: Vec<Vec<String>> = source_records
                .iter()
                .map(|s| {
                    vec![
                        s.name.clone(),
                        s.status.clone(),
                        s.source_version.clone().unwrap_or_else(|| "-".into()),
                        s.last_fetched.clone().unwrap_or_else(|| "never".into()),
                    ]
                })
                .collect();
            printer.table(&["Source", "Status", "Version", "Last Fetched"], &rows);
        }
    }

    // Pending decisions
    if !pending.is_empty() {
        printer.newline();
        printer.subheader("Pending Decisions");
        display_pending_decisions(printer, &pending);
    }

    // Modules
    if !resolved.merged.modules.is_empty() {
        printer.newline();
        printer.subheader("Modules");

        for mod_ref in &resolved.merged.modules {
            let mod_name = modules::resolve_profile_module_name(mod_ref);
            let (pkg_count, file_count) = all_modules
                .get(mod_name)
                .map(|m| (m.spec.packages.len(), m.spec.files.len()))
                .unwrap_or((0, 0));

            let summary = format!("{} pkgs, {} files", pkg_count, file_count);
            if let Some(state_rec) = state_map.get(mod_name) {
                if state_rec.status == "installed" {
                    printer.success(&format!("{}: {}, {}", mod_ref, summary, state_rec.status));
                } else {
                    printer.warning(&format!("{}: {}, {}", mod_ref, summary, state_rec.status));
                }
            } else {
                printer.info(&format!("{}: {}, not yet applied", mod_ref, summary));
            }
        }
    }

    // Managed resources
    if !resources.is_empty() {
        printer.newline();
        printer.subheader("Managed Resources");
        printer.table(
            &["Type", "Resource", "Source"],
            &resources
                .iter()
                .map(|r| {
                    vec![
                        r.resource_type.clone(),
                        r.resource_id.clone(),
                        r.source.clone(),
                    ]
                })
                .collect::<Vec<_>>(),
        );
    }

    Ok(())
}

fn cmd_status_module(cli: &Cli, printer: &Printer, mod_name: &str) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    let cache_base = modules::default_module_cache_dir()?;
    let all_modules = modules::load_all_modules(&config_dir, &cache_base, printer)?;

    let module = match all_modules.get(mod_name) {
        Some(m) => m,
        None => {
            // Module not found — show empty status gracefully
            if printer.is_structured() {
                #[derive(serde::Serialize)]
                #[serde(rename_all = "camelCase")]
                struct ModuleStatus {
                    name: String,
                    packages: usize,
                    files: usize,
                    depends: Vec<String>,
                    status: String,
                    last_applied: Option<String>,
                }
                printer.write_structured(&ModuleStatus {
                    name: mod_name.to_string(),
                    packages: 0,
                    files: 0,
                    depends: vec![],
                    status: "not found".into(),
                    last_applied: None,
                });
            } else {
                printer.header(&format!("Status: {}", mod_name));
                printer.info(&format!("Module '{}' not found", mod_name));
            }
            return Ok(());
        }
    };

    let state = open_state_store(cli.state_dir.as_deref())?;
    let state_rec = state.module_state_by_name(mod_name)?;

    if printer.is_structured() {
        #[derive(serde::Serialize)]
        #[serde(rename_all = "camelCase")]
        struct ModuleStatus {
            name: String,
            packages: usize,
            files: usize,
            depends: Vec<String>,
            status: String,
            last_applied: Option<String>,
        }
        let status = state_rec
            .as_ref()
            .map(|s| s.status.clone())
            .unwrap_or_else(|| "not applied".into());
        let last_applied = state_rec.as_ref().map(|s| s.installed_at.clone());
        printer.write_structured(&ModuleStatus {
            name: mod_name.to_string(),
            packages: module.spec.packages.len(),
            files: module.spec.files.len(),
            depends: module.spec.depends.clone(),
            status,
            last_applied,
        });
        return Ok(());
    }

    printer.header(&format!("Status: {}", mod_name));

    // Module info
    printer.key_value("Packages", &module.spec.packages.len().to_string());
    printer.key_value("Files", &module.spec.files.len().to_string());
    if !module.spec.depends.is_empty() {
        printer.key_value("Dependencies", &module.spec.depends.join(", "));
    }

    // State from DB
    if let Some(rec) = &state_rec {
        printer.key_value("Status", &rec.status);
        printer.key_value("Last applied", &rec.installed_at);
        printer.key_value("Packages hash", &rec.packages_hash);
        printer.key_value("Files hash", &rec.files_hash);
    } else {
        printer.key_value("Status", "not applied");
    }

    // Show deployed files from manifest
    let deployed_files = state.module_deployed_files(mod_name)?;
    if !deployed_files.is_empty() {
        printer.newline();
        printer.subheader("Deployed Files");
        for f in &deployed_files {
            let exists = std::path::Path::new(&f.file_path).exists();
            if exists {
                printer.success(&f.file_path);
            } else {
                printer.error(&format!("{} (missing)", f.file_path));
            }
        }
    }

    Ok(())
}

fn cmd_log(
    printer: &Printer,
    count: u32,
    show_output: Option<i64>,
    state_dir: Option<&Path>,
) -> anyhow::Result<()> {
    let state = open_state_store(state_dir)?;

    if let Some(apply_id) = show_output {
        return cmd_log_show_output(printer, &state, apply_id);
    }

    let history = state.history(count)?;

    if printer.is_structured() {
        printer.write_structured(&LogOutput { entries: history });
        return Ok(());
    }

    printer.header("Apply History");

    if history.is_empty() {
        printer.newline();
        printer.info("No applies recorded yet");
        return Ok(());
    }

    printer.newline();
    printer.table(
        &["ID", "Time", "Profile", "Status", "Summary"],
        &history
            .iter()
            .map(|record| {
                vec![
                    record.id.to_string(),
                    record.timestamp.clone(),
                    record.profile.clone(),
                    match record.status {
                        cfgd_core::state::ApplyStatus::Success => "success".to_string(),
                        cfgd_core::state::ApplyStatus::Partial => "partial".to_string(),
                        cfgd_core::state::ApplyStatus::Failed => "failed".to_string(),
                        cfgd_core::state::ApplyStatus::InProgress => "in_progress".to_string(),
                    },
                    record.summary.clone().unwrap_or_else(|| "-".to_string()),
                ]
            })
            .collect::<Vec<_>>(),
    );

    Ok(())
}

fn cmd_log_show_output(
    printer: &Printer,
    state: &cfgd_core::state::StateStore,
    apply_id: i64,
) -> anyhow::Result<()> {
    // Verify the apply ID exists before showing output
    if state.get_apply(apply_id)?.is_none() {
        anyhow::bail!("no apply found with ID {}", apply_id);
    }

    let entries = state.journal_entries(apply_id)?;

    if entries.is_empty() {
        printer.info(&format!("No journal entries for apply #{}", apply_id));
        return Ok(());
    }

    printer.header(&format!("Apply #{} — Script Output", apply_id));

    let mut found_output = false;
    for entry in &entries {
        if let Some(ref output) = entry.script_output {
            found_output = true;
            printer.newline();
            printer.subheader(&format!(
                "[{}] {} ({})",
                entry.phase, entry.resource_id, entry.action_type
            ));
            for line in output.lines() {
                printer.info(line);
            }
        }
    }

    if !found_output {
        printer.newline();
        printer.info("No script output captured for this apply");
    }

    Ok(())
}

fn print_verify_results(results: &[reconciler::VerifyResult], printer: &Printer) -> (usize, usize) {
    let mut pass_count = 0;
    let mut fail_count = 0;

    for result in results {
        if result.matches {
            pass_count += 1;
            printer.success(&format!(
                "{} {} — {}",
                result.resource_type, result.resource_id, result.expected
            ));
        } else {
            fail_count += 1;
            printer.error(&format!(
                "{} {} — want: {}, have: {}",
                result.resource_type, result.resource_id, result.expected, result.actual
            ));
        }
    }

    (pass_count, fail_count)
}

fn cmd_verify(cli: &Cli, printer: &Printer, module_filter: Option<&str>) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    let state = open_state_store(cli.state_dir.as_deref())?;

    let (resolved, resolved_modules, registry) = if let Some(mod_name) = module_filter {
        let resolved = empty_resolved_profile(mod_name);
        let registry = build_registry();
        let platform = Platform::detect();
        let mgr_map = managers_map(&registry);
        let cache_base = modules::default_module_cache_dir()?;
        let mods = modules::resolve_modules(
            &[mod_name.to_string()],
            &config_dir,
            &cache_base,
            &platform,
            &mgr_map,
            printer,
        )
        .unwrap_or_default();
        (resolved, mods, registry)
    } else {
        let (_cfg, mut resolved) = load_config_and_profile(cli, printer)?;
        packages::resolve_manifest_packages(&mut resolved.merged.packages, &config_dir)?;
        let registry = build_registry_with_profile(&resolved.merged.packages);
        (resolved, Vec::new(), registry)
    };

    let results = reconciler::verify(&resolved, &registry, &state, printer, &resolved_modules)?;

    let pass_count = results.iter().filter(|r| r.matches).count();
    let fail_count = results.iter().filter(|r| !r.matches).count();

    if printer.is_structured() {
        printer.write_structured(&VerifyOutput {
            results,
            pass_count,
            fail_count,
        });
        return Ok(());
    }

    printer.header("Verify");
    printer.newline();

    if results.is_empty() {
        printer.info("No managed resources to verify");
        return Ok(());
    }

    let (pass_count, fail_count) = print_verify_results(&results, printer);

    printer.newline();
    if fail_count == 0 {
        printer.success(&format!(
            "All {} resource(s) match desired state",
            pass_count
        ));
    } else {
        printer.warning(&format!("{} passed, {} failed", pass_count, fail_count));
    }

    Ok(())
}

// ---------------------------------------------------------------------------
// Compliance command output types
// ---------------------------------------------------------------------------

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ComplianceSnapshotOutput {
    snapshot: cfgd_core::compliance::ComplianceSnapshot,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ComplianceHistoryOutput {
    entries: Vec<cfgd_core::state::ComplianceHistoryRow>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ComplianceDiffOutput {
    id1: i64,
    id2: i64,
    added: Vec<cfgd_core::compliance::ComplianceCheck>,
    removed: Vec<cfgd_core::compliance::ComplianceCheck>,
    changed: Vec<ComplianceCheckChange>,
}

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct ComplianceCheckChange {
    key: String,
    old_status: String,
    new_status: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    detail: Option<String>,
}

// ---------------------------------------------------------------------------
// Compliance command handlers
// ---------------------------------------------------------------------------

/// Collect a compliance snapshot, hash it, and store in the state store.
/// Shared setup used by both `cmd_compliance_snapshot` and `cmd_compliance_export`.
fn collect_and_store_compliance_snapshot(
    cli: &Cli,
    printer: &Printer,
) -> anyhow::Result<(CfgdConfig, cfgd_core::compliance::ComplianceSnapshot)> {
    let (cfg, mut resolved) = load_config_and_profile(cli, printer)?;
    let config_dir = config_dir(cli);
    packages::resolve_manifest_packages(&mut resolved.merged.packages, &config_dir)?;
    let registry = build_registry_with_profile(&resolved.merged.packages);

    let profile_name = cli
        .profile
        .as_deref()
        .unwrap_or_else(|| cfg.active_profile().unwrap_or("default"));

    let scope = cfg
        .spec
        .compliance
        .as_ref()
        .map(|c| c.scope.clone())
        .unwrap_or_default();

    let sources: Vec<String> = cfg.spec.sources.iter().map(|s| s.name.clone()).collect();

    let snapshot = cfgd_core::compliance::collect_snapshot(
        profile_name,
        &resolved.merged,
        &registry,
        &scope,
        &sources,
    )?;

    let state = open_state_store(cli.state_dir.as_deref())?;
    let json = serde_json::to_string(&snapshot).map_err(|e| anyhow::anyhow!("serialize: {}", e))?;
    let hash = cfgd_core::sha256_hex(json.as_bytes());
    state.store_compliance_snapshot(&snapshot, &hash)?;

    Ok((cfg, snapshot))
}

/// Build a snapshot and print summary table; optionally store in state.
fn cmd_compliance_snapshot(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let (_cfg, snapshot) = collect_and_store_compliance_snapshot(cli, printer)?;

    if printer.is_structured() {
        printer.write_structured(&ComplianceSnapshotOutput {
            snapshot: snapshot.clone(),
        });
    } else {
        print_compliance_summary(&snapshot, printer);
    }

    Ok(())
}

/// Export snapshot to configured export path (or stdout if -o json).
fn cmd_compliance_export(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let (cfg, snapshot) = collect_and_store_compliance_snapshot(cli, printer)?;

    if printer.is_structured() {
        printer.write_structured(&ComplianceSnapshotOutput {
            snapshot: snapshot.clone(),
        });
        return Ok(());
    }

    let export = cfg
        .spec
        .compliance
        .as_ref()
        .map(|c| c.export.clone())
        .unwrap_or_default();

    let export_path = cfgd_core::compliance::export_snapshot_to_file(&snapshot, &export)?;
    printer.success(&format!(
        "Compliance snapshot written to {}",
        export_path.display()
    ));
    print_compliance_summary(&snapshot, printer);

    Ok(())
}

/// Show compliance snapshot history.
fn cmd_compliance_history(cli: &Cli, printer: &Printer, since: Option<&str>) -> anyhow::Result<()> {
    let state = open_state_store(cli.state_dir.as_deref())?;

    // Convert --since duration to an ISO 8601 timestamp cutoff.
    let since_ts: Option<String> = since
        .map(|s| {
            let dur = cfgd_core::parse_duration_str(s)
                .map_err(|e| anyhow::anyhow!("invalid --since value '{}': {}", s, e))?;
            let cutoff_secs = cfgd_core::unix_secs_now().saturating_sub(dur.as_secs());
            Ok::<String, anyhow::Error>(cfgd_core::unix_secs_to_iso8601(cutoff_secs))
        })
        .transpose()?;

    let entries = state.compliance_history(since_ts.as_deref(), 100)?;

    if printer.is_structured() {
        printer.write_structured(&ComplianceHistoryOutput {
            entries: entries.clone(),
        });
        return Ok(());
    }

    printer.header("Compliance History");

    if entries.is_empty() {
        printer.newline();
        printer.info("No compliance snapshots recorded yet");
        return Ok(());
    }

    printer.newline();
    printer.table(
        &["ID", "Timestamp", "Compliant", "Warning", "Violation"],
        &entries
            .iter()
            .map(|row| {
                vec![
                    row.id.to_string(),
                    row.timestamp.clone(),
                    row.compliant.to_string(),
                    row.warning.to_string(),
                    row.violation.to_string(),
                ]
            })
            .collect::<Vec<_>>(),
    );

    Ok(())
}

/// Show diff between two snapshots by ID.
fn cmd_compliance_diff(cli: &Cli, printer: &Printer, id1: i64, id2: i64) -> anyhow::Result<()> {
    let state = open_state_store(cli.state_dir.as_deref())?;

    let snap1 = state
        .get_compliance_snapshot(id1)?
        .ok_or_else(|| anyhow::anyhow!("snapshot #{} not found", id1))?;
    let snap2 = state
        .get_compliance_snapshot(id2)?
        .ok_or_else(|| anyhow::anyhow!("snapshot #{} not found", id2))?;

    // Build a key for each check to match them between snapshots.
    fn check_key(c: &cfgd_core::compliance::ComplianceCheck) -> String {
        let id = c
            .target
            .as_deref()
            .or(c.name.as_deref())
            .or(c.key.as_deref())
            .or(c.path.as_deref())
            .unwrap_or("(unknown)");
        format!("{}:{}", c.category, id)
    }

    use std::collections::HashMap;
    let map1: HashMap<String, &cfgd_core::compliance::ComplianceCheck> =
        snap1.checks.iter().map(|c| (check_key(c), c)).collect();
    let map2: HashMap<String, &cfgd_core::compliance::ComplianceCheck> =
        snap2.checks.iter().map(|c| (check_key(c), c)).collect();

    let mut added: Vec<cfgd_core::compliance::ComplianceCheck> = Vec::new();
    let mut removed: Vec<cfgd_core::compliance::ComplianceCheck> = Vec::new();
    let mut changed: Vec<ComplianceCheckChange> = Vec::new();

    for (key, check2) in &map2 {
        if let Some(check1) = map1.get(key) {
            if check1.status != check2.status {
                changed.push(ComplianceCheckChange {
                    key: key.clone(),
                    old_status: format!("{:?}", check1.status),
                    new_status: format!("{:?}", check2.status),
                    detail: check2.detail.clone(),
                });
            }
        } else {
            added.push((*check2).clone());
        }
    }
    for (key, check1) in &map1 {
        if !map2.contains_key(key) {
            removed.push((*check1).clone());
        }
    }

    // Sort for deterministic output
    added.sort_by_key(check_key);
    removed.sort_by_key(check_key);
    changed.sort_by(|a, b| a.key.cmp(&b.key));

    if printer.is_structured() {
        printer.write_structured(&ComplianceDiffOutput {
            id1,
            id2,
            added: added.clone(),
            removed: removed.clone(),
            changed: changed.clone(),
        });
        return Ok(());
    }

    printer.header(&format!("Compliance Diff #{} → #{}", id1, id2));
    printer.newline();
    printer.key_value("Snapshot 1", &snap1.timestamp);
    printer.key_value("Snapshot 2", &snap2.timestamp);
    printer.newline();

    if added.is_empty() && removed.is_empty() && changed.is_empty() {
        printer.success("No differences between snapshots");
        return Ok(());
    }

    if !added.is_empty() {
        printer.subheader(&format!("Added ({} check(s))", added.len()));
        for check in &added {
            printer.success(&format!("  + {}", check_key(check)));
        }
        printer.newline();
    }

    if !removed.is_empty() {
        printer.subheader(&format!("Removed ({} check(s))", removed.len()));
        for check in &removed {
            printer.warning(&format!("  - {}", check_key(check)));
        }
        printer.newline();
    }

    if !changed.is_empty() {
        printer.subheader(&format!("Changed ({} check(s))", changed.len()));
        for change in &changed {
            let msg = format!(
                "  ~ {} ({} → {})",
                change.key, change.old_status, change.new_status
            );
            if change.new_status == "Violation" {
                printer.error(&msg);
            } else if change.new_status == "Warning" {
                printer.warning(&msg);
            } else {
                printer.success(&msg);
            }
            if let Some(ref detail) = change.detail {
                printer.info(&format!("    {}", detail));
            }
        }
    }

    Ok(())
}

/// Print a human-readable compliance summary table.
fn print_compliance_summary(
    snapshot: &cfgd_core::compliance::ComplianceSnapshot,
    printer: &Printer,
) {
    printer.header("Compliance Snapshot");
    printer.newline();
    printer.key_value("Timestamp", &snapshot.timestamp);
    printer.key_value("Machine", &snapshot.machine.hostname);
    printer.key_value("Profile", &snapshot.profile);
    printer.newline();

    if snapshot.checks.is_empty() {
        printer.info("No checks performed");
        return;
    }

    // Group by category for the summary table
    use std::collections::BTreeMap;
    let mut by_category: BTreeMap<&str, (usize, usize, usize)> = BTreeMap::new();
    for check in &snapshot.checks {
        let entry = by_category.entry(check.category.as_str()).or_default();
        match check.status {
            cfgd_core::compliance::ComplianceStatus::Compliant => entry.0 += 1,
            cfgd_core::compliance::ComplianceStatus::Warning => entry.1 += 1,
            cfgd_core::compliance::ComplianceStatus::Violation => entry.2 += 1,
        }
    }

    printer.table(
        &["Category", "Compliant", "Warning", "Violation"],
        &by_category
            .iter()
            .map(|(cat, (c, w, v))| {
                vec![cat.to_string(), c.to_string(), w.to_string(), v.to_string()]
            })
            .collect::<Vec<_>>(),
    );

    printer.newline();
    let s = &snapshot.summary;
    if s.violation > 0 {
        printer.error(&format!(
            "Summary: {} compliant, {} warning, {} violation",
            s.compliant, s.warning, s.violation
        ));
    } else if s.warning > 0 {
        printer.warning(&format!(
            "Summary: {} compliant, {} warning, {} violation",
            s.compliant, s.warning, s.violation
        ));
    } else {
        printer.success(&format!("All {} check(s) compliant", s.compliant));
    }
}

fn cmd_diff(cli: &Cli, printer: &Printer, module_filter: Option<&str>) -> anyhow::Result<()> {
    printer.header("Diff");

    let config_dir = config_dir(cli);

    if let Some(mod_name) = module_filter {
        // Module-only diff: load just that module's files and packages
        let registry = build_registry();
        let platform = Platform::detect();
        let mgr_map = managers_map(&registry);
        let cache_base = modules::default_module_cache_dir()?;
        let resolved_modules = match modules::resolve_modules(
            &[mod_name.to_string()],
            &config_dir,
            &cache_base,
            &platform,
            &mgr_map,
            printer,
        ) {
            Ok(mods) => mods,
            Err(_) => {
                printer.info(&format!(
                    "Module '{}' not found — nothing to diff",
                    mod_name
                ));
                return Ok(());
            }
        };

        printer.key_value("Module", mod_name);
        printer.newline();

        // Module file diffs
        printer.subheader("Files");
        let mut has_file_diff = false;
        for module in &resolved_modules {
            for file in &module.files {
                if file.target.exists() {
                    if file.source.exists() {
                        let source_content =
                            std::fs::read_to_string(&file.source).unwrap_or_default();
                        let target_content =
                            std::fs::read_to_string(&file.target).unwrap_or_default();
                        if source_content != target_content {
                            has_file_diff = true;
                            printer.info(&format!("{}:", file.target.display()));
                            printer.diff(&target_content, &source_content);
                        }
                    }
                } else {
                    has_file_diff = true;
                    printer.warning(&format!("{}: missing", file.target.display()));
                }
            }
        }
        if !has_file_diff {
            printer.success("No file drift");
        }

        // Module package diffs — check if resolved packages are installed
        printer.newline();
        printer.subheader("Packages");
        let mut has_pkg_drift = false;
        for module in &resolved_modules {
            for pkg in &module.packages {
                if pkg.manager == "script" {
                    continue;
                }
                if let Some(mgr) = mgr_map.get(pkg.manager.as_str()) {
                    let installed = mgr.installed_packages().unwrap_or_default();
                    if !installed.contains(&pkg.resolved_name) {
                        has_pkg_drift = true;
                        printer
                            .warning(&format!("{}: missing — {}", pkg.manager, pkg.resolved_name));
                    }
                }
            }
        }
        if !has_pkg_drift {
            printer.success("No package drift");
        }

        return Ok(());
    }

    let (_cfg, mut resolved) = load_config_and_profile(cli, printer)?;

    // Resolve manifest files
    packages::resolve_manifest_packages(&mut resolved.merged.packages, &config_dir)?;

    let registry = build_registry_with_profile(&resolved.merged.packages);

    printer.newline();

    // File diffs
    printer.subheader("Files");
    let fm = CfgdFileManager::new(&config_dir, &resolved)?;
    fm.diff(&resolved.merged, printer)?;

    // Package drift
    printer.newline();
    printer.subheader("Packages");
    let all_managers: Vec<&dyn cfgd_core::providers::PackageManager> = registry
        .package_managers
        .iter()
        .map(|m| m.as_ref())
        .collect();
    let pkg_actions = packages::plan_packages(&resolved.merged, &all_managers)?;
    print_package_drift(&pkg_actions, printer);

    // System drift
    printer.newline();
    printer.subheader("System");
    let available_configurators = registry.available_system_configurators();
    let mut has_system_drift = false;
    for configurator in &available_configurators {
        let key = configurator.name();
        let desired = match resolved.merged.system.get(key) {
            Some(v) => v,
            None => continue,
        };
        match configurator.diff(desired) {
            Ok(drifts) if !drifts.is_empty() => {
                has_system_drift = true;
                for drift in &drifts {
                    printer.warning(&format!(
                        "{}.{}: want {}, have {}",
                        key, drift.key, drift.expected, drift.actual
                    ));
                }
            }
            Err(e) => {
                printer.warning(&format!("{}: error checking drift — {}", key, e));
            }
            _ => {}
        }
    }
    if !has_system_drift {
        printer.success("No system drift");
    }

    Ok(())
}

fn print_package_drift(pkg_actions: &[PackageAction], printer: &Printer) {
    let pkg_diffs: Vec<&PackageAction> = pkg_actions
        .iter()
        .filter(|a| !matches!(a, PackageAction::Skip { .. }))
        .collect();
    if pkg_diffs.is_empty() {
        printer.success("No package drift");
    } else {
        for action in &pkg_diffs {
            match action {
                PackageAction::Bootstrap {
                    manager, method, ..
                } => {
                    printer.warning(&format!(
                        "{}: not installed — can bootstrap via {}",
                        manager, method
                    ));
                }
                PackageAction::Install {
                    manager, packages, ..
                } => {
                    printer.warning(&format!("{}: missing — {}", manager, packages.join(", ")));
                }
                PackageAction::Uninstall {
                    manager, packages, ..
                } => {
                    printer.warning(&format!("{}: extra — {}", manager, packages.join(", ")));
                }
                PackageAction::Skip { .. } => {}
            }
        }
    }
}

// --- Validation helpers ---

/// Validate a resource name (module or profile) for filesystem safety.
/// Allows alphanumeric, hyphen, underscore, and dot (but not leading dot).
fn validate_resource_name(name: &str, kind: &str) -> anyhow::Result<()> {
    if name.is_empty() {
        anyhow::bail!("{kind} name cannot be empty");
    }
    if name.len() > 128 {
        anyhow::bail!("{kind} name too long (max 128 characters)");
    }
    if name.starts_with('.') || name.starts_with('-') {
        anyhow::bail!("{kind} name cannot start with '.' or '-'");
    }
    if !name
        .chars()
        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
    {
        anyhow::bail!(
            "{kind} name '{}' contains invalid characters — use only alphanumeric, hyphen, underscore, or dot",
            name
        );
    }
    Ok(())
}

// --- Scan helpers ---

/// Scan a profiles/ directory and return sorted profile names.
fn scan_profile_names(profiles_dir: &Path) -> anyhow::Result<Vec<String>> {
    let mut names = Vec::new();
    if profiles_dir.exists() {
        for entry in std::fs::read_dir(profiles_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().is_some_and(|e| e == "yaml" || e == "yml")
                && let Ok(doc) = config::load_profile(&path)
            {
                names.push(doc.metadata.name);
            }
        }
        names.sort();
    }
    Ok(names)
}

/// Scan a modules/ directory and return sorted module names.
fn scan_module_names(modules_dir: &Path) -> anyhow::Result<Vec<String>> {
    let mut names = Vec::new();
    if modules_dir.exists() {
        for entry in std::fs::read_dir(modules_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir()
                && path.join("module.yaml").exists()
                && let Some(n) = entry.file_name().to_str()
            {
                names.push(n.to_string());
            }
        }
        names.sort();
    }
    Ok(names)
}

// --- Config CRUD ---

fn cmd_config_show(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }

    let cfg = config::load_config(config_path)?;

    if printer.write_structured(&cfg) {
        return Ok(());
    }

    printer.header("Configuration");
    printer.key_value("File", &config_path.display().to_string());
    printer.key_value("Profile", cfg.spec.profile.as_deref().unwrap_or("(none)"));

    // Origins
    if !cfg.spec.origin.is_empty() {
        printer.newline();
        printer.subheader("Origins");
        for (i, origin) in cfg.spec.origin.iter().enumerate() {
            let label = if i == 0 { "Primary" } else { "Secondary" };
            printer.key_value(label, &format!("{:?} — {}", origin.origin_type, origin.url));
            printer.key_value("  Branch", &origin.branch);
        }
    }

    // Sources
    if !cfg.spec.sources.is_empty() {
        printer.newline();
        printer.subheader("Sources");
        for src in &cfg.spec.sources {
            printer.key_value(&src.name, &src.origin.url);
        }
    }

    // Module registries
    if let Some(ref mods) = cfg.spec.modules {
        if !mods.registries.is_empty() {
            printer.newline();
            printer.subheader("Module Registries");
            for ms in &mods.registries {
                printer.key_value(&ms.name, &ms.url);
            }
        }

        // Module security
        if let Some(ref sec) = mods.security {
            printer.newline();
            printer.subheader("Module Security");
            printer.key_value(
                "Require signatures",
                if sec.require_signatures { "yes" } else { "no" },
            );
        }
    }

    // Daemon
    if let Some(ref daemon) = cfg.spec.daemon {
        printer.newline();
        printer.subheader("Daemon");
        printer.key_value("Enabled", if daemon.enabled { "yes" } else { "no" });
        if let Some(ref reconcile) = daemon.reconcile {
            printer.key_value("  Reconcile interval", &reconcile.interval);
            printer.key_value(
                "  On change",
                if reconcile.on_change { "yes" } else { "no" },
            );
            printer.key_value(
                "  Auto apply",
                if reconcile.auto_apply { "yes" } else { "no" },
            );
        }
        if let Some(ref sync) = daemon.sync {
            printer.key_value("  Sync interval", &sync.interval);
        }
    }

    // Secrets
    if let Some(ref secrets) = cfg.spec.secrets {
        printer.newline();
        printer.subheader("Secrets");
        printer.key_value("Backend", &secrets.backend);
    }

    // Theme
    if let Some(ref theme) = cfg.spec.theme {
        printer.newline();
        printer.subheader("Theme");
        printer.key_value("Theme", &theme.name);
    }

    Ok(())
}

fn cmd_config_edit(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }

    open_in_editor(config_path, printer)?;

    // Validate after editing — loop until valid or user cancels
    loop {
        match config::load_config(config_path) {
            Ok(_) => {
                printer.success("Configuration is valid");
                break;
            }
            Err(e) => {
                printer.error(&format!("Invalid configuration: {}", e));
                if !printer.prompt_confirm("Re-open in editor to fix?")? {
                    printer.warning("Saved with validation errors");
                    break;
                }
                open_in_editor(config_path, printer)?;
            }
        }
    }

    Ok(())
}

// --- Config get/set/unset ---

/// Walk a dotted key path through a YAML value, returning the leaf.
/// Use "." to return the root value itself.
fn walk_yaml_path<'a>(
    value: &'a serde_yaml::Value,
    path: &str,
) -> anyhow::Result<&'a serde_yaml::Value> {
    if path == "." {
        return Ok(value);
    }
    let segments: Vec<&str> = path.split('.').collect();
    if segments.iter().any(|s| s.is_empty()) {
        anyhow::bail!("invalid key path '{}': contains empty segment", path);
    }
    let mut current = value;

    for (i, segment) in segments.iter().enumerate() {
        match current {
            serde_yaml::Value::Mapping(map) => {
                let key = serde_yaml::Value::String((*segment).to_string());
                current = map.get(&key).ok_or_else(|| {
                    let partial = segments[..=i].join(".");
                    anyhow::anyhow!("key '{}' not found in config", partial)
                })?;
            }
            _ => {
                let partial = segments[..i].join(".");
                anyhow::bail!("'{}' is not a mapping", partial);
            }
        }
    }

    Ok(current)
}

/// Walk a dotted key path, creating intermediate mappings as needed.
/// Returns a mutable reference to the *parent* mapping and the leaf key name.
fn walk_yaml_path_mut<'a>(
    value: &'a mut serde_yaml::Value,
    path: &str,
) -> anyhow::Result<(&'a mut serde_yaml::Mapping, String)> {
    let segments: Vec<&str> = path.split('.').collect();
    if segments.is_empty() || segments.iter().any(|s| s.is_empty()) {
        anyhow::bail!("invalid key path '{}': contains empty segment", path);
    }

    let mut current = value;
    // Walk to the parent of the final segment, creating intermediate maps
    for segment in &segments[..segments.len() - 1] {
        let key = serde_yaml::Value::String((*segment).to_string());
        if !current.as_mapping().is_some_and(|m| m.contains_key(&key)) {
            // Create intermediate mapping
            let map = current
                .as_mapping_mut()
                .ok_or_else(|| anyhow::anyhow!("cannot traverse into non-mapping"))?;
            map.insert(
                key.clone(),
                serde_yaml::Value::Mapping(serde_yaml::Mapping::new()),
            );
        }
        current = current
            .as_mapping_mut()
            .ok_or_else(|| anyhow::anyhow!("cannot traverse into non-mapping"))?
            .get_mut(&key)
            .ok_or_else(|| anyhow::anyhow!("failed to create intermediate mapping"))?;
    }

    let parent = current
        .as_mapping_mut()
        .ok_or_else(|| anyhow::anyhow!("parent is not a mapping"))?;
    let leaf = segments
        .last()
        .ok_or_else(|| anyhow::anyhow!("empty key path"))?
        .to_string();
    Ok((parent, leaf))
}

/// Parse a string value into the most appropriate YAML type.
fn parse_yaml_value(s: &str) -> serde_yaml::Value {
    match s {
        "true" => serde_yaml::Value::Bool(true),
        "false" => serde_yaml::Value::Bool(false),
        "null" | "~" => serde_yaml::Value::Null,
        _ => {
            // Try integer, then float, then string
            if let Ok(n) = s.parse::<i64>() {
                serde_yaml::Value::Number(n.into())
            } else if let Ok(f) = s.parse::<f64>() {
                serde_yaml::Value::Number(serde_yaml::Number::from(f))
            } else {
                serde_yaml::Value::String(s.to_string())
            }
        }
    }
}

fn cmd_config_get(cli: &Cli, printer: &Printer, key: &str) -> anyhow::Result<()> {
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }

    let contents = std::fs::read_to_string(config_path)?;
    let raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;

    let spec = raw
        .get("spec")
        .ok_or_else(|| anyhow::anyhow!("config has no 'spec' section"))?;

    let value = walk_yaml_path(spec, key)?;

    if printer.is_structured() {
        // Convert serde_yaml::Value to serde_json::Value for structured output
        let json_value: serde_json::Value =
            serde_json::to_value(value).unwrap_or(serde_json::Value::Null);
        printer.write_structured(&json_value);
        return Ok(());
    }

    match value {
        serde_yaml::Value::Null => {} // key exists but null — print nothing
        serde_yaml::Value::String(s) => printer.stdout_line(s),
        serde_yaml::Value::Bool(b) => printer.stdout_line(&b.to_string()),
        serde_yaml::Value::Number(n) => printer.stdout_line(&n.to_string()),
        other => {
            let yaml = serde_yaml::to_string(other)?;
            let trimmed = yaml.strip_prefix("---\n").unwrap_or(&yaml);
            printer.stdout_line(trimmed.trim_end());
        }
    }

    Ok(())
}

fn cmd_config_set(cli: &Cli, printer: &Printer, key: &str, value: &str) -> anyhow::Result<()> {
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }

    let contents = std::fs::read_to_string(config_path)?;
    let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;

    let spec = raw
        .get_mut("spec")
        .ok_or_else(|| anyhow::anyhow!("config has no 'spec' section"))?;

    let (parent, leaf_key) = walk_yaml_path_mut(spec, key)?;
    let yaml_key = serde_yaml::Value::String(leaf_key);
    parent.insert(yaml_key, parse_yaml_value(value));

    // Validate by round-tripping through the typed config parser
    let output = serde_yaml::to_string(&raw)?;
    if let Err(e) = config::parse_config(&output, config_path) {
        anyhow::bail!("invalid value for '{}': {}", key, e);
    }

    cfgd_core::atomic_write_str(config_path, &output)?;
    printer.success(&format!("Set {} = {}", key, value));
    Ok(())
}

fn cmd_config_unset(cli: &Cli, printer: &Printer, key: &str) -> anyhow::Result<()> {
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }

    let contents = std::fs::read_to_string(config_path)?;
    let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;

    let spec = raw
        .get_mut("spec")
        .ok_or_else(|| anyhow::anyhow!("config has no 'spec' section"))?;

    let (parent, leaf_key) = walk_yaml_path_mut(spec, key)?;
    let yaml_key = serde_yaml::Value::String(leaf_key.clone());
    if parent.remove(&yaml_key).is_none() {
        anyhow::bail!("key '{}' not found in config", key);
    }

    // Validate the result is still parseable
    let output = serde_yaml::to_string(&raw)?;
    if let Err(e) = config::parse_config(&output, config_path) {
        anyhow::bail!("cannot unset '{}': result would be invalid: {}", key, e);
    }

    cfgd_core::atomic_write_str(config_path, &output)?;
    printer.success(&format!("Unset {}", key));
    Ok(())
}

// --- Source CRUD ---

fn cmd_source_edit(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    let source_path = config_dir.join("cfgd-source.yaml");
    if !source_path.exists() {
        anyhow::bail!(
            "No cfgd-source.yaml found in {} — run 'cfgd source create' to scaffold one",
            config_dir.display()
        );
    }

    open_in_editor(&source_path, printer)?;

    // Validate after editing — loop until valid or user cancels
    loop {
        let contents = std::fs::read_to_string(&source_path)?;
        match config::parse_config_source(&contents) {
            Ok(_) => {
                printer.success("Source manifest is valid");
                break;
            }
            Err(e) => {
                printer.error(&format!("Invalid source manifest: {}", e));
                if !printer.prompt_confirm("Re-open in editor to fix?")? {
                    printer.warning("Saved with validation errors");
                    break;
                }
                open_in_editor(&source_path, printer)?;
            }
        }
    }

    Ok(())
}

fn cmd_source_create(
    cli: &Cli,
    printer: &Printer,
    name: Option<&str>,
    description: Option<&str>,
    version: Option<&str>,
) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    let source_path = config_dir.join("cfgd-source.yaml");
    if source_path.exists() {
        anyhow::bail!(
            "cfgd-source.yaml already exists at {} — use 'cfgd source edit' to modify it",
            source_path.display()
        );
    }

    // Interactive mode if no flags provided
    let is_interactive = name.is_none() && description.is_none() && version.is_none();

    // Determine name: flag > interactive prompt > directory name
    let source_name = match name {
        Some(n) => n.to_string(),
        None => {
            let dir_name = config_dir
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("my-config");
            if is_interactive {
                printer.prompt_text("Source name", dir_name)?
            } else {
                dir_name.to_string()
            }
        }
    };

    let source_description = match description {
        Some(d) => d.to_string(),
        None => {
            if is_interactive {
                printer.prompt_text("Description", "Team configuration source")?
            } else {
                "Team configuration source".to_string()
            }
        }
    };

    let source_version = match version {
        Some(v) => v.to_string(),
        None => "0.1.0".to_string(),
    };

    let profile_names = scan_profile_names(&config_dir.join("profiles"))?;
    let module_names = scan_module_names(&config_dir.join("modules"))?;

    // Build profiles YAML block
    let profiles_yaml = if profile_names.is_empty() {
        "    profiles: []".to_string()
    } else {
        let mut lines = vec!["    profiles:".to_string()];
        for p in &profile_names {
            lines.push(format!("      - {}", p));
        }
        lines.join("\n")
    };

    // Build modules YAML block
    let modules_yaml = if module_names.is_empty() {
        "    modules: []".to_string()
    } else {
        let mut lines = vec!["    modules:".to_string()];
        for m in &module_names {
            lines.push(format!("      - {}", m));
        }
        lines.join("\n")
    };

    let yaml = format!(
        "apiVersion: cfgd.io/v1alpha1\n\
         kind: ConfigSource\n\
         metadata:\n\
         \x20 name: {}\n\
         \x20 version: \"{}\"\n\
         \x20 description: \"{}\"\n\
         spec:\n\
         \x20 provides:\n\
         {}\n\
         {}\n\
         \x20 policy:\n\
         \x20   required:\n\
         \x20     packages: {{}}\n\
         \x20     modules: []\n\
         \x20   recommended:\n\
         \x20     packages: {{}}\n\
         \x20     modules: []\n\
         \x20   optional:\n\
         \x20     packages: {{}}\n\
         \x20     modules: []\n\
         \x20   constraints:\n\
         \x20     noScripts: true\n\
         \x20     noSecretsRead: true\n",
        source_name, source_version, source_description, profiles_yaml, modules_yaml,
    );

    cfgd_core::atomic_write_str(&source_path, &yaml)?;
    printer.success(&format!(
        "Created cfgd-source.yaml at {}",
        source_path.display()
    ));
    if !profile_names.is_empty() {
        printer.info(&format!(
            "Included {} profile(s): {}",
            profile_names.len(),
            profile_names.join(", ")
        ));
    }
    if !module_names.is_empty() {
        printer.info(&format!(
            "Included {} module(s): {}",
            module_names.len(),
            module_names.join(", ")
        ));
    }
    printer.info("Edit the file to configure policy tiers and platform-profiles");

    Ok(())
}

// --- Workflow Generation ---

fn cmd_workflow_generate(cli: &Cli, printer: &Printer, force: bool) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    let workflow_dir = config_dir.join(".github").join("workflows");
    let workflow_path = workflow_dir.join("cfgd-release.yml");

    // Scan for profiles and modules
    let profile_names = scan_profile_names(&config_dir.join("profiles"))?;
    let module_names = scan_module_names(&config_dir.join("modules"))?;

    if profile_names.is_empty() && module_names.is_empty() {
        printer.warning("No profiles or modules found — nothing to generate");
        return Ok(());
    }

    // Check for existing file
    if workflow_path.exists()
        && !force
        && !printer
            .prompt_confirm(&format!(
                "Workflow already exists at {} — overwrite?",
                workflow_path.display()
            ))
            .unwrap_or(false)
    {
        printer.info("Skipped workflow generation");
        return Ok(());
    }

    let yaml = generate_release_workflow_yaml(&module_names, &profile_names);

    std::fs::create_dir_all(&workflow_dir)?;
    cfgd_core::atomic_write_str(&workflow_path, &yaml)?;

    printer.success(&format!(
        "Generated release workflow at {}",
        workflow_path.display()
    ));
    printer.info(&format!(
        "Covers {} module(s) and {} profile(s)",
        module_names.len(),
        profile_names.len()
    ));

    Ok(())
}

fn generate_release_workflow_yaml(modules: &[String], profiles: &[String]) -> String {
    let mut yaml = String::new();
    let has_targets = !modules.is_empty() || !profiles.is_empty();

    // Header
    yaml.push_str(
        "# Auto-generated by cfgd — manages release tagging for modules and profiles.\n\
         # Regenerate with: cfgd workflow generate --force\n\
         name: cfgd Release\n\
         \n\
         on:\n\
         \x20 push:\n\
         \x20   branches: [main]\n",
    );

    if has_targets {
        yaml.push_str("    paths:\n");
        for m in modules {
            yaml.push_str(&format!("      - 'modules/{}/**'\n", m));
        }
        for p in profiles {
            yaml.push_str(&format!("      - 'profiles/{}.yaml'\n", p));
            yaml.push_str(&format!("      - 'profiles/{}.yml'\n", p));
        }
    } else {
        yaml.push_str(
            "    # paths: (auto-populated when modules/profiles are created)\n\
             \x20   #   - 'modules/<name>/**'\n\
             \x20   #   - 'profiles/<name>.yaml'\n",
        );
    }

    yaml.push_str(
        "\n\
         permissions:\n\
         \x20 contents: write\n\
         \n\
         jobs:\n",
    );

    if !has_targets {
        yaml.push_str(
            "  # Jobs are auto-generated when modules or profiles are created.\n\
             \x20 # Run `cfgd workflow generate --force` to regenerate manually.\n\
             \x20 placeholder:\n\
             \x20   runs-on: ubuntu-latest\n\
             \x20   steps:\n\
             \x20     - run: echo \"No modules or profiles to tag yet.\"\n",
        );
        return yaml;
    }

    // Detect changes job
    yaml.push_str(
        "\x20 detect-changes:\n\
         \x20   runs-on: ubuntu-latest\n\
         \x20   outputs:\n",
    );
    for m in modules {
        let safe = m.replace('-', "_");
        yaml.push_str(&format!(
            "      module_{}: ${{{{ steps.changes.outputs.module_{} }}}}\n",
            safe, safe
        ));
    }
    for p in profiles {
        let safe = p.replace('-', "_");
        yaml.push_str(&format!(
            "      profile_{}: ${{{{ steps.changes.outputs.profile_{} }}}}\n",
            safe, safe
        ));
    }

    yaml.push_str(
        "\x20   steps:\n\
         \x20     - uses: actions/checkout@v4\n\
         \x20       with:\n\
         \x20         fetch-depth: 0\n\
         \x20     - id: changes\n\
         \x20       run: |\n\
         \x20         if git rev-parse HEAD~1 >/dev/null 2>&1; then\n\
         \x20           CHANGED=$(git diff --name-only HEAD~1 HEAD)\n\
         \x20         else\n\
         \x20           CHANGED=$(git diff-tree --no-commit-id --name-only -r HEAD)\n\
         \x20         fi\n",
    );

    for m in modules {
        let safe = m.replace('-', "_");
        yaml.push_str(&format!(
            "          if echo \"$CHANGED\" | grep -q '^modules/{}/'; then\n\
             \x20           echo \"module_{}=true\" >> $GITHUB_OUTPUT\n\
             \x20         else\n\
             \x20           echo \"module_{}=false\" >> $GITHUB_OUTPUT\n\
             \x20         fi\n",
            m, safe, safe
        ));
    }
    for p in profiles {
        let safe = p.replace('-', "_");
        yaml.push_str(&format!(
            "          if echo \"$CHANGED\" | grep -q '^profiles/{}\\.'; then\n\
             \x20           echo \"profile_{}=true\" >> $GITHUB_OUTPUT\n\
             \x20         else\n\
             \x20           echo \"profile_{}=false\" >> $GITHUB_OUTPUT\n\
             \x20         fi\n",
            p, safe, safe
        ));
    }

    // Tag modules job
    if !modules.is_empty() {
        yaml.push_str(
            "\n\
             \x20 tag-modules:\n\
             \x20   runs-on: ubuntu-latest\n\
             \x20   needs: detect-changes\n\
             \x20   strategy:\n\
             \x20     matrix:\n\
             \x20       include:\n",
        );
        for m in modules {
            let safe = m.replace('-', "_");
            yaml.push_str(&format!(
                "          - name: {}\n\
                 \x20           changed: ${{{{ needs.detect-changes.outputs.module_{} }}}}\n",
                m, safe
            ));
        }
        yaml.push_str(
            "\x20   steps:\n\
             \x20     - uses: actions/checkout@v4\n\
             \x20       if: matrix.changed == 'true'\n\
             \x20       with:\n\
             \x20         fetch-depth: 0\n\
             \x20     - name: Read module version\n\
             \x20       if: matrix.changed == 'true'\n\
             \x20       id: version\n\
             \x20       run: |\n\
             \x20         VERSION=$(grep -oP 'version:\\s*\"?\\K[^\"\\s]+' \"modules/${{ matrix.name }}/module.yaml\" || echo \"0.1.0\")\n\
             \x20         echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n\
             \x20     - name: Tag module release\n\
             \x20       if: matrix.changed == 'true'\n\
             \x20       run: |\n\
             \x20         TAG=\"${{ matrix.name }}/v${{ steps.version.outputs.version }}\"\n\
             \x20         git tag -f \"$TAG\"\n\
             \x20         git push origin \"$TAG\" --force\n",
        );
    }

    // Tag profiles job
    if !profiles.is_empty() {
        yaml.push_str(
            "\n\
             \x20 tag-profiles:\n\
             \x20   runs-on: ubuntu-latest\n\
             \x20   needs: detect-changes\n\
             \x20   strategy:\n\
             \x20     matrix:\n\
             \x20       include:\n",
        );
        for p in profiles {
            let safe = p.replace('-', "_");
            yaml.push_str(&format!(
                "          - name: {}\n\
                 \x20           changed: ${{{{ needs.detect-changes.outputs.profile_{} }}}}\n",
                p, safe
            ));
        }
        yaml.push_str(
            "\x20   steps:\n\
             \x20     - uses: actions/checkout@v4\n\
             \x20       if: matrix.changed == 'true'\n\
             \x20       with:\n\
             \x20         fetch-depth: 0\n\
             \x20     - name: Tag profile release\n\
             \x20       if: matrix.changed == 'true'\n\
             \x20       run: |\n\
             \x20         DATE=$(date +%Y%m%d)\n\
             \x20         TAG=\"profile/${{ matrix.name }}/${DATE}\"\n\
             \x20         git tag -f \"$TAG\"\n\
             \x20         git push origin \"$TAG\" --force\n",
        );
    }

    yaml
}

fn maybe_update_workflow(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let config_dir = config_dir(cli);
    init::regenerate_workflow(&config_dir, printer)?;
    Ok(())
}

// --- Module Commands ---

/// Build a HashMap of manager name → &dyn PackageManager from the registry.
fn managers_map(
    registry: &ProviderRegistry,
) -> std::collections::HashMap<String, &dyn cfgd_core::providers::PackageManager> {
    registry
        .package_managers
        .iter()
        .map(|m| (m.name().to_string(), m.as_ref()))
        .collect()
}

fn module_state_map(
    state: &cfgd_core::state::StateStore,
) -> std::collections::HashMap<String, cfgd_core::state::ModuleStateRecord> {
    state
        .module_states()
        .unwrap_or_default()
        .into_iter()
        .map(|s| (s.module_name.clone(), s))
        .collect()
}

fn open_in_editor(path: &Path, printer: &Printer) -> anyhow::Result<()> {
    let editor = std::env::var("EDITOR")
        .or_else(|_| std::env::var("VISUAL"))
        .unwrap_or_else(|_| "vi".to_string());

    let status = std::process::Command::new(&editor)
        .arg(path)
        .stdin(std::process::Stdio::inherit())
        .stdout(std::process::Stdio::inherit())
        .stderr(std::process::Stdio::inherit())
        .status()
        .map_err(|e| anyhow::anyhow!("Failed to open editor '{}': {}", editor, e))?;

    if !status.success() {
        printer.warning(&format!("Editor '{}' exited with non-zero status", editor));
    }
    Ok(())
}

/// Resolve the secret backend from config, check availability, and validate the file exists.
/// Returns a registry whose `secret_backend` is guaranteed `Some`.
fn resolve_secret_backend(cli: &Cli, file: &Path) -> anyhow::Result<ProviderRegistry> {
    let cfg = if cli.config.exists() {
        Some(config::load_config(&cli.config)?)
    } else {
        None
    };

    let mut registry = build_registry_with_config(cfg.as_ref());

    // Rebuild secret backend with config dir so sops can find .sops.yaml
    let cd = config_dir(cli);
    let (backend_name, age_key_path) = secret_backend_from_config(cfg.as_ref());
    registry.secret_backend = Some(secrets::build_secret_backend(
        &backend_name,
        age_key_path,
        Some(&cd),
    ));

    if !file.exists() {
        anyhow::bail!("File not found: {}", file.display());
    }

    match registry.secret_backend {
        Some(ref backend) if !backend.is_available() => {
            anyhow::bail!("{}: not installed", backend.name());
        }
        None => anyhow::bail!("No secret backend configured"),
        _ => {}
    }

    Ok(registry)
}

/// Shorthand: resolve secret backend and extract it in one call.
fn get_secret_backend(cli: &Cli, file: &Path) -> anyhow::Result<Box<dyn SecretBackend>> {
    let registry = resolve_secret_backend(cli, file)?;
    registry
        .secret_backend
        .ok_or_else(|| anyhow::anyhow!("No secret backend configured"))
}

fn cmd_secret_encrypt(cli: &Cli, printer: &Printer, file: &Path) -> anyhow::Result<()> {
    printer.header("Secret Encrypt");

    let backend = get_secret_backend(cli, file)?;
    backend.encrypt_file(file)?;

    printer.newline();
    printer.success(&format!(
        "Encrypted {} via {}",
        file.display(),
        backend.name()
    ));

    Ok(())
}

fn cmd_secret_decrypt(cli: &Cli, printer: &Printer, file: &Path) -> anyhow::Result<()> {
    printer.header("Secret Decrypt");

    let backend = get_secret_backend(cli, file)?;
    let decrypted = backend.decrypt_file(file)?;
    printer.info(secrecy::ExposeSecret::expose_secret(&decrypted));

    Ok(())
}

fn cmd_secret_edit(cli: &Cli, printer: &Printer, file: &Path) -> anyhow::Result<()> {
    printer.header("Secret Edit");

    let backend = get_secret_backend(cli, file)?;
    backend.edit_file(file)?;

    printer.newline();
    printer.success(&format!(
        "Edited and re-encrypted {} via {}",
        file.display(),
        backend.name()
    ));

    Ok(())
}

fn cmd_secret_init(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    printer.header("Secret Init");

    let config_dir = config_dir(cli);
    let key_path = secrets::init_age_key(&config_dir)?;

    printer.newline();
    printer.success(&format!("Age key: {}", key_path.display()));

    let sops_config = config_dir.join(".sops.yaml");
    if sops_config.exists() {
        printer.success(&format!(".sops.yaml: {}", sops_config.display()));
    }

    printer.info("Secrets setup complete — files can now be encrypted with 'cfgd secret encrypt'");

    Ok(())
}

fn cmd_doctor(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    // Gather data for both structured and human output
    let (config_check, loaded_cfg) = if cli.config.exists() {
        match config::load_config(&cli.config) {
            Ok(cfg) => (
                DoctorConfigCheck {
                    valid: true,
                    path: cli.config.display().to_string(),
                    name: Some(cfg.metadata.name.clone()),
                    profile: cfg.spec.profile.clone(),
                    error: None,
                },
                Some(cfg),
            ),
            Err(e) => (
                DoctorConfigCheck {
                    valid: false,
                    path: cli.config.display().to_string(),
                    name: None,
                    profile: None,
                    error: Some(format!("{}", e)),
                },
                None,
            ),
        }
    } else {
        (
            DoctorConfigCheck {
                valid: false,
                path: cli.config.display().to_string(),
                name: None,
                profile: None,
                error: Some("not found".into()),
            },
            None,
        )
    };

    let git_available = cfgd_core::command_available("git");

    let config_dir = config_dir(cli);
    let age_key_override = loaded_cfg
        .as_ref()
        .and_then(|c| c.spec.secrets.as_ref())
        .and_then(|s| s.sops.as_ref())
        .and_then(|s| s.age_key.as_ref());

    let health = secrets::check_secrets_health(&config_dir, age_key_override.map(|p| p.as_path()));

    // Build structured doctor output for --output json/yaml
    // Resolve profile to get declared managers (including custom) and build registry
    let resolved_packages = if let Some(ref cfg) = loaded_cfg {
        let profiles_dir = profiles_dir(cli);
        let profile_name = cli.profile.as_deref().or(cfg.spec.profile.as_deref());
        if let Some(pn) = profile_name
            && let Ok(mut resolved) = config::resolve_profile(pn, &profiles_dir)
        {
            let _ = packages::resolve_manifest_packages(&mut resolved.merged.packages, &config_dir);
            Some(resolved.merged.packages)
        } else {
            None
        }
    } else {
        None
    };

    let registry = if let Some(ref pkgs) = resolved_packages {
        build_registry_with_profile(pkgs)
    } else {
        build_registry()
    };
    let all_managers = &registry.package_managers;

    // Determine which managers are declared in config
    let declared_managers: Vec<String> = if let Some(ref pkgs) = resolved_packages {
        let mut declared = Vec::new();
        if let Some(ref brew) = pkgs.brew
            && (!brew.formulae.is_empty() || !brew.taps.is_empty() || !brew.casks.is_empty())
        {
            declared.push("brew".to_string());
        }
        if let Some(ref apt) = pkgs.apt
            && !apt.packages.is_empty()
        {
            declared.push("apt".to_string());
        }
        if let Some(ref cargo) = pkgs.cargo
            && !cargo.packages.is_empty()
        {
            declared.push("cargo".to_string());
        }
        if let Some(ref npm) = pkgs.npm
            && !npm.global.is_empty()
        {
            declared.push("npm".to_string());
        }
        for (name, _) in pkgs.non_empty_simple_lists() {
            declared.push(name.to_string());
        }
        if let Some(ref snap) = pkgs.snap
            && !snap.packages.is_empty()
        {
            declared.push("snap".to_string());
        }
        if let Some(ref flatpak) = pkgs.flatpak
            && !flatpak.packages.is_empty()
        {
            declared.push("flatpak".to_string());
        }
        for custom in &pkgs.custom {
            if !custom.packages.is_empty() {
                declared.push(custom.name.clone());
            }
        }
        declared
    } else {
        Vec::new()
    };

    // Build manager check data (deduplicate brew-tap/brew-cask under brew)
    let mut manager_checks: Vec<DoctorManagerCheck> = Vec::new();
    {
        let mut seen = std::collections::HashSet::new();
        for mgr in all_managers.iter() {
            let name = mgr.name();
            if name == "brew-tap" || name == "brew-cask" {
                continue;
            }
            if !seen.insert(name.to_string()) {
                continue;
            }
            manager_checks.push(DoctorManagerCheck {
                name: name.to_string(),
                available: mgr.is_available(),
                declared: declared_managers.iter().any(|d| d == name),
                can_bootstrap: mgr.can_bootstrap(),
            });
        }
    }

    // Modules health
    let module_list: Vec<String> = if let Some(ref cfg) = loaded_cfg {
        let profiles_dir = profiles_dir(cli);
        let profile_name = cli.profile.as_deref().or(cfg.spec.profile.as_deref());
        profile_name
            .and_then(|pn| config::resolve_profile(pn, &profiles_dir).ok())
            .map(|r| r.merged.modules)
            .unwrap_or_default()
    } else {
        Vec::new()
    };

    let cache_base = modules::default_module_cache_dir().unwrap_or_default();
    let all_modules =
        modules::load_all_modules(&config_dir, &cache_base, printer).unwrap_or_default();
    let module_checks: Vec<DoctorModuleCheck> = module_list
        .iter()
        .map(|mod_name| {
            if all_modules.contains_key(mod_name) {
                DoctorModuleCheck {
                    name: mod_name.clone(),
                    valid: true,
                    error: None,
                }
            } else {
                DoctorModuleCheck {
                    name: mod_name.clone(),
                    valid: false,
                    error: Some("module not found".into()),
                }
            }
        })
        .collect();

    // System configurators
    let configurator_checks: Vec<DoctorConfiguratorCheck> = registry
        .available_system_configurators()
        .iter()
        .map(|c| DoctorConfiguratorCheck {
            name: c.name().to_string(),
            available: true,
        })
        .collect();

    // Structured output
    if printer.write_structured(&DoctorOutput {
        config: config_check.clone(),
        git: git_available,
        secrets: DoctorSecretsCheck {
            sops_available: health.sops_available,
            sops_version: health.sops_version.clone(),
            age_key_exists: health.age_key_exists,
            age_key_path: health
                .age_key_path
                .as_ref()
                .map(|p| p.display().to_string()),
            sops_config_exists: health.sops_config_exists,
            providers: health
                .providers
                .iter()
                .map(|(n, a)| DoctorProviderCheck {
                    name: n.clone(),
                    available: *a,
                })
                .collect(),
        },
        package_managers: manager_checks,
        modules: module_checks,
        system_configurators: configurator_checks,
    }) {
        return Ok(());
    }

    // Human display
    printer.header("Doctor");

    let mut all_ok = config_check.valid && git_available;

    if config_check.valid {
        printer.success(&format!("Config file: {} (valid)", config_check.path));
        if let Some(name) = loaded_cfg.as_ref().map(|c| &c.metadata.name) {
            printer.key_value("Name", name);
        }
        printer.key_value(
            "Profile",
            loaded_cfg
                .as_ref()
                .and_then(|c| c.spec.profile.as_deref())
                .unwrap_or("(none)"),
        );
    } else if let Some(ref err) = config_check.error {
        if err == "not found" {
            printer.warning(&format!(
                "Config file not found: {} — run 'cfgd init' to create one",
                config_check.path
            ));
        } else {
            printer.error(&format!("Config file: {} — {}", config_check.path, err));
        }
    }

    if git_available {
        printer.success("git: found");
    } else {
        printer.error("git: not found — install git to use cfgd");
    }

    // Secrets
    printer.newline();
    printer.subheader("Secrets");

    if health.sops_available {
        let version_str = health.sops_version.as_deref().unwrap_or("unknown version");
        printer.success(&format!("sops: found ({})", version_str));
    } else {
        printer.warning(
            "sops: not found — required for secrets (https://github.com/getsops/sops#install)",
        );
    }

    if health.age_key_exists {
        if let Some(ref path) = health.age_key_path {
            printer.success(&format!("age key: {}", path.display()));
        }
    } else if let Some(ref path) = health.age_key_path {
        printer.warning(&format!(
            "age key: not found at {} — run 'cfgd init' to generate",
            path.display()
        ));
    }

    if health.sops_config_exists {
        if let Some(ref path) = health.sops_config_path {
            printer.success(&format!(".sops.yaml: {}", path.display()));
        }
    } else {
        printer.warning(".sops.yaml: not found — will be generated on 'cfgd init'");
    }

    for (name, available) in &health.providers {
        if *available {
            printer.success(&format!("provider {}: available", name));
        } else {
            printer.info(&format!("provider {}: not installed (optional)", name));
        }
    }

    // Package managers
    printer.newline();
    printer.subheader("Package Managers");

    let mut shown_managers = std::collections::HashSet::new();
    for mgr in all_managers.iter() {
        let name = mgr.name();
        if name == "brew-tap" || name == "brew-cask" {
            continue;
        }
        if !shown_managers.insert(name.to_string()) {
            continue;
        }
        let is_declared = declared_managers.iter().any(|d| d == name);
        let available = mgr.is_available();

        if is_declared {
            if available {
                printer.success(&format!("{}: available (declared in config)", name));
            } else if mgr.can_bootstrap() {
                let method = packages::bootstrap_method(mgr.as_ref());
                printer.warning(&format!(
                    "{}: not found — can auto-bootstrap via {}",
                    name, method
                ));
            } else {
                printer.error(&format!(
                    "{}: not found — declared in config but not available",
                    name
                ));
                all_ok = false;
            }
        } else if available {
            printer.info(&format!("{}: available (not used in config)", name));
        }
    }

    if !module_list.is_empty() {
        printer.newline();
        printer.subheader("Modules");

        let registry_for_modules = build_registry();
        let mgr_map = managers_map(&registry_for_modules);
        let platform = Platform::detect();

        for mod_name in &module_list {
            if let Some(module) = all_modules.get(mod_name) {
                printer.info(&format!("{}:", mod_name));
                for entry in &module.spec.packages {
                    match modules::resolve_package(entry, mod_name, &platform, &mgr_map) {
                        Ok(Some(resolved)) => {
                            let installed = mgr_map
                                .get(&resolved.manager)
                                .and_then(|m| m.installed_packages().ok())
                                .map(|pkgs| pkgs.contains(&resolved.resolved_name))
                                .unwrap_or(false);
                            if installed {
                                let ver = resolved.version.as_deref().unwrap_or("?");
                                printer.success(&format!(
                                    "  {} {} ({}, {})",
                                    entry.name, ver, resolved.manager, resolved.resolved_name
                                ));
                            } else {
                                printer.error(&format!(
                                    "  {} — not installed ({} {})",
                                    entry.name, resolved.manager, resolved.resolved_name
                                ));
                                all_ok = false;
                            }
                        }
                        Ok(None) => {
                            printer.info(&format!("  {} — skipped (platform)", entry.name));
                        }
                        Err(e) => {
                            printer.error(&format!("  {} — {}", entry.name, e));
                            all_ok = false;
                        }
                    }
                }
            } else {
                printer.error(&format!(
                    "{}: module not found in {}/modules/",
                    mod_name,
                    config_dir.display()
                ));
                all_ok = false;
            }
        }
    }

    // Check state store
    printer.newline();
    printer.subheader("System");

    match StateStore::open_default() {
        Ok(_) => printer.success("State store: accessible"),
        Err(e) => {
            printer.warning(&format!("State store: {}", e));
        }
    }

    // Check profiles directory
    let profiles_dir = profiles_dir(cli);
    if profiles_dir.exists() {
        let count = std::fs::read_dir(&profiles_dir)
            .map(|entries| {
                entries
                    .filter_map(|e| e.ok())
                    .filter(|e| e.path().extension().and_then(|ext| ext.to_str()) == Some("yaml"))
                    .count()
            })
            .unwrap_or(0);
        printer.success(&format!(
            "Profiles directory: {} ({} profiles)",
            profiles_dir.display(),
            count
        ));
    } else {
        printer.warning(&format!(
            "Profiles directory not found: {}",
            profiles_dir.display()
        ));
    }

    // Check config sources
    let doctor_config_path = &cli.config;
    if doctor_config_path.exists()
        && let Ok(cfg) = config::load_config(doctor_config_path)
        && !cfg.spec.sources.is_empty()
    {
        printer.newline();
        printer.subheader("Config Sources");
        let cache_dir = source_cache_dir(cli).ok();
        for source in &cfg.spec.sources {
            let cached = cache_dir.as_ref().and_then(|cd| {
                if cd.join(&source.name).exists() {
                    Some(format!("cached at {}", cd.join(&source.name).display()))
                } else {
                    None
                }
            });
            match cached {
                Some(info) => printer.success(&format!("{}: {}", source.name, info)),
                None => printer.warning(&format!(
                    "{}: not cached (run 'cfgd source update')",
                    source.name
                )),
            }
        }
    }

    printer.newline();
    if all_ok {
        printer.success("All checks passed");
    } else {
        printer.error("Some checks failed — see above");
    }

    Ok(())
}

pub(crate) fn config_dir(cli: &Cli) -> PathBuf {
    cli.config
        .parent()
        .unwrap_or_else(|| Path::new("."))
        .to_path_buf()
}

fn profiles_dir(cli: &Cli) -> PathBuf {
    config_dir(cli).join("profiles")
}

/// List sorted YAML file stems in a directory (e.g. "base" from "base.yaml").
/// Returns an empty vec if the directory doesn't exist.
pub(super) fn list_yaml_stems(dir: &Path) -> anyhow::Result<Vec<String>> {
    let mut names = Vec::new();
    if dir.exists() {
        for entry in std::fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().is_some_and(|e| e == "yaml" || e == "yml")
                && let Some(stem) = path.file_stem().and_then(|s| s.to_str())
            {
                names.push(stem.to_string());
            }
        }
        names.sort();
    }
    Ok(names)
}

/// Resolve profile name from explicit name or default to active profile.
fn resolve_profile_name(cli: &Cli, name: Option<&str>) -> anyhow::Result<String> {
    if let Some(n) = name {
        return Ok(n.to_string());
    }
    // Default to active profile
    let config_path = &cli.config;
    if !config_path.exists() {
        anyhow::bail!("{}", MSG_NO_CONFIG);
    }
    let cfg = config::load_config(config_path)?;
    if let Some(ref profile_override) = cli.profile {
        Ok(profile_override.clone())
    } else {
        Ok(cfg.active_profile()?.to_string())
    }
}

fn cmd_daemon(cli: &Cli, printer: &Printer, command: Option<&DaemonCommand>) -> anyhow::Result<()> {
    match command {
        Some(DaemonCommand::Status) => return cmd_daemon_status(printer),
        Some(DaemonCommand::Install) => return cmd_daemon_install(cli, printer),
        Some(DaemonCommand::Uninstall) => return cmd_daemon_uninstall(printer),
        Some(DaemonCommand::Service) => return cmd_daemon_service(),
        Some(DaemonCommand::Run) | None => {}
    }

    // Run daemon in foreground
    let config_path = std::fs::canonicalize(&cli.config).unwrap_or_else(|_| cli.config.clone());
    let profile_override = cli.profile.clone();
    let printer = std::sync::Arc::new(cfgd_core::output::Printer::new(if cli.quiet {
        cfgd_core::output::Verbosity::Quiet
    } else if cli.verbose {
        cfgd_core::output::Verbosity::Verbose
    } else {
        cfgd_core::output::Verbosity::Normal
    }));

    let hooks: std::sync::Arc<dyn cfgd_core::daemon::DaemonHooks> =
        std::sync::Arc::new(WorkstationDaemonHooks);
    let rt = tokio::runtime::Runtime::new()?;
    let result = rt.block_on(async {
        cfgd_core::daemon::run_daemon(config_path, profile_override, printer, hooks).await
    });
    rt.shutdown_timeout(std::time::Duration::from_secs(2));
    result?;

    Ok(())
}

fn cmd_daemon_status(printer: &Printer) -> anyhow::Result<()> {
    let status = cfgd_core::daemon::query_daemon_status()?;

    if printer.is_structured() {
        match &status {
            Some(s) => printer.write_structured(s),
            None => printer.write_structured(&cfgd_core::daemon::DaemonStatusResponse {
                running: false,
                pid: 0,
                uptime_secs: 0,
                last_reconcile: None,
                last_sync: None,
                drift_count: 0,
                sources: vec![],
                update_available: None,
                module_reconcile: vec![],
            }),
        };
        return Ok(());
    }

    printer.header("Daemon Status");

    match status {
        Some(status) => {
            printer.success("Daemon is running");
            printer.key_value("PID", &status.pid.to_string());
            printer.key_value("Uptime", &format!("{}s", status.uptime_secs));
            printer.key_value("Drift count", &status.drift_count.to_string());

            if let Some(ref last) = status.last_reconcile {
                printer.key_value("Last reconcile", last);
            }
            if let Some(ref last) = status.last_sync {
                printer.key_value("Last sync", last);
            }

            if let Some(ref version) = status.update_available {
                printer.newline();
                printer.warning(&format!(
                    "Update available: {} — run 'cfgd upgrade' to install",
                    version
                ));
            }

            printer.newline();
            printer.subheader("Sources");
            printer.table(
                &["Name", "Status", "Drift", "Last Sync"],
                &status
                    .sources
                    .iter()
                    .map(|s| {
                        vec![
                            s.name.clone(),
                            s.status.clone(),
                            s.drift_count.to_string(),
                            s.last_sync.clone().unwrap_or_else(|| "-".to_string()),
                        ]
                    })
                    .collect::<Vec<_>>(),
            );
        }
        None => {
            printer.warning("Daemon is not running");
            printer.info("Start with: cfgd daemon");
            printer.info("Install as service: cfgd daemon install");
        }
    }

    Ok(())
}

fn cmd_daemon_install(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    printer.header("Install Daemon Service");

    cfgd_core::daemon::install_service(&cli.config, cli.profile.as_deref())?;

    #[cfg(windows)]
    {
        printer.success("cfgd service installed and started");
        printer.info("The service will start automatically on boot");
        printer.info("Logs: %LOCALAPPDATA%\\cfgd\\daemon.log");
    }
    #[cfg(unix)]
    {
        print_daemon_install_success(printer);
    }

    Ok(())
}

fn cmd_daemon_uninstall(printer: &Printer) -> anyhow::Result<()> {
    printer.header("Uninstall Daemon Service");

    if cfg!(windows) {
        printer.info("Stopping and removing Windows Service: cfgd");
    } else if cfg!(target_os = "macos") {
        printer.info("Unloading: launchctl unload ~/Library/LaunchAgents/com.cfgd.daemon.plist");
    } else {
        printer.info("Stopping: systemctl --user disable --now cfgd.service");
    }

    cfgd_core::daemon::uninstall_service()?;
    printer.success("Daemon service removed");

    Ok(())
}

fn cmd_daemon_service() -> anyhow::Result<()> {
    let hooks: std::sync::Arc<dyn cfgd_core::daemon::DaemonHooks> =
        std::sync::Arc::new(WorkstationDaemonHooks);
    cfgd_core::daemon::run_as_windows_service(hooks)?;
    Ok(())
}

fn cmd_upgrade(printer: &Printer, check_only: bool) -> anyhow::Result<()> {
    use cfgd_core::upgrade;

    if check_only {
        let check = upgrade::check_latest(None, Some(printer))?;

        if check.update_available {
            printer.info(&format!(
                "Update available: {} -> {}",
                check.current, check.latest
            ));
            printer.info("Run 'cfgd upgrade' to install");
            // Exit code 1 = update available (scriptable)
            std::process::exit(1);
        } else {
            printer.success(&format!("cfgd {} is up to date", check.current));
        }

        return Ok(());
    }

    printer.header("Upgrade");

    let check = upgrade::check_latest(None, Some(printer))?;

    if !check.update_available {
        printer.success(&format!(
            "cfgd {} is already the latest version",
            check.current
        ));
        return Ok(());
    }

    printer.info(&format!(
        "Update available: {} -> {}",
        check.current, check.latest
    ));

    let release = check
        .release
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("release info not available"))?;

    let asset = upgrade::find_asset_for_platform(release)?;
    printer.key_value("Binary", &asset.name);
    if asset.size > 0 {
        printer.key_value("Size", &format_bytes(asset.size));
    }
    printer.newline();

    let installed_path = upgrade::download_and_install(release, asset, Some(printer))?;
    printer.success(&format!("Installed to {}", installed_path.display()));

    // Invalidate version cache since we just upgraded
    upgrade::invalidate_cache();

    // Restart daemon if running
    if upgrade::restart_daemon_if_running() {
        printer.info("Daemon restarted with new version");
    }

    printer.newline();
    printer.success(&format!("cfgd upgraded to {}", check.latest));

    Ok(())
}

fn format_bytes(bytes: u64) -> String {
    if bytes >= 1024 * 1024 {
        format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
    } else if bytes >= 1024 {
        format!("{:.1} KB", bytes as f64 / 1024.0)
    } else {
        format!("{} B", bytes)
    }
}

fn cmd_rollback(
    printer: &Printer,
    apply_id: i64,
    yes: bool,
    state_dir: Option<&Path>,
) -> anyhow::Result<()> {
    let state = open_state_store(state_dir)?;

    // Check if the target apply exists
    if state.get_apply(apply_id)?.is_none() {
        anyhow::bail!("no apply found with ID {}", apply_id);
    }

    // Preview: count file backups available for rollback (target apply's own
    // post-apply snapshots + subsequent apply backups) and non-file actions.
    let target_backups = state.get_apply_backups(apply_id)?;
    let after_backups = state.file_backups_after_apply(apply_id)?;
    let after_entries = state.journal_entries_after_apply(apply_id)?;

    // Unique file paths across both sources
    let mut file_paths = std::collections::HashSet::new();
    for bk in &target_backups {
        file_paths.insert(bk.file_path.clone());
    }
    for bk in &after_backups {
        file_paths.insert(bk.file_path.clone());
    }
    let file_count = file_paths.len();
    let non_file_actions: Vec<String> = after_entries
        .iter()
        .filter(|e| {
            !(e.phase == "files" || e.action_type == "file" || e.resource_id.starts_with("file:"))
        })
        .map(|e| e.resource_id.clone())
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .collect();
    let non_file_count = non_file_actions.len();

    printer.header("Rollback");
    printer.key_value("Target apply ID", &apply_id.to_string());
    printer.key_value("File backups to restore", &file_count.to_string());
    if non_file_count > 0 {
        printer.key_value(
            "Non-file actions (manual review)",
            &non_file_count.to_string(),
        );
    }

    if file_count == 0 && non_file_count == 0 {
        printer.info("No subsequent changes to roll back — system is already at this apply");
        return Ok(());
    }

    // Confirm
    if !yes {
        printer.newline();
        let confirmed = printer
            .prompt_confirm("Roll back to this apply?")
            .unwrap_or(false);
        if !confirmed {
            printer.info("Aborted");
            return Ok(());
        }
    }

    printer.newline();

    // Construct a minimal Reconciler — rollback only needs state, but Reconciler
    // requires a ProviderRegistry reference.
    let registry = ProviderRegistry::new();
    let reconciler = Reconciler::new(&registry, &state);
    let result = reconciler.rollback_apply(apply_id, printer)?;

    if printer.is_structured() {
        printer.write_structured(&RollbackOutput {
            apply_id,
            files_restored: result.files_restored,
            files_removed: result.files_removed,
            non_file_actions: result.non_file_actions.clone(),
        });
        return Ok(());
    }

    printer.newline();
    if result.files_restored > 0 {
        printer.success(&format!(
            "{} file(s) restored from backup",
            result.files_restored
        ));
    }
    if result.files_removed > 0 {
        printer.success(&format!(
            "{} newly created file(s) removed",
            result.files_removed
        ));
    }

    if !result.non_file_actions.is_empty() {
        printer.newline();
        printer.warning(&format!(
            "{} non-file action(s) require manual review:",
            result.non_file_actions.len()
        ));
        for action in &result.non_file_actions {
            printer.info(&format!("  {}", action));
        }
    }

    if result.files_restored == 0 && result.files_removed == 0 {
        printer.info("No files were changed during rollback");
    } else {
        printer.newline();
        printer.success("Rollback complete");
    }

    Ok(())
}

fn cmd_sync(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    printer.header("Sync");

    let (cfg, _resolved) = load_config_and_profile(cli, printer)?;
    let config_dir = config_dir(cli);

    printer.newline();
    printer.info("Syncing local repo with remote...");

    // Pull local config repo
    match cfgd_core::daemon::git_pull_sync(&config_dir) {
        Ok(true) => printer.success("Pulled new changes from remote"),
        Ok(false) => printer.success("Already up to date"),
        Err(e) => printer.warning(&format!("Pull failed: {}", e)),
    }

    // Sync all configured sources
    if !cfg.spec.sources.is_empty() {
        printer.newline();
        printer.subheader("Sources");

        let cache_dir = source_cache_dir(cli)?;
        let mut mgr = SourceManager::new(&cache_dir);
        mgr.set_allow_unsigned(cfg.spec.security.as_ref().is_some_and(|s| s.allow_unsigned));
        let mut changes_detected = false;

        for source_spec in &cfg.spec.sources {
            printer.info(&format!("Syncing source '{}'...", source_spec.name));

            // Capture old manifest before syncing (for permission change detection)
            let source_dir = cache_dir.join(&source_spec.name);
            let old_manifest = if source_dir.exists() {
                mgr.parse_manifest(&source_spec.name, &source_dir).ok()
            } else {
                None
            };

            match mgr.load_source(source_spec, printer) {
                Ok(()) => {
                    if let Some(cached) = mgr.get(&source_spec.name) {
                        // Detect permission-expanding changes
                        if let Some(ref old) = old_manifest {
                            let old_input =
                                build_permission_input(&source_spec.name, &old.spec.policy);
                            let new_input = build_permission_input(
                                &source_spec.name,
                                &cached.manifest.spec.policy,
                            );
                            let perm_changes =
                                composition::detect_permission_changes(&[old_input], &[new_input]);
                            if !perm_changes.is_empty() {
                                printer.newline();
                                printer.warning(&format!(
                                    "Source '{}' update changes permissions:",
                                    source_spec.name
                                ));
                                for change in &perm_changes {
                                    printer.warning(&format!("  - {}", change.description));
                                }
                                match printer.prompt_confirm("Accept permission changes?") {
                                    Ok(true) => {}
                                    Ok(false) => {
                                        printer.info(&format!(
                                            "Skipped source '{}' (permission changes rejected)",
                                            source_spec.name
                                        ));
                                        continue;
                                    }
                                    Err(_) => {
                                        printer.info(&format!(
                                            "Skipped source '{}' (prompt cancelled)",
                                            source_spec.name
                                        ));
                                        continue;
                                    }
                                }
                            }
                        }

                        let commit_short = cached
                            .last_commit
                            .as_deref()
                            .map(|c| &c[..c.len().min(12)])
                            .unwrap_or("unknown");
                        printer.success(&format!(
                            "'{}' synced (commit: {})",
                            source_spec.name, commit_short
                        ));
                        changes_detected = true;
                    }
                }
                Err(e) => {
                    printer.warning(&format!("Failed to sync '{}': {}", source_spec.name, e));
                }
            }
        }

        if changes_detected {
            printer.newline();
            printer.info(&format!("Sources updated. {}", MSG_RUN_APPLY));
        }
    }

    Ok(())
}

fn cmd_pull(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    printer.header("Pull");

    let (_cfg, _resolved) = load_config_and_profile(cli, printer)?;
    let config_dir = config_dir(cli);

    printer.newline();

    match cfgd_core::daemon::git_pull_sync(&config_dir) {
        Ok(true) => printer.success("Pulled new changes from remote"),
        Ok(false) => printer.success("Already up to date"),
        Err(e) => printer.warning(&format!("Pull failed: {}", e)),
    }

    Ok(())
}

fn default_device_id() -> String {
    cfgd_core::hostname_string()
}

// --- Source management commands (Phase 9) ---

fn source_cache_dir(cli: &Cli) -> anyhow::Result<std::path::PathBuf> {
    if let Some(ref state_dir) = cli.state_dir {
        Ok(state_dir.join("sources"))
    } else {
        SourceManager::default_cache_dir().map_err(|e| anyhow::anyhow!(e))
    }
}

fn cmd_source_add(cli: &Cli, printer: &Printer, args: &SourceAddArgs) -> anyhow::Result<()> {
    let url = &args.url;
    let name = args.name.as_deref();
    let branch = args.branch.as_deref();
    let profile = args.profile.as_deref();
    let accept_recommended = args.accept_recommended;
    let priority = args.priority;
    let opt_in = &args.opt_in;
    let sync_interval = args.sync_interval.as_deref();
    let auto_apply = args.auto_apply;
    let pin_version = args.pin_version.as_deref();
    printer.header("Add Config Source");

    // Infer name from URL if not provided
    let source_name = name
        .map(|s| s.to_string())
        .unwrap_or_else(|| infer_source_name(url));

    // Check if source already exists in config
    let config_path = cli.config.clone();
    if config_path.exists() {
        let cfg = config::load_config(&config_path)?;
        if cfg.spec.sources.iter().any(|s| s.name == source_name) {
            anyhow::bail!(
                "Source '{}' already exists. Use 'cfgd source update' to refresh.",
                source_name
            );
        }
    }

    // Clone and parse the source
    let cache_dir = source_cache_dir(cli)?;
    let mut mgr = SourceManager::new(&cache_dir);
    if config_path.exists()
        && let Ok(existing_cfg) = config::load_config(&config_path)
    {
        mgr.set_allow_unsigned(
            existing_cfg
                .spec
                .security
                .as_ref()
                .is_some_and(|s| s.allow_unsigned),
        );
    }
    let spec = SourceManager::build_source_spec(&source_name, url, profile);
    mgr.load_source(&spec, printer)?;

    let cached = mgr
        .get(&source_name)
        .ok_or_else(|| anyhow::anyhow!("Failed to load source '{}'", source_name))?;

    // Display source manifest info
    let manifest = &cached.manifest;
    printer.subheader("Source Manifest");
    printer.key_value("Name", &manifest.metadata.name);
    if let Some(ref version) = manifest.metadata.version {
        printer.key_value("Version", version);
    }
    if let Some(ref desc) = manifest.metadata.description {
        printer.key_value("Description", desc);
    }

    let provided_profiles = cfgd_core::config::source_profile_names(&manifest.spec.provides);
    if !provided_profiles.is_empty() {
        printer.key_value("Profiles", &provided_profiles.join(", "));
    }

    // Show policy summary
    let policy = &manifest.spec.policy;
    let required_count = count_policy_items(&policy.required);
    let recommended_count = count_policy_items(&policy.recommended);
    let locked_count = count_policy_items(&policy.locked);

    printer.newline();
    printer.subheader("Policy");
    if locked_count > 0 {
        printer.warning(&format!(
            "{} locked item(s) (cannot override)",
            locked_count
        ));
    }
    if required_count > 0 {
        printer.info(&format!(
            "{} required item(s) (team requirement)",
            required_count
        ));
    }
    if recommended_count > 0 {
        printer.info(&format!("{} recommended item(s)", recommended_count));
    }

    // Show constraints
    let constraints = &manifest.spec.policy.constraints;
    if constraints.no_scripts {
        printer.info("Scripts: blocked");
    }
    if constraints.no_secrets_read {
        printer.info("Secret access: blocked");
    }
    if !constraints.allowed_target_paths.is_empty() {
        printer.info(&format!(
            "Allowed paths: {}",
            constraints.allowed_target_paths.join(", ")
        ));
    }

    // Profile selection: explicit flag > platform auto-detect > single profile > interactive
    let auto_detected_profile =
        if profile.is_none() && !manifest.spec.provides.platform_profiles.is_empty() {
            let platform = cfgd_core::config::detect_platform();
            cfgd_core::config::match_platform_profile(
                &platform,
                &manifest.spec.provides.platform_profiles,
            )
            .inspect(|matched| {
                printer.success(&format!(
                    "Auto-selected profile '{}' for platform {}",
                    matched,
                    platform.distro.as_deref().unwrap_or(&platform.os)
                ));
            })
        } else {
            None
        };

    let selected_profile = if profile.is_some() {
        profile.map(|s| s.to_string())
    } else if auto_detected_profile.is_some() {
        auto_detected_profile
    } else if provided_profiles.len() == 1 {
        Some(provided_profiles[0].clone())
    } else if !provided_profiles.is_empty() {
        printer.newline();
        let selection =
            printer.prompt_select("Select a profile to subscribe to:", &provided_profiles)?;
        Some(selection.clone())
    } else {
        None
    };

    // Interactive priority prompt (when --priority not specified on command line)
    let resolved_priority = if let Some(p) = priority {
        p
    } else if args.yes {
        500
    } else {
        printer.newline();
        let input = printer.prompt_text("Set priority", "500")?;
        input
            .parse::<u32>()
            .map_err(|_| anyhow::anyhow!("invalid priority: '{}' (must be a number)", input))?
    };

    // Conflict preview: check for conflicts with current config before subscribing
    if config_path.exists()
        && let Ok(cfg) = config::load_config(&config_path)
    {
        let pdir = config_path
            .parent()
            .unwrap_or_else(|| Path::new("."))
            .join("profiles");
        let profile_name = cli.profile.as_deref().or(cfg.spec.profile.as_deref());

        if let Some(pn) = profile_name
            && let Ok(local_resolved) = config::resolve_profile(pn, &pdir)
        {
            // Build a CompositionInput for the prospective source
            let mut preview_layers = Vec::new();
            if let Some(ref pn) = selected_profile
                && let Ok(src_profiles_dir) = mgr.source_profiles_dir(&source_name)
                && src_profiles_dir.exists()
                && let Ok(r) = config::resolve_profile(pn, &src_profiles_dir)
            {
                preview_layers = r.layers;
            }

            let preview_sub = config::SubscriptionSpec {
                profile: selected_profile.clone(),
                priority: resolved_priority,
                accept_recommended,
                opt_in: opt_in.to_vec(),
                ..Default::default()
            };

            let preview_input = CompositionInput {
                source_name: source_name.clone(),
                priority: resolved_priority,
                policy: manifest.spec.policy.clone(),
                constraints: manifest.spec.policy.constraints.clone(),
                layers: preview_layers,
                subscription: SubscriptionConfig {
                    accept_recommended: preview_sub.accept_recommended,
                    opt_in: preview_sub.opt_in.clone(),
                    overrides: preview_sub.overrides.clone(),
                    reject: preview_sub.reject.clone(),
                },
            };

            match composition::compose(&local_resolved, &[preview_input]) {
                Ok(result) => {
                    if result.conflicts.is_empty() {
                        printer.newline();
                        printer.success("No conflicts with current config");
                    } else {
                        printer.newline();
                        printer.subheader("Conflicts with Current Config");
                        for conflict in &result.conflicts {
                            let label = conflict.resolution_type.label();
                            printer.warning(&format!(
                                "  {} {} <- {} ({})",
                                label,
                                conflict.resource_id,
                                conflict.winning_source,
                                conflict.details
                            ));
                        }
                    }
                }
                Err(e) => {
                    printer.warning(&format!("Failed to preview conflicts: {}", e));
                }
            }
        }
    }

    // Confirm subscription
    if !args.yes {
        printer.newline();
        if !printer.prompt_confirm("Subscribe to this source?")? {
            printer.info("Cancelled");
            return Ok(());
        }
    }

    // Build the source spec with user choices
    let mut source_spec =
        SourceManager::build_source_spec(&source_name, url, selected_profile.as_deref());
    if let Some(b) = branch {
        source_spec.origin.branch = b.to_string();
    }
    source_spec.subscription.accept_recommended = accept_recommended;
    source_spec.subscription.priority = resolved_priority;
    if !opt_in.is_empty() {
        source_spec.subscription.opt_in = opt_in.to_vec();
    }
    if let Some(interval) = sync_interval {
        source_spec.sync.interval = interval.to_string();
    }
    if auto_apply {
        source_spec.sync.auto_apply = true;
    }
    if let Some(pin) = pin_version {
        source_spec.sync.pin_version = Some(pin.to_string());
    }

    // Update cfgd.yaml
    add_source_to_config(&config_path, &source_spec)?;

    // Update state store
    let state = open_state_store(cli.state_dir.as_deref())?;
    state.upsert_config_source(
        &source_name,
        url,
        &spec.origin.branch,
        cached.last_commit.as_deref(),
        manifest.metadata.version.as_deref(),
        None,
    )?;

    printer.success(&format!("Subscribed to source '{}'", source_name));
    if let Some(ref profile) = selected_profile {
        printer.key_value("Profile", profile);
    }
    printer.info(MSG_RUN_APPLY);

    Ok(())
}

fn cmd_source_list(cli: &Cli, printer: &Printer) -> anyhow::Result<()> {
    let config_path = cli.config.clone();
    if !config_path.exists() {
        if printer.is_structured() {
            printer.write_structured(&Vec::<SourceListEntry>::new());
            return Ok(());
        }
        printer.header("Config Sources");
        printer.info("No config file found");
        return Ok(());
    }

    let cfg = config::load_config(&config_path)?;

    if cfg.spec.sources.is_empty() {
        if printer.is_structured() {
            printer.write_structured(&Vec::<SourceListEntry>::new());
            return Ok(());
        }
        printer.header("Config Sources");
        printer.info("No sources configured");
        return Ok(());
    }

    let state = open_state_store(cli.state_dir.as_deref())?;

    let entries: Vec<SourceListEntry> = cfg
        .spec
        .sources
        .iter()
        .map(|source| {
            let state_info = state.config_source_by_name(&source.name).ok().flatten();
            SourceListEntry {
                name: source.name.clone(),
                url: source.origin.url.clone(),
                priority: source.subscription.priority,
                version: state_info.as_ref().and_then(|s| s.source_version.clone()),
                status: state_info
                    .as_ref()
                    .map(|s| s.status.clone())
                    .unwrap_or_else(|| "unknown".into()),
                last_fetched: state_info.as_ref().and_then(|s| s.last_fetched.clone()),
            }
        })
        .collect();

    if printer.write_structured(&entries) {
        return Ok(());
    }

    printer.header("Config Sources");

    let rows: Vec<Vec<String>> = entries
        .iter()
        .map(|e| {
            vec![
                e.name.clone(),
                e.url.clone(),
                e.priority.to_string(),
                e.version.clone().unwrap_or_else(|| "-".into()),
                e.status.clone(),
                e.last_fetched.clone().unwrap_or_else(|| "never".into()),
            ]
        })
        .collect();
    printer.table(
        &[
            "Name",
            "URL",
            "Priority",
            "Version",
            "Status",
            "Last Fetched",
        ],
        &rows,
    );

    Ok(())
}

fn cmd_source_show(cli: &Cli, printer: &Printer, name: &str) -> anyhow::Result<()> {
    let config_path = cli.config.clone();
    let cfg = config::load_config(&config_path)?;

    let source_spec = cfg
        .spec
        .sources
        .iter()
        .find(|s| s.name == name)
        .ok_or_else(|| anyhow::anyhow!("Source '{}' not found", name))?;

    if printer.is_structured() {
        let state = open_state_store(cli.state_dir.as_deref())?;
        let state_info = state.config_source_by_name(name)?;
        let resources = state.managed_resources_by_source(name)?;
        let output = SourceShowOutput {
            name: name.to_string(),
            url: source_spec.origin.url.clone(),
            branch: source_spec.origin.branch.clone(),
            priority: source_spec.subscription.priority,
            accept_recommended: source_spec.subscription.accept_recommended,
            profile: source_spec.subscription.profile.clone(),
            sync_interval: source_spec.sync.interval.clone(),
            auto_apply: source_spec.sync.auto_apply,
            version_pin: source_spec.sync.pin_version.clone(),
            state: state_info.map(|s| SourceStateInfo {
                status: s.status,
                last_fetched: s.last_fetched,
                last_commit: s.last_commit,
                version: s.source_version,
            }),
            managed_resources: resources
                .iter()
                .map(|r| SourceResourceEntry {
                    resource_type: r.resource_type.clone(),
                    resource_id: r.resource_id.clone(),
                })
                .collect(),
        };
        printer.write_structured(&output);
        return Ok(());
    }

    printer.header(&format!("Source: {}", name));
    printer.key_value("URL", &source_spec.origin.url);
    printer.key_value("Branch", &source_spec.origin.branch);
    printer.key_value("Priority", &source_spec.subscription.priority.to_string());
    printer.key_value(
        "Accept Recommended",
        &source_spec.subscription.accept_recommended.to_string(),
    );
    if let Some(ref profile) = source_spec.subscription.profile {
        printer.key_value("Profile", profile);
    }
    printer.key_value("Sync Interval", &source_spec.sync.interval);
    printer.key_value("Auto Apply", &source_spec.sync.auto_apply.to_string());
    if let Some(ref pin) = source_spec.sync.pin_version {
        printer.key_value("Version Pin", pin);
    }

    // Show state info
    let state = open_state_store(cli.state_dir.as_deref())?;
    if let Some(state_info) = state.config_source_by_name(name)? {
        printer.newline();
        printer.subheader("State");
        printer.key_value("Status", &state_info.status);
        if let Some(ref fetched) = state_info.last_fetched {
            printer.key_value("Last Fetched", fetched);
        }
        if let Some(ref commit) = state_info.last_commit {
            printer.key_value("Last Commit", &commit[..commit.len().min(12)]);
        }
        if let Some(ref version) = state_info.source_version {
            printer.key_value("Version", version);
        }
    }

    // Show managed resources from this source
    let resources = state.managed_resources_by_source(name)?;
    if !resources.is_empty() {
        printer.newline();
        printer.subheader("Managed Resources");
        let rows: Vec<Vec<String>> = resources
            .iter()
            .map(|r| vec![r.resource_type.clone(), r.resource_id.clone()])
            .collect();
        printer.table(&["Type", "Resource"], &rows);
    }

    // Load and show manifest from cache
    let cache_dir = source_cache_dir(cli)?;
    let mut mgr = SourceManager::new(&cache_dir);
    mgr.set_allow_unsigned(cfg.spec.security.as_ref().is_some_and(|s| s.allow_unsigned));
    // Populate the manager from the cached source on disk
    if let Err(e) = mgr.load_source(source_spec, printer) {
        printer.warning(&format!("Failed to load source manifest: {}", e));
    }
    if let Some(cached) = mgr.get(name) {
        printer.newline();
        printer.subheader("Manifest");
        printer.key_value("Name", &cached.manifest.metadata.name);
        if let Some(ref desc) = cached.manifest.metadata.description {
            printer.key_value("Description", desc);
        }

        let policy = &cached.manifest.spec.policy;
        let locked_count = count_policy_items(&policy.locked);
        let required_count = count_policy_items(&policy.required);
        let recommended_count = count_policy_items(&policy.recommended);

        if locked_count + required_count + recommended_count > 0 {
            printer.newline();
            printer.subheader("Policy Summary");

            if locked_count > 0 {
                printer.key_value("Locked", &locked_count.to_string());
                display_policy_items(printer, &policy.locked, "  ");
            }
            if required_count > 0 {
                printer.key_value("Required", &required_count.to_string());
                display_policy_items(printer, &policy.required, "  ");
            }
            if recommended_count > 0 {
                printer.key_value("Recommended", &recommended_count.to_string());
                display_policy_items(printer, &policy.recommended, "  ");
            }
        }
    }

    Ok(())
}

fn cmd_source_remove(
    cli: &Cli,
    printer: &Printer,
    name: &str,
    keep_all: bool,
    remove_all: bool,
) -> anyhow::Result<()> {
    if keep_all && remove_all {
        anyhow::bail!("cannot use --keep-all and --remove-all together");
    }

    printer.header(&format!("Remove Source: {}", name));

    let config_path = cli.config.clone();
    let cfg = config::load_config(&config_path)?;

    if !cfg.spec.sources.iter().any(|s| s.name == name) {
        anyhow::bail!("Source '{}' not found in config", name);
    }

    let state = open_state_store(cli.state_dir.as_deref())?;
    let resources = state.managed_resources_by_source(name)?;

    if !resources.is_empty() && !keep_all && !remove_all {
        // Interactive: ask for each resource or batch
        printer.info(&format!(
            "This source manages {} resource(s):",
            resources.len()
        ));
        let rows: Vec<Vec<String>> = resources
            .iter()
            .map(|r| vec![r.resource_type.clone(), r.resource_id.clone()])
            .collect();
        printer.table(&["Type", "Resource"], &rows);
        printer.newline();

        let options = vec![
            "Keep all (resources become locally managed)".to_string(),
            "Remove all".to_string(),
        ];
        let choice = printer.prompt_select("What to do with these resources?", &options)?;

        if choice.starts_with("Keep") {
            // Re-assign resources to local
            for r in &resources {
                state.upsert_managed_resource(
                    &r.resource_type,
                    &r.resource_id,
                    "local",
                    r.last_hash.as_deref(),
                    r.last_applied,
                )?;
            }
            printer.info("Resources transferred to local management");
        }
        // If "Remove all", they'll be cleaned up when state is updated
    } else if keep_all {
        for r in &resources {
            state.upsert_managed_resource(
                &r.resource_type,
                &r.resource_id,
                "local",
                r.last_hash.as_deref(),
                r.last_applied,
            )?;
        }
    }

    // Remove from config
    remove_source_from_config(&config_path, name)?;

    // Remove from state
    state.remove_config_source(name)?;
    state.remove_source_config_hash(name)?;

    // Remove cached data
    let cache_dir = source_cache_dir(cli)?;
    let mut mgr = SourceManager::new(&cache_dir);
    let _ = mgr.remove_source(name);

    printer.success(&format!("Source '{}' removed", name));
    Ok(())
}

fn cmd_source_update(cli: &Cli, printer: &Printer, name: Option<&str>) -> anyhow::Result<()> {
    printer.header("Update Sources");

    let config_path = cli.config.clone();
    let cfg = config::load_config(&config_path)?;

    if cfg.spec.sources.is_empty() {
        printer.info("No sources configured");
        return Ok(());
    }

    let cache_dir = source_cache_dir(cli)?;
    let mut mgr = SourceManager::new(&cache_dir);
    mgr.set_allow_unsigned(cfg.spec.security.as_ref().is_some_and(|s| s.allow_unsigned));
    let state = open_state_store(cli.state_dir.as_deref())?;

    let sources_to_update: Vec<&config::SourceSpec> = if let Some(name) = name {
        cfg.spec.sources.iter().filter(|s| s.name == name).collect()
    } else {
        cfg.spec.sources.iter().collect()
    };

    if sources_to_update.is_empty()
        && let Some(name) = name
    {
        anyhow::bail!("Source '{}' not found", name);
    }

    for source in &sources_to_update {
        // Capture old manifest before fetching (for permission change detection)
        let source_dir = cache_dir.join(&source.name);
        let old_manifest = if source_dir.exists() {
            mgr.parse_manifest(&source.name, &source_dir).ok()
        } else {
            None
        };

        match mgr.load_source(source, printer) {
            Ok(()) => {
                if let Some(cached) = mgr.get(&source.name) {
                    // Detect permission-expanding changes between old and new manifests
                    if let Some(ref old) = old_manifest {
                        let old_input = build_permission_input(&source.name, &old.spec.policy);
                        let new_input =
                            build_permission_input(&source.name, &cached.manifest.spec.policy);
                        let perm_changes =
                            composition::detect_permission_changes(&[old_input], &[new_input]);
                        if !perm_changes.is_empty() {
                            printer.newline();
                            printer.warning(&format!(
                                "Source '{}' update changes permissions:",
                                source.name
                            ));
                            for change in &perm_changes {
                                printer.warning(&format!("  - {}", change.description));
                            }
                            match printer.prompt_confirm("Accept permission changes?") {
                                Ok(true) => {}
                                Ok(false) => {
                                    printer.info(&format!(
                                        "Skipped source '{}' (permission changes rejected)",
                                        source.name
                                    ));
                                    continue;
                                }
                                Err(_) => {
                                    printer.info(&format!(
                                        "Skipped source '{}' (prompt cancelled)",
                                        source.name
                                    ));
                                    continue;
                                }
                            }
                        }
                    }

                    state.upsert_config_source(
                        &source.name,
                        &source.origin.url,
                        &source.origin.branch,
                        cached.last_commit.as_deref(),
                        cached.manifest.metadata.version.as_deref(),
                        source.sync.pin_version.as_deref(),
                    )?;
                    printer.success(&format!("Updated source '{}'", source.name));
                }
            }
            Err(e) => {
                printer.error(&format!("Failed to update source '{}': {}", source.name, e));
                state.update_config_source_status(&source.name, "error")?;
            }
        }
    }

    Ok(())
}

/// Build a minimal [`CompositionInput`] from a source policy for permission change detection.
/// Only the `source_name`, `policy`, and `constraints` fields are used by
/// [`composition::detect_permission_changes`]; the rest are defaulted.
fn build_permission_input(name: &str, policy: &config::ConfigSourcePolicy) -> CompositionInput {
    CompositionInput {
        source_name: name.to_string(),
        priority: 0,
        policy: policy.clone(),
        constraints: policy.constraints.clone(),
        layers: Vec::new(),
        subscription: SubscriptionConfig::default(),
    }
}

fn cmd_source_override(
    cli: &Cli,
    printer: &Printer,
    source_name: &str,
    action: &str,
    path: &str,
    value: Option<&str>,
) -> anyhow::Result<()> {
    let config_path = cli.config.clone();
    let cfg = config::load_config(&config_path)?;

    // Verify source exists in config
    if !cfg.spec.sources.iter().any(|s| s.name == source_name) {
        anyhow::bail!("Source '{}' not found", source_name);
    }

    match action {
        "reject" => {
            printer.info(&format!(
                "Rejecting '{}' from source '{}'",
                path, source_name
            ));
            update_source_rejection(&config_path, source_name, path)?;
            printer.success(&format!("Rejected '{}' from '{}'", path, source_name));
        }
        "set" => {
            let val = value.ok_or_else(|| anyhow::anyhow!("'set' action requires a value"))?;
            printer.info(&format!(
                "Overriding '{}' = '{}' for source '{}'",
                path, val, source_name
            ));
            update_source_override(&config_path, source_name, path, val)?;
            printer.success(&format!(
                "Override set: {} = {} for '{}'",
                path, val, source_name
            ));
        }
        other => {
            anyhow::bail!("Unknown action '{}'. Use 'set' or 'reject'.", other);
        }
    }

    Ok(())
}

fn cmd_source_replace(
    cli: &Cli,
    printer: &Printer,
    old_name: &str,
    new_url: &str,
) -> anyhow::Result<()> {
    printer.header(&format!("Replace Source: {}", old_name));

    // Capture old source's profile and priority before removing
    let config_path = cli.config.clone();
    let old_cfg = config::load_config(&config_path)?;
    let old_source = old_cfg.spec.sources.iter().find(|s| s.name == old_name);
    let old_profile = old_source.and_then(|s| s.subscription.profile.clone());
    let old_priority = old_source.map(|s| s.subscription.priority).unwrap_or(500);

    // Remove old source (keeping resources)
    cmd_source_remove(cli, printer, old_name, true, false)?;

    // Add new source with same name, carrying over profile and priority
    cmd_source_add(
        cli,
        printer,
        &SourceAddArgs {
            url: new_url.to_string(),
            name: Some(old_name.to_string()),
            branch: None,
            profile: old_profile,
            accept_recommended: false,
            priority: Some(old_priority),
            opt_in: vec![],
            sync_interval: None,
            auto_apply: false,
            pin_version: None,
            yes: true,
        },
    )?;

    printer.success(&format!("Source '{}' replaced with {}", old_name, new_url));
    Ok(())
}

fn cmd_source_priority(
    cli: &Cli,
    printer: &Printer,
    name: &str,
    value: Option<u32>,
) -> anyhow::Result<()> {
    let config_path = cli.config.clone();
    let cfg = config::load_config(&config_path)?;

    let source = cfg
        .spec
        .sources
        .iter()
        .find(|s| s.name == name)
        .ok_or_else(|| anyhow::anyhow!("source '{}' not found", name))?;

    match value {
        Some(new_priority) => {
            // Update priority in cfgd.yaml
            with_source_config(&config_path, name, |source_entry| {
                let subscription = source_entry.get_mut("subscription").ok_or_else(|| {
                    anyhow::anyhow!("source '{}' has no subscription block", name)
                })?;

                if let Some(mapping) = subscription.as_mapping_mut() {
                    mapping.insert(
                        serde_yaml::Value::String("priority".into()),
                        serde_yaml::Value::Number(serde_yaml::Number::from(new_priority)),
                    );
                }
                Ok(())
            })?;

            printer.success(&format!(
                "Source '{}' priority updated: {} -> {}",
                name, source.subscription.priority, new_priority
            ));
        }
        None => {
            printer.key_value("Source", name);
            printer.key_value("Priority", &source.subscription.priority.to_string());
            printer.info("Local config priority is 1000");
        }
    }

    Ok(())
}

// --- Source config helpers ---

fn infer_source_name(url: &str) -> String {
    // Extract name from URL: git@github.com:acme/dev-config.git -> acme-dev-config
    let cleaned = url
        .trim_end_matches(".git")
        .rsplit('/')
        .next()
        .or_else(|| url.rsplit(':').next())
        .unwrap_or(url);

    // If the path component includes org/repo, use org-repo
    if let Some(rest) = url.strip_prefix("git@")
        && let Some(path) = rest.split(':').nth(1)
    {
        return path.trim_end_matches(".git").replace('/', "-");
    }

    cleaned.to_string()
}

fn count_policy_items(items: &config::PolicyItems) -> usize {
    let mut count = 0;
    if let Some(ref pkgs) = items.packages {
        if let Some(ref brew) = pkgs.brew {
            count += brew.formulae.len() + brew.casks.len() + brew.taps.len();
        }
        if let Some(ref apt) = pkgs.apt {
            count += apt.packages.len();
        }
        if let Some(ref cargo) = pkgs.cargo {
            count += cargo.packages.len();
        }
        count += pkgs.pipx.len() + pkgs.dnf.len();
        if let Some(ref npm) = pkgs.npm {
            count += npm.global.len();
        }
    }
    count += items.files.len();
    count += items.env.len();
    count += items.system.len();
    count
}

fn display_policy_items(printer: &Printer, items: &config::PolicyItems, indent: &str) {
    if let Some(ref pkgs) = items.packages {
        if let Some(ref brew) = pkgs.brew {
            for f in &brew.formulae {
                printer.info(&format!("{indent}brew formula: {f}"));
            }
            for c in &brew.casks {
                printer.info(&format!("{indent}brew cask: {c}"));
            }
        }
        if let Some(ref apt) = pkgs.apt {
            for p in &apt.packages {
                printer.info(&format!("{indent}apt: {p}"));
            }
        }
        if let Some(ref cargo) = pkgs.cargo {
            for p in &cargo.packages {
                printer.info(&format!("{indent}cargo: {p}"));
            }
        }
        for p in &pkgs.pipx {
            printer.info(&format!("{indent}pipx: {p}"));
        }
        for p in &pkgs.dnf {
            printer.info(&format!("{indent}dnf: {p}"));
        }
        if let Some(ref npm) = pkgs.npm {
            for p in &npm.global {
                printer.info(&format!("{indent}npm: {p}"));
            }
        }
    }
    for f in &items.files {
        printer.info(&format!("{indent}file: {}", f.target.display()));
    }
    for ev in &items.env {
        printer.info(&format!("{indent}env: {}", ev.name));
    }
    for k in items.system.keys() {
        printer.info(&format!("{indent}system: {k}"));
    }
}

fn display_pending_decisions(printer: &Printer, decisions: &[cfgd_core::state::PendingDecision]) {
    let mut by_source: std::collections::BTreeMap<&str, Vec<&cfgd_core::state::PendingDecision>> =
        std::collections::BTreeMap::new();
    for d in decisions {
        by_source.entry(&d.source).or_default().push(d);
    }
    for (source_name, items) in &by_source {
        printer.info(&format!(
            "{}: {} pending item{}",
            source_name,
            items.len(),
            if items.len() == 1 { "" } else { "s" }
        ));
        for item in items {
            printer.info(&format!(
                "  {} {} — {} ({})",
                item.tier, item.resource, item.summary, item.action
            ));
        }
    }
}

fn add_source_to_config(config_path: &Path, source: &config::SourceSpec) -> anyhow::Result<()> {
    if !config_path.exists() {
        anyhow::bail!("Config file not found: {}", config_path.display());
    }

    let contents = std::fs::read_to_string(config_path)?;
    let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;

    // Get or create spec.sources array
    let spec = raw
        .get_mut("spec")
        .ok_or_else(|| anyhow::anyhow!("config missing 'spec'"))?;

    let sources = spec
        .as_mapping_mut()
        .ok_or_else(|| anyhow::anyhow!("spec is not a mapping"))?
        .entry(serde_yaml::Value::String("sources".into()))
        .or_insert(serde_yaml::Value::Sequence(vec![]));

    let seq = sources
        .as_sequence_mut()
        .ok_or_else(|| anyhow::anyhow!("sources is not a sequence"))?;

    let source_value = serde_yaml::to_value(source)?;
    seq.push(source_value);

    let output = serde_yaml::to_string(&raw)?;
    cfgd_core::atomic_write_str(config_path, &output)?;

    Ok(())
}

fn remove_source_from_config(config_path: &Path, name: &str) -> anyhow::Result<()> {
    if !config_path.exists() {
        return Ok(());
    }

    let contents = std::fs::read_to_string(config_path)?;
    let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;

    if let Some(spec) = raw.get_mut("spec")
        && let Some(sources) = spec.get_mut("sources")
        && let Some(seq) = sources.as_sequence_mut()
    {
        seq.retain(|item| {
            item.get("name")
                .and_then(|n| n.as_str())
                .map(|n| n != name)
                .unwrap_or(true)
        });
    }

    let output = serde_yaml::to_string(&raw)?;
    cfgd_core::atomic_write_str(config_path, &output)?;

    Ok(())
}

fn update_source_rejection(
    config_path: &Path,
    source_name: &str,
    path: &str,
) -> anyhow::Result<()> {
    with_source_config(config_path, source_name, |source| {
        let subscription = source
            .as_mapping_mut()
            .and_then(|m| {
                m.entry(serde_yaml::Value::String("subscription".into()))
                    .or_insert(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
                m.get_mut(serde_yaml::Value::String("subscription".into()))
            })
            .ok_or_else(|| anyhow::anyhow!("cannot access subscription"))?;

        let sub_map = subscription
            .as_mapping_mut()
            .ok_or_else(|| anyhow::anyhow!("subscription is not a mapping"))?;
        let reject = sub_map
            .entry(serde_yaml::Value::String("reject".into()))
            .or_insert(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
        // Replace null with empty mapping (serde serializes default Value::Null)
        if reject.is_null() {
            *reject = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        }

        set_nested_yaml_value(reject, path, &serde_yaml::Value::Null)?;
        Ok(())
    })
}

fn update_source_override(
    config_path: &Path,
    source_name: &str,
    path: &str,
    value: &str,
) -> anyhow::Result<()> {
    with_source_config(config_path, source_name, |source| {
        let subscription = source
            .as_mapping_mut()
            .and_then(|m| {
                m.entry(serde_yaml::Value::String("subscription".into()))
                    .or_insert(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
                m.get_mut(serde_yaml::Value::String("subscription".into()))
            })
            .ok_or_else(|| anyhow::anyhow!("cannot access subscription"))?;

        let sub_map = subscription
            .as_mapping_mut()
            .ok_or_else(|| anyhow::anyhow!("subscription is not a mapping"))?;
        let overrides = sub_map
            .entry(serde_yaml::Value::String("overrides".into()))
            .or_insert(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
        // Replace null with empty mapping (serde serializes default Value::Null)
        if overrides.is_null() {
            *overrides = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        }

        set_nested_yaml_value(
            overrides,
            path,
            &serde_yaml::Value::String(value.to_string()),
        )?;
        Ok(())
    })
}

fn find_source_in_config<'a>(
    raw: &'a mut serde_yaml::Value,
    source_name: &str,
) -> Option<&'a mut serde_yaml::Value> {
    raw.get_mut("spec")?
        .get_mut("sources")?
        .as_sequence_mut()?
        .iter_mut()
        .find(|item| {
            item.get("name")
                .and_then(|n| n.as_str())
                .map(|n| n == source_name)
                .unwrap_or(false)
        })
}

/// Load config YAML, find a named source, apply a mutation, and write back.
/// The closure receives the mutable source entry; the helper handles I/O.
fn with_source_config<F>(config_path: &Path, source_name: &str, f: F) -> anyhow::Result<()>
where
    F: FnOnce(&mut serde_yaml::Value) -> anyhow::Result<()>,
{
    let contents = std::fs::read_to_string(config_path)?;
    let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents)?;
    let source = find_source_in_config(&mut raw, source_name)
        .ok_or_else(|| anyhow::anyhow!("source '{}' not found in config file", source_name))?;
    f(source)?;
    let output = serde_yaml::to_string(&raw)?;
    cfgd_core::atomic_write_str(config_path, &output)?;
    Ok(())
}

// --- Plan filtering for --skip and --only ---

/// Compute the dot-notation resource path for an action.
/// Returns the phase-level prefix and the action-specific path components.
///
/// Examples:
///   PackageAction::Install { manager: "brew", packages: ["ripgrep"] } → "packages.brew"
///   SystemAction::SetValue { configurator: "sysctl", key: "net.ipv4.ip_forward" } → "system.sysctl.net.ipv4.ip_forward"
///   FileAction::Create { target: "/etc/foo" } → "files./etc/foo"
///   SecretAction::Resolve { provider: "1password" } → "secrets.1password"
///   ScriptAction::Run { path: "scripts/setup.sh" } → "scripts.scripts/setup.sh"
fn action_path(phase: &PhaseName, action: &reconciler::Action) -> String {
    let prefix = phase.as_str();
    match action {
        reconciler::Action::Package(pa) => {
            let manager = match pa {
                PackageAction::Bootstrap { manager, .. } => manager,
                PackageAction::Install { manager, .. } => manager,
                PackageAction::Uninstall { manager, .. } => manager,
                PackageAction::Skip { manager, .. } => manager,
            };
            format!("{}.{}", prefix, manager)
        }
        reconciler::Action::System(sa) => match sa {
            reconciler::SystemAction::SetValue {
                configurator, key, ..
            } => format!("{}.{}.{}", prefix, configurator, key),
            reconciler::SystemAction::Skip { configurator, .. } => {
                format!("{}.{}", prefix, configurator)
            }
        },
        reconciler::Action::File(fa) => {
            let target = match fa {
                FileAction::Create { target, .. } => target,
                FileAction::Update { target, .. } => target,
                FileAction::Delete { target, .. } => target,
                FileAction::SetPermissions { target, .. } => target,
                FileAction::Skip { target, .. } => target,
            };
            format!("{}:{}", prefix, target.display())
        }
        reconciler::Action::Secret(sa) => match sa {
            SecretAction::Decrypt { target, .. } => {
                format!("{}:{}", prefix, target.display())
            }
            SecretAction::Resolve {
                provider,
                reference,
                ..
            } => format!("{}.{}.{}", prefix, provider, reference),
            SecretAction::ResolveEnv {
                provider,
                reference,
                envs,
                ..
            } => format!("{}.{}.{}:[{}]", prefix, provider, reference, envs.join(",")),
            SecretAction::Skip { source, .. } => {
                format!("{}.{}", prefix, source)
            }
        },
        reconciler::Action::Script(sa) => match sa {
            reconciler::ScriptAction::Run { entry, .. } => {
                format!("{}:{}", prefix, entry.run_str())
            }
        },
        reconciler::Action::Module(ma) => {
            format!("{}.{}", prefix, ma.module_name)
        }
        reconciler::Action::Env(ea) => match ea {
            reconciler::EnvAction::WriteEnvFile { path, .. } => {
                format!("{}:{}", prefix, path.display())
            }
            reconciler::EnvAction::InjectSourceLine { rc_path, .. } => {
                format!("{}:{}", prefix, rc_path.display())
            }
        },
    }
}

/// Check if a pattern matches an action path.
/// A pattern is a prefix match: "packages.brew" matches "packages.brew.ripgrep".
/// For file/script paths using `:`, "files:" matches all files.
fn pattern_matches(pattern: &str, action_path: &str) -> bool {
    if action_path == pattern {
        return true;
    }
    // "packages" matches "packages.brew.ripgrep"
    // "packages.brew" matches "packages.brew.ripgrep"
    if action_path.starts_with(pattern) && action_path[pattern.len()..].starts_with(['.', ':']) {
        return true;
    }
    // "packages" should also match "packages:..." (colon-separated paths)
    false
}

/// Check if a file target is an unmanaged file — exists on disk but not tracked by cfgd.
/// A cfgd-managed symlink (pointing into config_dir) is NOT unmanaged.
fn is_unmanaged_file(target: &Path, config_dir: &Path, state: &StateStore) -> bool {
    // Target must exist on disk
    if !target.exists() && target.symlink_metadata().is_err() {
        return false;
    }

    // If it's a symlink pointing into the config dir, it's cfgd-managed
    if let Ok(link_target) = target.read_link() {
        if link_target.starts_with(config_dir) {
            return false;
        }
        // Also check ~/.cache/cfgd/modules/ for module symlinks
        {
            let module_cache = cfgd_core::expand_tilde(Path::new("~/.cache/cfgd/modules"));
            if link_target.starts_with(&module_cache) {
                return false;
            }
        }
    }

    // Check state store — if already tracked, it's managed
    let target_str = target.display().to_string();
    if let Ok(managed) = state.is_resource_managed("file", &target_str) {
        return !managed;
    }

    true
}

/// Handle unmanaged file targets in the plan: for each file Create/Update action targeting
/// an existing file not managed by cfgd, prompt the user to adopt, backup, or skip.
fn handle_unmanaged_file_targets(
    plan: &mut reconciler::Plan,
    config_dir: &Path,
    state: &StateStore,
    printer: &Printer,
    auto_yes: bool,
) -> anyhow::Result<()> {
    let options = vec![
        "Adopt (overwrite with cfgd-managed version)".to_string(),
        "Backup (save as .cfgd-backup, then overwrite)".to_string(),
        "Skip (leave file untouched)".to_string(),
    ];

    for phase in &mut plan.phases {
        let mut i = 0;
        while i < phase.actions.len() {
            // Profile file actions
            if let reconciler::Action::File(
                FileAction::Create { target, .. } | FileAction::Update { target, .. },
            ) = &phase.actions[i]
            {
                let target = target.clone();
                if is_unmanaged_file(&target, config_dir, state) && !auto_yes {
                    let choice = prompt_backup_choice(&target, None, printer, &options)?;
                    apply_backup_choice(choice, &target, &mut phase.actions[i], printer)?;
                }
            }

            // Module file actions
            if let reconciler::Action::Module(ref ma) = phase.actions[i]
                && let reconciler::ModuleActionKind::DeployFiles { files } = &ma.kind
            {
                let needs_prompt = !auto_yes
                    && files.iter().any(|f| {
                        let t = cfgd_core::expand_tilde(&f.target);
                        is_unmanaged_file(&t, config_dir, state)
                    });
                if needs_prompt {
                    let module_name = ma.module_name.clone();
                    if let reconciler::Action::Module(ref mut ma) = phase.actions[i]
                        && let reconciler::ModuleActionKind::DeployFiles { ref mut files } = ma.kind
                    {
                        let mut j = 0;
                        while j < files.len() {
                            let file_target = cfgd_core::expand_tilde(&files[j].target);
                            if is_unmanaged_file(&file_target, config_dir, state) {
                                let choice = prompt_backup_choice(
                                    &file_target,
                                    Some(&module_name),
                                    printer,
                                    &options,
                                )?;
                                if choice.starts_with("Backup") {
                                    backup_file(&file_target, printer)?;
                                } else if choice.starts_with("Skip") {
                                    files.remove(j);
                                    continue;
                                }
                            }
                            j += 1;
                        }
                    }
                }
            }

            i += 1;
        }
    }

    Ok(())
}

/// Prompt the user to choose how to handle an unmanaged file target.
fn prompt_backup_choice<'a>(
    target: &Path,
    module_name: Option<&str>,
    printer: &Printer,
    options: &'a [String],
) -> anyhow::Result<&'a String> {
    let msg = if let Some(m) = module_name {
        format!(
            "Module '{}': target exists as unmanaged file: {}",
            m,
            target.display()
        )
    } else {
        format!("Target exists as unmanaged file: {}", target.display())
    };
    printer.warning(&msg);
    Ok(printer
        .prompt_select("How should cfgd handle this file?", options)
        .unwrap_or(&options[0]))
}

/// Rename a file to <path>.cfgd-backup.
fn backup_file(target: &Path, printer: &Printer) -> anyhow::Result<()> {
    let backup_path = PathBuf::from(format!("{}.cfgd-backup", target.display()));
    std::fs::rename(target, &backup_path).map_err(|e| {
        anyhow::anyhow!(
            "Failed to backup {} to {}: {}",
            target.display(),
            backup_path.display(),
            e
        )
    })?;
    printer.success(&format!("Backed up to {}", backup_path.display()));
    Ok(())
}

/// Apply the user's backup choice to a file action.
fn apply_backup_choice(
    choice: &str,
    target: &Path,
    action: &mut reconciler::Action,
    printer: &Printer,
) -> anyhow::Result<()> {
    if choice.starts_with("Backup") {
        backup_file(target, printer)?;
    } else if choice.starts_with("Skip") {
        let origin = match action {
            reconciler::Action::File(FileAction::Create { origin, .. })
            | reconciler::Action::File(FileAction::Update { origin, .. }) => origin.clone(),
            _ => "local".to_string(),
        };
        *action = reconciler::Action::File(FileAction::Skip {
            target: target.to_path_buf(),
            reason: "skipped by user (unmanaged file exists)".to_string(),
            origin,
        });
    }
    Ok(())
}

/// Apply --skip and --only filters to a plan, modifying it in place.
/// For package actions, individual packages can be filtered from install/uninstall lists.
fn filter_plan(plan: &mut reconciler::Plan, skip: &[String], only: &[String]) {
    if skip.is_empty() && only.is_empty() {
        return;
    }

    for phase in &mut plan.phases {
        let mut filtered_actions = Vec::new();

        for action in std::mem::take(&mut phase.actions) {
            // Package install/uninstall actions need per-package granularity
            if let reconciler::Action::Package(ref pa) = action {
                match pa {
                    PackageAction::Install {
                        manager,
                        packages,
                        origin,
                    } => {
                        let kept =
                            filter_package_list(phase.name.as_str(), manager, packages, skip, only);
                        if !kept.is_empty() {
                            filtered_actions.push(reconciler::Action::Package(
                                PackageAction::Install {
                                    manager: manager.clone(),
                                    packages: kept,
                                    origin: origin.clone(),
                                },
                            ));
                        }
                        continue;
                    }
                    PackageAction::Uninstall {
                        manager,
                        packages,
                        origin,
                    } => {
                        let kept =
                            filter_package_list(phase.name.as_str(), manager, packages, skip, only);
                        if !kept.is_empty() {
                            filtered_actions.push(reconciler::Action::Package(
                                PackageAction::Uninstall {
                                    manager: manager.clone(),
                                    packages: kept,
                                    origin: origin.clone(),
                                },
                            ));
                        }
                        continue;
                    }
                    _ => {}
                }
            }

            // Non-package actions: action-level filtering
            let path = action_path(&phase.name, &action);
            let should_skip = skip.iter().any(|s| pattern_matches(s, &path));
            let passes_only = only.is_empty()
                || only
                    .iter()
                    .any(|o| pattern_matches(o, &path) || pattern_matches(&path, o));

            if !should_skip && passes_only {
                filtered_actions.push(action);
            }
        }

        phase.actions = filtered_actions;
    }
}

/// Filter individual packages from an install/uninstall list based on skip/only patterns.
fn filter_package_list(
    phase: &str,
    manager: &str,
    packages: &[String],
    skip: &[String],
    only: &[String],
) -> Vec<String> {
    packages
        .iter()
        .filter(|pkg| {
            let pkg_path = format!("{}.{}.{}", phase, manager, pkg);

            // Check skip: pattern can target the specific package, manager, or phase
            let pkg_skip = skip.iter().any(|s| pattern_matches(s, &pkg_path));

            // Check only: the pattern must cover this package.
            // "packages" covers "packages.brew.ripgrep" (broad → specific)
            // "packages.brew.ripgrep" covers "packages.brew.ripgrep" (exact)
            // But "packages.brew.ripgrep" does NOT cover "packages.brew.fd"
            let pkg_only = only.is_empty()
                || only
                    .iter()
                    .any(|o| pattern_matches(o, &pkg_path) || pattern_matches(&pkg_path, o));

            !pkg_skip && pkg_only
        })
        .cloned()
        .collect()
}

fn set_nested_yaml_value(
    root: &mut serde_yaml::Value,
    path: &str,
    value: &serde_yaml::Value,
) -> anyhow::Result<()> {
    let parts: Vec<&str> = path.split('.').collect();
    let mut current = root;

    for (i, part) in parts.iter().enumerate() {
        if i == parts.len() - 1 {
            // Last part: set the value
            if let Some(mapping) = current.as_mapping_mut() {
                mapping.insert(serde_yaml::Value::String(part.to_string()), value.clone());
            }
        } else {
            // Intermediate part: navigate or create
            let mapping = current
                .as_mapping_mut()
                .ok_or_else(|| anyhow::anyhow!("expected mapping at '{}'", part))?;
            current = mapping
                .entry(serde_yaml::Value::String(part.to_string()))
                .or_insert(serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
        }
    }

    Ok(())
}

// --- Plan integration with sources (Phase 9) ---

/// Compose sources with local profile for plan generation.
fn compose_with_sources(
    cli: &Cli,
    cfg: &config::CfgdConfig,
    local_resolved: &ResolvedProfile,
    printer: &Printer,
) -> anyhow::Result<composition::CompositionResult> {
    if cfg.spec.sources.is_empty() {
        // No sources, return local profile as-is
        return Ok(composition::CompositionResult {
            resolved: local_resolved.clone(),
            conflicts: Vec::new(),
            source_env: std::collections::HashMap::new(),
            source_commits: std::collections::HashMap::new(),
        });
    }

    let cache_dir = source_cache_dir(cli)?;
    let mut mgr = SourceManager::new(&cache_dir);
    mgr.set_allow_unsigned(cfg.spec.security.as_ref().is_some_and(|s| s.allow_unsigned));
    mgr.load_sources(&cfg.spec.sources, printer)?;

    let mut inputs = Vec::new();
    for source_spec in &cfg.spec.sources {
        if let Some(cached) = mgr.get(&source_spec.name) {
            // Load source profile layers
            let mut layers = Vec::new();
            if let Some(ref profile_name) = source_spec.subscription.profile {
                let profiles_dir = mgr.source_profiles_dir(&source_spec.name)?;
                if profiles_dir.exists() {
                    match config::resolve_profile(profile_name, &profiles_dir) {
                        Ok(resolved) => {
                            layers = resolved.layers;
                        }
                        Err(e) => {
                            printer.warning(&format!(
                                "Failed to resolve profile '{}' from source '{}': {}",
                                profile_name, source_spec.name, e
                            ));
                        }
                    }
                }
            }

            // Validate security constraints
            for layer in &layers {
                if let Err(e) = composition::validate_constraints(
                    &source_spec.name,
                    &cached.manifest.spec.policy.constraints,
                    &layer.spec,
                ) {
                    printer.error(&format!(
                        "Security violation in source '{}': {}",
                        source_spec.name, e
                    ));
                    continue;
                }
            }

            // Check if local config overrides any locked resources from this source
            if let Err(e) = composition::check_locked_violations(
                &source_spec.name,
                &cached.manifest.spec.policy.locked,
                &local_resolved.merged,
            ) {
                printer.warning(&format!(
                    "Locked resource conflict with source '{}': {}",
                    source_spec.name, e
                ));
            }

            inputs.push(CompositionInput {
                source_name: source_spec.name.clone(),
                priority: source_spec.subscription.priority,
                policy: cached.manifest.spec.policy.clone(),
                constraints: cached.manifest.spec.policy.constraints.clone(),
                layers,
                subscription: SubscriptionConfig::from_spec(source_spec),
            });
        }
    }

    let mut result = composition::compose(local_resolved, &inputs)?;

    // Collect source commit hashes for record_source_apply linkage
    for source_spec in &cfg.spec.sources {
        if let Some(cached) = mgr.get(&source_spec.name)
            && let Some(ref commit) = cached.last_commit
        {
            result
                .source_commits
                .insert(source_spec.name.clone(), commit.clone());
        }
    }

    // Display conflicts
    if !result.conflicts.is_empty() {
        printer.newline();
        printer.subheader("Source Conflicts");
        for conflict in &result.conflicts {
            match conflict.resolution_type {
                composition::ResolutionType::Locked => {
                    printer.warning(&conflict.details);
                }
                composition::ResolutionType::Required => {
                    printer.info(&conflict.details);
                }
                composition::ResolutionType::Rejected => {
                    printer.info(&conflict.details);
                }
                composition::ResolutionType::Override => {
                    printer.info(&conflict.details);
                }
                composition::ResolutionType::Default => {}
            }
        }

        // Persist conflicts to state
        if let Ok(state) = open_state_store(cli.state_dir.as_deref()) {
            for conflict in &result.conflicts {
                let _ = state.record_source_conflict(
                    &conflict.winning_source,
                    "composition",
                    &conflict.resource_id,
                    conflict.resolution_type.label(),
                    Some(&conflict.details),
                );
            }
        }
    }

    Ok(result)
}

fn cmd_checkin(
    cli: &Cli,
    printer: &Printer,
    server_url: &str,
    api_key: Option<&str>,
    device_id: Option<&str>,
) -> anyhow::Result<()> {
    let (cfg, resolved) = load_config_and_profile(cli, printer)?;
    let registry = build_registry_with_profile(&resolved.merged.packages);

    // Try stored device credential first, fall back to explicit args
    let stored_cred = cfgd_core::server_client::load_credential().ok().flatten();
    let client = if api_key.is_none() {
        if let Some(ref cred) = stored_cred {
            if cred.server_url.trim_end_matches('/') == server_url.trim_end_matches('/') {
                cfgd_core::server_client::ServerClient::from_credential(cred)
            } else {
                let did = device_id
                    .map(|s| s.to_string())
                    .unwrap_or_else(default_device_id);
                cfgd_core::server_client::ServerClient::new(server_url, None, &did)
            }
        } else {
            let did = device_id
                .map(|s| s.to_string())
                .unwrap_or_else(default_device_id);
            cfgd_core::server_client::ServerClient::new(server_url, None, &did)
        }
    } else {
        let did = device_id
            .map(|s| s.to_string())
            .unwrap_or_else(default_device_id);
        cfgd_core::server_client::ServerClient::new(server_url, api_key, &did)
    };

    // Compute config hash
    let config_yaml = serde_yaml::to_string(&resolved.merged.system)
        .map_err(|e| anyhow::anyhow!("failed to serialize system config: {}", e))?;
    let config_hash = cfgd_core::sha256_hex(config_yaml.as_bytes());

    // Collect compliance summary if enabled
    let compliance_summary = if let Some(ref compliance_cfg) = cfg.spec.compliance {
        if compliance_cfg.enabled {
            let profile_name = cfg.active_profile().unwrap_or("unknown");
            match cfgd_core::compliance::collect_snapshot(
                profile_name,
                &resolved.merged,
                &registry,
                &compliance_cfg.scope,
                &[],
            ) {
                Ok(snapshot) => {
                    printer.info(&format!(
                        "Compliance: {} compliant, {} warning, {} violation",
                        snapshot.summary.compliant,
                        snapshot.summary.warning,
                        snapshot.summary.violation,
                    ));
                    Some(snapshot.summary)
                }
                Err(e) => {
                    tracing::warn!(error = %e, "Failed to collect compliance snapshot for checkin");
                    None
                }
            }
        } else {
            None
        }
    } else {
        None
    };

    // Check in
    let resp = client
        .checkin(&config_hash, compliance_summary, printer)
        .map_err(|e| anyhow::anyhow!("{}", e))?;
    printer.key_value("Server status", &resp.status);
    printer.key_value("Config changed", &resp.config_changed.to_string());

    // Save desired config from server for next reconcile
    if let Some(ref desired) = resp.desired_config {
        match cfgd_core::state::save_pending_server_config(desired) {
            Ok(path) => {
                printer.warning(&format!(
                    "Server pushed a new desired config — saved to {}",
                    path.display()
                ));
                printer.info(MSG_RUN_APPLY);
            }
            Err(e) => {
                tracing::warn!(error = %e, "Failed to save pending server config");
                printer.warning("Server sent desired config but failed to save it locally");
            }
        }
    }

    // Collect and report drift
    let mut all_drifts = Vec::new();
    let available = registry.available_system_configurators();

    for configurator in &available {
        let key = configurator.name();
        let desired = match resolved.merged.system.get(key) {
            Some(v) => v,
            None => continue,
        };
        if let Ok(drifts) = configurator.diff(desired) {
            all_drifts.extend(drifts);
        }
    }

    if !all_drifts.is_empty() {
        client
            .report_drift(&all_drifts, printer)
            .map_err(|e| anyhow::anyhow!("{}", e))?;
        printer.warning(&format!("{} drift items reported", all_drifts.len()));
    } else {
        printer.success("No drift to report");
    }

    Ok(())
}

fn cmd_decide(
    printer: &Printer,
    action: &str,
    resource: Option<&str>,
    source: Option<&str>,
    all: bool,
    state_dir: Option<&Path>,
) -> anyhow::Result<()> {
    let resolution = match action {
        "accept" => "accepted",
        "reject" => "rejected",
        other => {
            anyhow::bail!("Unknown action '{}'. Use 'accept' or 'reject'.", other);
        }
    };

    let state = open_state_store(state_dir)?;

    if all {
        let count = state.resolve_all_decisions(resolution)?;
        if count == 0 {
            printer.info("No pending decisions");
        } else {
            printer.success(&format!(
                "{} {} item{}",
                resolution.to_uppercase(),
                count,
                if count == 1 { "" } else { "s" }
            ));
            printer.info("Changes will take effect on next reconcile");
        }
        return Ok(());
    }

    if let Some(source_name) = source {
        let count = state.resolve_decisions_for_source(source_name, resolution)?;
        if count == 0 {
            printer.info(&format!(
                "No pending decisions for source '{}'",
                source_name
            ));
        } else {
            printer.success(&format!(
                "{} {} item{} from {}",
                resolution.to_uppercase(),
                count,
                if count == 1 { "" } else { "s" },
                source_name,
            ));
            printer.info("Changes will take effect on next reconcile");
        }
        return Ok(());
    }

    if let Some(resource_path) = resource {
        let resolved = state.resolve_decision(resource_path, resolution)?;
        if resolved {
            printer.success(&format!(
                "{}: {} will {} on next reconcile",
                resolution.to_uppercase(),
                resource_path,
                if resolution == "accepted" {
                    "be applied"
                } else {
                    "not be applied"
                }
            ));
        } else {
            printer.warning(&format!(
                "No pending decision found for '{}'",
                resource_path
            ));
        }
        return Ok(());
    }

    // No resource, source, or --all — show pending decisions
    let decisions = state.pending_decisions()?;
    if decisions.is_empty() {
        printer.info("No pending decisions");
        return Ok(());
    }

    printer.subheader("Pending Decisions");
    display_pending_decisions(printer, &decisions);
    printer.newline();
    printer
        .info("Use `cfgd decide accept <resource>` or `cfgd decide reject <resource>` to resolve");
    printer.info("Use `cfgd decide accept --all` or `cfgd decide accept --source <name>` for bulk operations");

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Arc, Mutex};

    const TEST_CONFIG_YAML: &str = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: default\n";

    const DEFAULT_PROFILE_YAML: &str = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  env:
    - name: editor
      value: vim
  packages:
    cargo:
      - bat
"#;

    const WORK_PROFILE_YAML: &str = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: work
spec:
  inherits:
    - default
  env:
    - name: editor
      value: code
  packages:
    cargo:
      - exa
"#;

    const SIMPLE_MODULE_YAML: &str = "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages: []\n";

    const RICH_CONFIG_YAML: &str = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: my-machine
spec:
  profile: default
  sources:
    - name: team-config
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 100
  daemon:
    enabled: true
    reconcile:
      interval: 5m
      onChange: true
      autoApply: false
    sync:
      interval: 10m
  secrets:
    backend: age
"#;

    // -----------------------------------------------------------------------
    // CliTestHarness — builder for isolated CLI test environments
    // -----------------------------------------------------------------------

    struct CliTestHarnessBuilder {
        config_yaml: String,
        profiles: Vec<(String, String)>,
        modules: Vec<(String, String)>,
        output_format: cfgd_core::output::OutputFormat,
    }

    impl CliTestHarnessBuilder {
        fn new() -> Self {
            Self {
                config_yaml: TEST_CONFIG_YAML.to_string(),
                profiles: vec![
                    ("default.yaml".into(), DEFAULT_PROFILE_YAML.into()),
                    ("work.yaml".into(), WORK_PROFILE_YAML.into()),
                ],
                modules: Vec::new(),
                output_format: cfgd_core::output::OutputFormat::Table,
            }
        }

        fn config(mut self, yaml: &str) -> Self {
            self.config_yaml = yaml.to_string();
            self
        }

        fn rich_config(self) -> Self {
            self.config(RICH_CONFIG_YAML)
        }

        fn profile(mut self, name: &str, content: &str) -> Self {
            self.profiles
                .push((format!("{name}.yaml"), content.to_string()));
            self
        }

        fn module(mut self, name: &str, content: &str) -> Self {
            self.modules.push((name.to_string(), content.to_string()));
            self
        }

        fn json(mut self) -> Self {
            self.output_format = cfgd_core::output::OutputFormat::Json;
            self
        }

        fn build(self) -> CliTestHarness {
            let config_dir = tempfile::tempdir().unwrap();
            let state_dir = tempfile::tempdir().unwrap();

            std::fs::write(config_dir.path().join("cfgd.yaml"), &self.config_yaml).unwrap();

            let profiles_dir = config_dir.path().join("profiles");
            std::fs::create_dir_all(&profiles_dir).unwrap();
            for (name, content) in &self.profiles {
                std::fs::write(profiles_dir.join(name), content).unwrap();
            }

            let modules_dir = config_dir.path().join("modules");
            std::fs::create_dir_all(&modules_dir).unwrap();
            for (name, content) in &self.modules {
                let mod_dir = modules_dir.join(name);
                std::fs::create_dir_all(mod_dir.join("files")).unwrap();
                std::fs::write(mod_dir.join("module.yaml"), content).unwrap();
            }

            let (printer, buf) = if self.output_format == cfgd_core::output::OutputFormat::Table {
                Printer::for_test()
            } else {
                Printer::for_test_with_format(self.output_format.clone())
            };

            CliTestHarness {
                config_dir,
                state_dir,
                printer,
                buf,
                output_format: self.output_format,
            }
        }
    }

    struct CliTestHarness {
        config_dir: tempfile::TempDir,
        state_dir: tempfile::TempDir,
        printer: Printer,
        buf: Arc<Mutex<String>>,
        output_format: cfgd_core::output::OutputFormat,
    }

    impl CliTestHarness {
        fn builder() -> CliTestHarnessBuilder {
            CliTestHarnessBuilder::new()
        }

        fn cli(&self) -> Cli {
            Cli {
                config: self.config_dir.path().join("cfgd.yaml"),
                profile: None,
                no_color: true,
                verbose: false,
                quiet: true,
                output: OutputFormatArg(self.output_format.clone()),
                jsonpath: None,
                state_dir: Some(self.state_dir.path().to_path_buf()),
                command: Command::Status { module: None },
            }
        }

        fn cli_with_command(&self, command: Command) -> Cli {
            Cli {
                command,
                ..self.cli()
            }
        }

        fn printer(&self) -> &Printer {
            &self.printer
        }

        fn config_path(&self) -> &Path {
            self.config_dir.path()
        }

        fn state_path(&self) -> &Path {
            self.state_dir.path()
        }

        fn output(&self) -> String {
            self.buf.lock().unwrap().clone()
        }

        fn json_output(&self) -> serde_json::Value {
            extract_json(&self.output())
        }

        fn assert_output_contains(&self, expected: &str) {
            let output = self.output();
            assert!(
                output.contains(expected),
                "expected output to contain '{expected}', got:\n{output}"
            );
        }

        fn assert_header(&self, header: &str) {
            self.assert_output_contains(header);
        }
    }

    // -----------------------------------------------------------------------
    // Free-standing assertion helpers
    // -----------------------------------------------------------------------

    fn assert_json_has_fields(json: &serde_json::Value, fields: &[&str]) {
        for field in fields {
            assert!(
                json.get(*field).is_some(),
                "JSON missing required field '{field}', got: {json}"
            );
        }
    }

    fn assert_json_field_type(json: &serde_json::Value, field: &str, type_name: &str) {
        let val = json
            .get(field)
            .unwrap_or_else(|| panic!("JSON missing field '{field}', got: {json}"));
        let matches = match type_name {
            "string" => val.is_string(),
            "number" | "u64" | "i64" | "f64" => val.is_number(),
            "bool" | "boolean" => val.is_boolean(),
            "array" => val.is_array(),
            "object" => val.is_object(),
            "null" => val.is_null(),
            _ => panic!("unknown type check '{type_name}'"),
        };
        assert!(matches, "expected '{field}' to be {type_name}, got: {val}");
    }

    fn assert_error_contains(result: &anyhow::Result<()>, expected: &str) {
        match result {
            Ok(_) => panic!("expected error containing '{expected}', but got Ok"),
            Err(e) => {
                let msg = e.to_string();
                assert!(
                    msg.contains(expected),
                    "expected error to contain '{expected}', got: {msg}"
                );
            }
        }
    }

    fn create_test_config_dir() -> tempfile::TempDir {
        let dir = tempfile::tempdir().unwrap();

        // Create profiles directory with a test profile
        let profiles_dir = dir.path().join("profiles");
        std::fs::create_dir_all(&profiles_dir).unwrap();

        std::fs::write(
            profiles_dir.join("default.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  env:
    - name: editor
      value: vim
  packages:
    cargo:
      - bat
"#,
        )
        .unwrap();

        std::fs::write(
            profiles_dir.join("work.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: work
spec:
  inherits:
    - default
  env:
    - name: editor
      value: code
  packages:
    cargo:
      - exa
"#,
        )
        .unwrap();

        dir
    }

    #[test]
    fn cli_has_output_flag() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        assert!(
            cmd.get_arguments().any(|a| a.get_id() == "output"),
            "should have global --output flag"
        );
    }

    #[test]
    fn cli_has_jsonpath_flag() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        assert!(
            cmd.get_arguments().any(|a| a.get_id() == "jsonpath"),
            "should have global --jsonpath flag"
        );
    }

    #[test]
    fn cli_output_flag_has_short_alias() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let output_arg = cmd
            .get_arguments()
            .find(|a| a.get_id() == "output")
            .unwrap();
        assert_eq!(
            output_arg.get_short(),
            Some('o'),
            "--output should have -o short alias"
        );
    }

    #[test]
    fn cli_init_has_apply_flag() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let init_cmd = cmd
            .get_subcommands()
            .find(|c| c.get_name() == "init")
            .unwrap();
        assert!(
            init_cmd.get_arguments().any(|a| a.get_id() == "apply"),
            "init should have --apply flag"
        );
        assert!(
            init_cmd
                .get_arguments()
                .any(|a| a.get_id() == "install_daemon"),
            "init should have --install-daemon flag"
        );
    }

    #[test]
    fn cli_daemon_has_subcommands() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let daemon_cmd = cmd
            .get_subcommands()
            .find(|c| c.get_name() == "daemon")
            .unwrap();
        let subcommands: Vec<&str> = daemon_cmd.get_subcommands().map(|c| c.get_name()).collect();
        assert!(
            subcommands.contains(&"run"),
            "daemon should have run subcommand"
        );
        assert!(
            subcommands.contains(&"install"),
            "daemon should have install subcommand"
        );
        assert!(
            subcommands.contains(&"uninstall"),
            "daemon should have uninstall subcommand"
        );
        assert!(
            subcommands.contains(&"status"),
            "daemon should have status subcommand"
        );
    }

    #[test]
    fn cli_has_source_subcommand() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let source_cmd = cmd.get_subcommands().find(|c| c.get_name() == "source");
        assert!(source_cmd.is_some(), "source subcommand should exist");

        let source_cmd = source_cmd.unwrap();
        let subcommands: Vec<&str> = source_cmd.get_subcommands().map(|c| c.get_name()).collect();
        assert!(subcommands.contains(&"add"));
        assert!(subcommands.contains(&"list"));
        assert!(subcommands.contains(&"show"));
        assert!(subcommands.contains(&"remove"));
        assert!(subcommands.contains(&"update"));
        assert!(subcommands.contains(&"override"));
        assert!(subcommands.contains(&"priority"));
        assert!(subcommands.contains(&"replace"));
    }

    #[test]
    fn infer_source_name_from_ssh_url() {
        assert_eq!(
            super::infer_source_name("git@github.com:acme-corp/dev-config.git"),
            "acme-corp-dev-config"
        );
    }

    #[test]
    fn infer_source_name_from_https_url() {
        assert_eq!(
            super::infer_source_name("https://github.com/acme/config.git"),
            "config"
        );
    }

    #[test]
    fn count_policy_items_empty() {
        let items = cfgd_core::config::PolicyItems::default();
        assert_eq!(super::count_policy_items(&items), 0);
    }

    #[test]
    fn add_and_remove_source_in_config() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(
            &config_path,
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
"#,
        )
        .unwrap();

        let source = cfgd_core::sources::SourceManager::build_source_spec(
            "acme",
            "git@github.com:acme/config.git",
            Some("backend"),
        );
        super::add_source_to_config(&config_path, &source).unwrap();

        let cfg = cfgd_core::config::load_config(&config_path).unwrap();
        assert_eq!(cfg.spec.sources.len(), 1);
        assert_eq!(cfg.spec.sources[0].name, "acme");

        super::remove_source_from_config(&config_path, "acme").unwrap();
        let cfg = cfgd_core::config::load_config(&config_path).unwrap();
        assert!(cfg.spec.sources.is_empty());
    }

    #[test]
    fn set_nested_yaml_value_creates_path() {
        let mut root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        super::set_nested_yaml_value(
            &mut root,
            "env.EDITOR",
            &serde_yaml::Value::String("nvim".into()),
        )
        .unwrap();

        let editor = root
            .get("env")
            .and_then(|v| v.get("EDITOR"))
            .and_then(|v| v.as_str());
        assert_eq!(editor, Some("nvim"));
    }

    #[test]
    fn pattern_matches_exact() {
        assert!(super::pattern_matches("packages.brew", "packages.brew"));
    }

    #[test]
    fn pattern_matches_prefix() {
        assert!(super::pattern_matches("packages", "packages.brew.ripgrep"));
        assert!(super::pattern_matches(
            "packages.brew",
            "packages.brew.ripgrep"
        ));
    }

    #[test]
    fn pattern_no_partial_match() {
        // "packages.br" should NOT match "packages.brew"
        assert!(!super::pattern_matches("packages.br", "packages.brew"));
    }

    #[test]
    fn pattern_matches_file_colon_paths() {
        assert!(super::pattern_matches("files", "files:/etc/foo"));
    }

    #[test]
    fn filter_plan_skip_entire_phase() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["ripgrep".into(), "fd".into()],
                        origin: "local".into(),
                    })],
                },
                Phase {
                    name: PhaseName::Files,
                    actions: vec![Action::File(FileAction::Create {
                        source: "/tmp/a".into(),
                        target: "/tmp/b".into(),
                        origin: "local".into(),
                        strategy: cfgd_core::config::FileStrategy::default(),
                        source_hash: None,
                    })],
                },
            ],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["packages".into()], &[]);
        assert!(plan.phases[0].actions.is_empty());
        assert_eq!(plan.phases[1].actions.len(), 1);
    }

    #[test]
    fn filter_plan_skip_single_package() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Install {
                    manager: "brew".into(),
                    packages: vec!["ripgrep".into(), "fd".into(), "bat".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["packages.brew.fd".into()], &[]);

        // Should keep ripgrep and bat, skip fd
        match &plan.phases[0].actions[0] {
            Action::Package(PackageAction::Install { packages, .. }) => {
                assert_eq!(packages, &["ripgrep".to_string(), "bat".to_string()]);
            }
            _ => panic!("expected Install action"),
        }
    }

    #[test]
    fn filter_plan_only_specific_manager() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![
                    Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["ripgrep".into()],
                        origin: "local".into(),
                    }),
                    Action::Package(PackageAction::Install {
                        manager: "cargo".into(),
                        packages: vec!["bat".into()],
                        origin: "local".into(),
                    }),
                ],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &["packages.brew".into()]);

        // Only brew should remain
        assert_eq!(plan.phases[0].actions.len(), 1);
        match &plan.phases[0].actions[0] {
            Action::Package(PackageAction::Install { manager, .. }) => {
                assert_eq!(manager, "brew");
            }
            _ => panic!("expected Install action"),
        }
    }

    #[test]
    fn filter_plan_only_specific_package() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Install {
                    manager: "brew".into(),
                    packages: vec!["ripgrep".into(), "fd".into(), "bat".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &["packages.brew.ripgrep".into()]);

        match &plan.phases[0].actions[0] {
            Action::Package(PackageAction::Install { packages, .. }) => {
                assert_eq!(packages, &["ripgrep".to_string()]);
            }
            _ => panic!("expected Install action"),
        }
    }

    // --- Module CRUD tests ---

    fn test_cli(dir: &Path) -> Cli {
        test_cli_with_state(dir, None)
    }

    fn test_cli_with_state(dir: &Path, state_dir: Option<PathBuf>) -> Cli {
        Cli {
            config: dir.join("cfgd.yaml"),
            profile: None,
            no_color: true,
            verbose: false,
            quiet: true,
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Table),
            jsonpath: None,
            state_dir,
            command: Command::Status { module: None },
        }
    }

    fn test_printer() -> Printer {
        Printer::new(cfgd_core::output::Verbosity::Quiet)
    }

    /// Extract JSON object or array from captured output that may contain
    /// preamble text (e.g. key_value lines from load_config_and_profile).
    fn extract_json(output: &str) -> serde_json::Value {
        // Find first '{' or '['
        let start = output
            .find('{')
            .or_else(|| output.find('['))
            .unwrap_or_else(|| panic!("no JSON found in output: {output}"));
        let json_str = output[start..].trim();
        serde_json::from_str(json_str)
            .unwrap_or_else(|e| panic!("invalid JSON at offset {start}: {e}, got: {output}"))
    }

    fn test_profile_create_args(name: &str) -> ProfileCreateArgs {
        ProfileCreateArgs {
            name: name.to_string(),
            inherits: vec![],
            modules: vec![],
            packages: vec![],
            env: vec![],
            aliases: vec![],
            system: vec![],
            files: vec![],
            private: false,
            secrets: vec![],
            pre_apply: vec![],
            post_apply: vec![],
            pre_reconcile: vec![],
            post_reconcile: vec![],
            on_change: vec![],
            on_drift: vec![],
        }
    }

    fn empty_profile_update_args() -> ProfileUpdateArgs {
        ProfileUpdateArgs {
            name: None,
            inherits: vec![],
            modules: vec![],
            packages: vec![],
            files: vec![],
            env: vec![],
            aliases: vec![],
            system: vec![],
            secrets: vec![],
            pre_apply: vec![],
            post_apply: vec![],
            pre_reconcile: vec![],
            post_reconcile: vec![],
            on_change: vec![],
            on_drift: vec![],
            private: false,
        }
    }

    fn create_module_in_dir(dir: &Path, name: &str, content: &str) {
        let mod_dir = dir.join("modules").join(name);
        std::fs::create_dir_all(mod_dir.join("files")).unwrap();
        std::fs::write(mod_dir.join("module.yaml"), content).unwrap();
    }

    fn empty_module_update_args(name: &str) -> ModuleUpdateArgs {
        ModuleUpdateArgs {
            name: name.to_string(),
            packages: vec![],
            files: vec![],
            env: vec![],
            aliases: vec![],
            depends: vec![],
            post_apply: vec![],
            private: false,
            description: None,
            sets: vec![],
        }
    }

    fn test_module_create_args(name: &str) -> ModuleCreateArgs {
        ModuleCreateArgs {
            name: name.to_string(),
            description: None,
            depends: vec![],
            packages: vec![],
            files: vec![],
            env: vec![],
            aliases: vec![],
            private: false,
            post_apply: vec![],
            sets: vec![],
            apply: false,
            yes: false,
        }
    }

    #[test]
    fn module_create_with_flags_produces_valid_yaml() {
        let dir = tempfile::tempdir().unwrap();
        let module_dir = dir.path().join("modules").join("test-mod");
        let module_yaml = module_dir.join("module.yaml");

        // Create a test file to import
        let test_file = dir.path().join("testfile.txt");
        std::fs::write(&test_file, "content").unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            description: Some("A test module".to_string()),
            depends: vec!["base".to_string()],
            packages: vec!["curl".to_string(), "vim".to_string()],
            files: vec![test_file.display().to_string()],
            post_apply: vec!["echo done".to_string()],
            sets: vec![
                "package.curl.minVersion=7.0".to_string(),
                "package.curl.prefer=brew,apt".to_string(),
                "package.vim.alias.snap=nvim".to_string(),
            ],
            ..test_module_create_args("test-mod")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        assert!(module_yaml.exists());

        let contents = std::fs::read_to_string(&module_yaml).unwrap();
        let doc = config::parse_module(&contents).unwrap();

        assert_eq!(doc.metadata.name, "test-mod");
        assert_eq!(doc.metadata.description, Some("A test module".to_string()));
        assert_eq!(doc.spec.depends, vec!["base"]);
        assert_eq!(doc.spec.packages.len(), 2);
        assert_eq!(doc.spec.packages[0].name, "curl");
        assert_eq!(doc.spec.packages[0].min_version, Some("7.0".to_string()));
        assert_eq!(doc.spec.packages[0].prefer, vec!["brew", "apt"]);
        assert_eq!(doc.spec.packages[1].name, "vim");
        assert_eq!(
            doc.spec.packages[1].aliases.get("snap"),
            Some(&"nvim".to_string())
        );
        assert_eq!(doc.spec.files.len(), 1);
        assert!(doc.spec.files[0].source.contains("testfile.txt"));
        assert!(
            doc.spec
                .scripts
                .as_ref()
                .unwrap()
                .post_apply
                .contains(&config::ScriptEntry::Simple("echo done".to_string()))
        );
        assert!(module_dir.join("files").join("testfile.txt").exists());
    }

    #[test]
    fn module_create_refuses_duplicate() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "existing",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: existing\nspec: {}\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            description: Some("dup".to_string()),
            ..test_module_create_args("existing")
        };
        let result = module::cmd_module_create(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("already exists"));
    }

    #[test]
    fn module_update_add_and_remove_packages() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages:\n    - name: curl\n    - name: vim\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            packages: vec!["ripgrep".to_string(), "-vim".to_string()],
            ..empty_module_update_args("test-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "test-mod").unwrap();
        let names: Vec<&str> = doc.spec.packages.iter().map(|p| p.name.as_str()).collect();
        assert!(names.contains(&"curl"));
        assert!(names.contains(&"ripgrep"));
        assert!(!names.contains(&"vim"));
    }

    #[test]
    fn module_update_set_overrides() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages:\n    - name: neovim\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            sets: vec![
                "package.neovim.minVersion=0.9".to_string(),
                "package.neovim.prefer=brew,snap,apt".to_string(),
                "package.neovim.alias.snap=nvim".to_string(),
            ],
            ..empty_module_update_args("test-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "test-mod").unwrap();
        let pkg = &doc.spec.packages[0];
        assert_eq!(pkg.min_version, Some("0.9".to_string()));
        assert_eq!(pkg.prefer, vec!["brew", "snap", "apt"]);
        assert_eq!(pkg.aliases.get("snap"), Some(&"nvim".to_string()));
    }

    #[test]
    fn module_delete_refuses_when_referenced() {
        let dir = create_test_config_dir();
        create_module_in_dir(
            dir.path(),
            "used-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: used-mod\nspec: {}\n",
        );

        // Update profile to reference the module
        let profile_path = dir.path().join("profiles").join("default.yaml");
        let mut doc = config::load_profile(&profile_path).unwrap();
        doc.spec.modules.push("used-mod".to_string());
        let yaml = serde_yaml::to_string(&doc).unwrap();
        std::fs::write(&profile_path, &yaml).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_delete(&cli, &printer, "used-mod", true, false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("referenced by"));
    }

    #[test]
    fn module_delete_succeeds_when_unreferenced() {
        let dir = create_test_config_dir();
        create_module_in_dir(
            dir.path(),
            "orphan-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: orphan-mod\nspec: {}\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_delete(&cli, &printer, "orphan-mod", true, false).unwrap();
        assert!(!dir.path().join("modules").join("orphan-mod").exists());
    }

    #[test]
    fn module_delete_purge_removes_target_files() {
        let dir = create_test_config_dir();

        // Create a target file outside the module directory
        let target_dir = dir.path().join("targets");
        std::fs::create_dir_all(&target_dir).unwrap();
        let target_file = target_dir.join("deployed.conf");
        std::fs::write(&target_file, "deployed content").unwrap();
        assert!(target_file.exists());

        // Create a module with a file entry pointing at the target
        let module_yaml = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: purge-mod\nspec:\n  files:\n    - source: files/deployed.conf\n      target: {}\n",
            target_file.display()
        );
        create_module_in_dir(dir.path(), "purge-mod", &module_yaml);
        // Write a source file in the module
        std::fs::write(
            dir.path()
                .join("modules")
                .join("purge-mod")
                .join("files")
                .join("deployed.conf"),
            "source content",
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_delete(&cli, &printer, "purge-mod", true, true).unwrap();
        assert!(!dir.path().join("modules").join("purge-mod").exists());
        assert!(!target_file.exists(), "purge should remove target file");
    }

    #[test]
    fn module_delete_no_purge_preserves_target_files() {
        let dir = create_test_config_dir();

        // Create a target file (not a symlink into the module)
        let target_dir = dir.path().join("targets");
        std::fs::create_dir_all(&target_dir).unwrap();
        let target_file = target_dir.join("regular.conf");
        std::fs::write(&target_file, "user content").unwrap();

        let module_yaml = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: keep-mod\nspec:\n  files:\n    - source: files/regular.conf\n      target: {}\n",
            target_file.display()
        );
        create_module_in_dir(dir.path(), "keep-mod", &module_yaml);

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_delete(&cli, &printer, "keep-mod", true, false).unwrap();
        assert!(!dir.path().join("modules").join("keep-mod").exists());
        assert!(
            target_file.exists(),
            "without purge, non-symlinked target files are preserved"
        );
    }

    #[test]
    fn apply_module_sets_rejects_invalid_format() {
        let mut doc = config::ModuleDocument {
            api_version: cfgd_core::API_VERSION.to_string(),
            kind: "Module".to_string(),
            metadata: config::ModuleMetadata {
                name: "test".to_string(),
                description: None,
            },
            spec: config::ModuleSpec::default(),
        };

        // No = sign
        assert!(module::apply_module_sets(&["bad-format".to_string()], &mut doc).is_err());
        // Invalid path prefix
        assert!(module::apply_module_sets(&["foo.bar=baz".to_string()], &mut doc).is_err());
        // Package not found
        assert!(
            module::apply_module_sets(&["package.missing.minVersion=1.0".to_string()], &mut doc)
                .is_err()
        );
        // Empty package name
        assert!(
            module::apply_module_sets(&["package..minVersion=1.0".to_string()], &mut doc).is_err()
        );
        // Empty field name
        assert!(module::apply_module_sets(&["package.curl.=1.0".to_string()], &mut doc).is_err());
    }

    #[test]
    fn module_update_idempotent_add() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages:\n    - name: curl\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            packages: vec!["curl".to_string()],
            ..empty_module_update_args("test-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "test-mod").unwrap();
        assert_eq!(doc.spec.packages.len(), 1);
    }

    // --- Profile CRUD tests ---

    #[test]
    fn profile_create_with_flags() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileCreateArgs {
            inherits: vec!["default".to_string()],
            modules: vec!["nvim".to_string()],
            packages: vec!["brew:curl".to_string(), "cargo:bat".to_string()],
            env: vec!["EDITOR=nvim".to_string()],
            system: vec!["shell=/bin/zsh".to_string()],
            ..test_profile_create_args("new-profile")
        };
        profile::cmd_profile_create(&cli, &printer, &args).unwrap();

        let profile_path = dir.path().join("profiles").join("new-profile.yaml");
        assert!(profile_path.exists());

        let doc = config::load_profile(&profile_path).unwrap();
        assert_eq!(doc.metadata.name, "new-profile");
        assert_eq!(doc.spec.inherits, vec!["default"]);
        assert_eq!(doc.spec.modules, vec!["nvim"]);
        assert!(doc.spec.env.iter().any(|e| e.name == "EDITOR"));
        assert!(doc.spec.system.contains_key("shell"));
    }

    #[test]
    fn profile_create_refuses_duplicate() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = test_profile_create_args("default");
        let result = profile::cmd_profile_create(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("already exists"));
    }

    #[test]
    fn profile_create_refuses_missing_parent() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileCreateArgs {
            inherits: vec!["nonexistent".to_string()],
            ..test_profile_create_args("child")
        };
        let result = profile::cmd_profile_create(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn profile_update_add_and_remove() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileUpdateArgs {
            modules: vec!["nvim".to_string()],
            packages: vec!["brew:jq".to_string()],
            env: vec!["EDITOR=nvim".to_string()],
            system: vec!["shell=/bin/zsh".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let profile_path = dir.path().join("profiles").join("default.yaml");
        let doc = config::load_profile(&profile_path).unwrap();
        assert!(doc.spec.modules.contains(&"nvim".to_string()));
        assert!(doc.spec.env.iter().any(|e| e.name == "EDITOR"));
        assert!(doc.spec.system.contains_key("shell"));
    }

    #[test]
    fn profile_delete_refuses_active() {
        let dir = create_test_config_dir();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: test\nspec:\n  profile: default\n",
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = profile::cmd_profile_delete(&cli, &printer, "default", true);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("active profile"));
    }

    #[test]
    fn profile_delete_refuses_when_inherited() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = profile::cmd_profile_delete(&cli, &printer, "default", true);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("inherited by"));
    }

    #[test]
    fn profile_delete_succeeds() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        profile::cmd_profile_delete(&cli, &printer, "work", true).unwrap();
        assert!(!dir.path().join("profiles").join("work.yaml").exists());
    }

    #[test]
    fn profiles_inheriting_finds_children() {
        let dir = create_test_config_dir();
        let result = profile::profiles_inheriting(&dir.path().join("profiles"), "default").unwrap();
        assert_eq!(result, vec!["work"]);

        let result = profile::profiles_inheriting(&dir.path().join("profiles"), "work").unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn parse_manager_package_valid() {
        let (mgr, pkg) = profile::parse_manager_package("brew:curl").unwrap();
        assert_eq!(mgr, "brew");
        assert_eq!(pkg, "curl");
    }

    #[test]
    fn parse_manager_package_invalid() {
        assert!(profile::parse_manager_package("no-colon").is_err());
        assert!(profile::parse_manager_package(":curl").is_err());
        assert!(profile::parse_manager_package("brew:").is_err());
        assert!(profile::parse_manager_package(":").is_err());
    }

    #[test]
    fn parse_package_flag_with_known_manager() {
        let known = &["brew", "apt", "cargo"];
        let (mgr, pkg) = parse_package_flag("brew:curl", known);
        assert_eq!(mgr, Some("brew".to_string()));
        assert_eq!(pkg, "curl");
    }

    #[test]
    fn parse_package_flag_bare_name() {
        let known = &["brew", "apt", "cargo"];
        let (mgr, pkg) = parse_package_flag("ripgrep", known);
        assert_eq!(mgr, None);
        assert_eq!(pkg, "ripgrep");
    }

    #[test]
    fn parse_package_flag_unknown_prefix_treated_as_bare() {
        let known = &["brew", "apt", "cargo"];
        // "python3:amd64" — "python3" is not a known manager
        let (mgr, pkg) = parse_package_flag("python3:amd64", known);
        assert_eq!(mgr, None);
        assert_eq!(pkg, "python3:amd64");
    }

    #[test]
    fn parse_package_flag_empty_parts() {
        let known = &["brew"];
        // ":curl" — empty prefix, not a known manager
        let (mgr, pkg) = parse_package_flag(":curl", known);
        assert_eq!(mgr, None);
        assert_eq!(pkg, ":curl");

        // "brew:" — empty suffix
        let (mgr, pkg) = parse_package_flag("brew:", known);
        assert_eq!(mgr, None);
        assert_eq!(pkg, "brew:");
    }

    #[test]
    fn parse_secret_spec_valid() {
        let spec = profile::parse_secret_spec("secrets/key.enc:~/.config/app/key").unwrap();
        assert_eq!(spec.source, "secrets/key.enc");
        assert_eq!(spec.target, Some(PathBuf::from("~/.config/app/key")));
        assert!(spec.template.is_none());
        assert!(spec.backend.is_none());
        assert!(spec.envs.is_none());
    }

    #[test]
    fn parse_secret_spec_provider_url() {
        // Provider URLs with :// must not be split on the scheme colon
        let spec = profile::parse_secret_spec("op://vault/item:~/.config/key").unwrap();
        assert_eq!(spec.source, "op://vault/item");
        assert_eq!(spec.target, Some(PathBuf::from("~/.config/key")));
    }

    #[test]
    fn parse_secret_spec_absolute_target() {
        let spec = profile::parse_secret_spec("secrets/db.enc:/etc/app/db.conf").unwrap();
        assert_eq!(spec.source, "secrets/db.enc");
        assert_eq!(spec.target, Some(PathBuf::from("/etc/app/db.conf")));
    }

    #[test]
    fn parse_secret_spec_invalid() {
        assert!(profile::parse_secret_spec("no-colon").is_err());
        assert!(profile::parse_secret_spec(":target").is_err());
        assert!(profile::parse_secret_spec("source:").is_err());
    }

    #[test]
    fn profile_update_inherits() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add inherits
        let args = ProfileUpdateArgs {
            inherits: vec!["default".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "work", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("work.yaml")).unwrap();
        assert!(doc.spec.inherits.contains(&"default".to_string()));

        // Remove inherits
        let args = ProfileUpdateArgs {
            inherits: vec!["-default".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "work", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("work.yaml")).unwrap();
        assert!(!doc.spec.inherits.contains(&"default".to_string()));
    }

    #[test]
    fn profile_update_secrets() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add secret
        let args = ProfileUpdateArgs {
            secrets: vec!["secrets/key.enc:~/.config/app/key".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert_eq!(doc.spec.secrets.len(), 1);
        assert_eq!(doc.spec.secrets[0].source, "secrets/key.enc");

        // Remove secret
        let args = ProfileUpdateArgs {
            secrets: vec!["-~/.config/app/key".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(doc.spec.secrets.is_empty());
    }

    #[test]
    fn profile_update_scripts() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add pre-apply, post-apply, pre-reconcile, post-reconcile, on-change
        let args = ProfileUpdateArgs {
            pre_apply: vec!["scripts/pre.sh".to_string()],
            post_apply: vec!["scripts/post.sh".to_string()],
            pre_reconcile: vec!["scripts/pre-rec.sh".to_string()],
            post_reconcile: vec!["scripts/post-rec.sh".to_string()],
            on_change: vec!["scripts/on-change.sh".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let scripts = doc.spec.scripts.as_ref().unwrap();
        assert_eq!(
            scripts.pre_apply,
            vec![config::ScriptEntry::Simple("scripts/pre.sh".to_string())]
        );
        assert_eq!(
            scripts.post_apply,
            vec![config::ScriptEntry::Simple("scripts/post.sh".to_string())]
        );
        assert_eq!(
            scripts.pre_reconcile,
            vec![config::ScriptEntry::Simple(
                "scripts/pre-rec.sh".to_string()
            )]
        );
        assert_eq!(
            scripts.post_reconcile,
            vec![config::ScriptEntry::Simple(
                "scripts/post-rec.sh".to_string()
            )]
        );
        assert_eq!(
            scripts.on_change,
            vec![config::ScriptEntry::Simple(
                "scripts/on-change.sh".to_string()
            )]
        );

        // Remove all scripts
        let args = ProfileUpdateArgs {
            pre_apply: vec!["-scripts/pre.sh".to_string()],
            post_apply: vec!["-scripts/post.sh".to_string()],
            pre_reconcile: vec!["-scripts/pre-rec.sh".to_string()],
            post_reconcile: vec!["-scripts/post-rec.sh".to_string()],
            on_change: vec!["-scripts/on-change.sh".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let scripts = doc.spec.scripts.as_ref().unwrap();
        assert!(scripts.pre_apply.is_empty());
        assert!(scripts.post_apply.is_empty());
        assert!(scripts.pre_reconcile.is_empty());
        assert!(scripts.post_reconcile.is_empty());
        assert!(scripts.on_change.is_empty());
    }

    #[test]
    fn profiles_using_module_finds_references() {
        let dir = create_test_config_dir();

        // Add module ref to default profile
        let profile_path = dir.path().join("profiles").join("default.yaml");
        let mut doc = config::load_profile(&profile_path).unwrap();
        doc.spec.modules.push("my-mod".to_string());
        std::fs::write(&profile_path, serde_yaml::to_string(&doc).unwrap()).unwrap();

        let result = module::profiles_using_module(&dir.path().join("profiles"), "my-mod").unwrap();
        assert_eq!(result, vec!["default"]);

        let result =
            module::profiles_using_module(&dir.path().join("profiles"), "nonexistent").unwrap();
        assert!(result.is_empty());
    }

    // --- Config CRUD tests ---

    #[test]
    fn config_show_displays_config() {
        let dir = create_test_config_dir();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test-config
spec:
  profile: default
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();
        let result = cmd_config_show(&cli, &printer);
        assert!(result.is_ok(), "config show failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Configuration"),
            "output should contain header 'Configuration', got: {output}"
        );
        assert!(
            output.contains("default"),
            "output should show profile name 'default', got: {output}"
        );
    }

    #[test]
    fn config_show_fails_without_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();
        let result = cmd_config_show(&cli, &printer);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    // --- Source CRUD tests ---

    #[test]
    fn source_create_scaffolds_manifest() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = cmd_source_create(
            &cli,
            &printer,
            Some("my-source"),
            Some("Test"),
            Some("1.0.0"),
        );
        assert!(
            result.is_ok(),
            "source create should scaffold manifest successfully: {:?}",
            result.err()
        );

        let source_path = dir.path().join("cfgd-source.yaml");
        assert!(source_path.exists());

        let contents = std::fs::read_to_string(&source_path).unwrap();
        assert!(contents.contains("my-source"));
        assert!(contents.contains("Test"));
        assert!(contents.contains("1.0.0"));
        // Should include profiles found in the directory
        assert!(contents.contains("default"));
        assert!(contents.contains("work"));
    }

    #[test]
    fn source_create_refuses_duplicate() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();
        std::fs::write(dir.path().join("cfgd-source.yaml"), "existing").unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();
        let result = cmd_source_create(&cli, &printer, Some("x"), Some("x"), Some("1.0"));
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("already exists"));
    }

    #[test]
    fn source_edit_fails_without_manifest() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();
        let result = cmd_source_edit(&cli, &printer);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("No cfgd-source.yaml")
        );
    }

    // --- Workflow tests ---

    #[test]
    fn generate_workflow_yaml_contains_all_resources() {
        let modules = vec!["neovim".to_string(), "zsh".to_string()];
        let profiles = vec!["default".to_string(), "work".to_string()];

        let yaml = generate_release_workflow_yaml(&modules, &profiles);

        // Header
        assert!(yaml.contains("name: cfgd Release"));
        assert!(yaml.contains("on:"));

        // Module paths
        assert!(yaml.contains("modules/neovim/**"));
        assert!(yaml.contains("modules/zsh/**"));

        // Profile paths
        assert!(yaml.contains("profiles/default.yaml"));
        assert!(yaml.contains("profiles/work.yaml"));

        // Jobs
        assert!(yaml.contains("detect-changes:"));
        assert!(yaml.contains("tag-modules:"));
        assert!(yaml.contains("tag-profiles:"));

        // Module outputs
        assert!(yaml.contains("module_neovim"));
        assert!(yaml.contains("module_zsh"));

        // Profile outputs
        assert!(yaml.contains("profile_default"));
        assert!(yaml.contains("profile_work"));
    }

    #[test]
    fn generate_workflow_yaml_modules_only() {
        let modules = vec!["vim".to_string()];
        let profiles: Vec<String> = vec![];

        let yaml = generate_release_workflow_yaml(&modules, &profiles);

        assert!(yaml.contains("tag-modules:"));
        assert!(!yaml.contains("tag-profiles:"));
    }

    #[test]
    fn generate_workflow_yaml_profiles_only() {
        let modules: Vec<String> = vec![];
        let profiles = vec!["default".to_string()];

        let yaml = generate_release_workflow_yaml(&modules, &profiles);

        assert!(!yaml.contains("tag-modules:"));
        assert!(yaml.contains("tag-profiles:"));
    }

    #[test]
    fn workflow_generate_creates_file() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = cmd_workflow_generate(&cli, &printer, false);
        assert!(
            result.is_ok(),
            "workflow generate should create the workflow file: {:?}",
            result.err()
        );

        let workflow_path = dir
            .path()
            .join(".github")
            .join("workflows")
            .join("cfgd-release.yml");
        assert!(workflow_path.exists());

        let contents = std::fs::read_to_string(&workflow_path).unwrap();
        assert!(contents.contains("cfgd Release"));
        assert!(contents.contains("default"));
    }

    #[test]
    fn workflow_generate_empty_repo() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        // No profiles or modules — should warn and return Ok
        let result = cmd_workflow_generate(&cli, &printer, false);
        assert!(
            result.is_ok(),
            "workflow generate should return Ok with no modules/profiles (warn+skip): {:?}",
            result.err()
        );

        let workflow_path = dir
            .path()
            .join(".github")
            .join("workflows")
            .join("cfgd-release.yml");
        assert!(
            !workflow_path.exists(),
            "no workflow file should be created for empty repo"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No profiles") || output.contains("nothing to generate"),
            "should warn about no profiles/modules, got: {output}"
        );
    }

    #[test]
    fn generate_workflow_yaml_hyphens_in_names() {
        let modules = vec!["my-module".to_string()];
        let profiles = vec!["my-profile".to_string()];

        let yaml = generate_release_workflow_yaml(&modules, &profiles);

        // Hyphens should be converted to underscores in output names
        assert!(yaml.contains("module_my_module"));
        assert!(yaml.contains("profile_my_profile"));
    }

    #[test]
    fn test_validate_resource_name_valid() {
        // Each call must succeed (unwrap ensures no silent failures)
        validate_resource_name("my-module", "Module").unwrap();
        validate_resource_name("my_module", "Module").unwrap();
        validate_resource_name("Module123", "Module").unwrap();
        validate_resource_name("a", "Module").unwrap();
        validate_resource_name("foo.bar", "Module").unwrap();
    }

    #[test]
    fn test_validate_resource_name_invalid() {
        assert!(validate_resource_name("", "Module").is_err());
        assert!(validate_resource_name("../etc", "Module").is_err());
        assert!(validate_resource_name(".hidden", "Module").is_err());
        assert!(validate_resource_name("-leading", "Module").is_err());
        assert!(validate_resource_name("foo/bar", "Module").is_err());
        assert!(validate_resource_name("foo bar", "Module").is_err());
        assert!(validate_resource_name("a".repeat(129).as_str(), "Module").is_err());
    }

    #[test]
    fn workflow_generate_force_overwrites() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // First generate
        cmd_workflow_generate(&cli, &printer, false).unwrap();
        let path = dir.path().join(".github/workflows/cfgd-release.yml");
        assert!(path.exists());

        // Write something different to the file
        std::fs::write(&path, "old content").unwrap();

        // Force overwrite
        cmd_workflow_generate(&cli, &printer, true).unwrap();
        let contents = std::fs::read_to_string(&path).unwrap();
        assert!(contents.contains("cfgd Release"));
        assert!(!contents.contains("old content"));
    }

    #[test]
    fn source_create_with_modules() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        // Create a module
        create_module_in_dir(
            dir.path(),
            "neovim",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: neovim\nspec:\n  packages: []\n  files: []\n  depends: []\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = cmd_source_create(
            &cli,
            &printer,
            Some("test-source"),
            Some("Test"),
            Some("1.0.0"),
        );
        assert!(
            result.is_ok(),
            "source create should succeed with modules present: {:?}",
            result.err()
        );

        let source_path = dir.path().join("cfgd-source.yaml");
        assert!(source_path.exists());

        let contents = std::fs::read_to_string(&source_path).unwrap();
        // Should contain both the profile and the module
        assert!(contents.contains("default"));
        assert!(contents.contains("neovim"));
    }

    #[test]
    fn source_create_output_is_parseable() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        cmd_source_create(
            &cli,
            &printer,
            Some("my-source"),
            Some("desc"),
            Some("0.1.0"),
        )
        .unwrap();

        let contents = std::fs::read_to_string(dir.path().join("cfgd-source.yaml")).unwrap();
        let result = config::parse_config_source(&contents);
        assert!(
            result.is_ok(),
            "Generated source YAML should be parseable: {:?}",
            result.err()
        );

        let doc = result.unwrap();
        assert_eq!(doc.metadata.name, "my-source");
        assert_eq!(doc.metadata.version, Some("0.1.0".to_string()));
    }

    #[test]
    fn config_show_with_all_sections() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  origin:
    - url: https://github.com/test/config
      branch: main
      type: Git
  sources:
    - name: team-config
      origin:
        url: https://github.com/test/team
        branch: main
        type: Git
      subscription:
        priority: 100
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd/modules
    security:
      requireSignatures: true
  daemon:
    enabled: true
    reconcile:
      interval: 5m
      onChange: true
      autoApply: false
    sync:
      interval: 30m
  secrets:
    backend: sops-age
  theme:
    name: ocean
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        let result = cmd_config_show(&cli, &printer);
        assert!(result.is_ok(), "config show failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(output.contains("Configuration"), "missing header");
        assert!(output.contains("Origins"), "missing Origins section");
        assert!(output.contains("Sources"), "missing Sources section");
        assert!(output.contains("team-config"), "missing source name");
        assert!(
            output.contains("Module Registries"),
            "missing Module Registries section"
        );
        assert!(output.contains("community"), "missing registry name");
        assert!(output.contains("Daemon"), "missing Daemon section");
        assert!(output.contains("Enabled: yes"), "missing daemon enabled");
        assert!(output.contains("Secrets"), "missing Secrets section");
        assert!(output.contains("sops-age"), "missing secrets backend");
        assert!(output.contains("Theme"), "missing Theme section");
        assert!(output.contains("ocean"), "missing theme name");
    }

    // --- Alias expansion tests ---

    #[test]
    fn expand_aliases_no_builtins() {
        // Aliases come from cfgd.yaml only — no hardcoded builtins.
        // Without a config, "add" and "remove" pass through unchanged.
        let args = vec!["cfgd".into(), "add".into(), "~/.zshrc".into()];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);

        let args = vec!["cfgd".into(), "remove".into(), "~/.zshrc".into()];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);
    }

    #[test]
    fn expand_aliases_no_match_passthrough() {
        let args = vec!["cfgd".into(), "apply".into(), "--dry-run".into()];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);
    }

    #[test]
    fn expand_aliases_skips_global_flags() {
        // Without config-defined aliases, "add" passes through even with global flags
        let args = vec![
            "cfgd".into(),
            "--verbose".into(),
            "add".into(),
            "~/.zshrc".into(),
        ];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);
    }

    #[test]
    fn expand_aliases_with_config_flag() {
        // With nonexistent config, no aliases are loaded — passthrough
        let args = vec![
            "cfgd".into(),
            "--config".into(),
            "/tmp/nonexistent.yaml".into(),
            "add".into(),
            "~/.zshrc".into(),
        ];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);
    }

    #[test]
    fn expand_aliases_empty_args() {
        let args = vec!["cfgd".into()];
        let expanded = expand_aliases(args.clone());
        assert_eq!(expanded, args);
    }

    #[test]
    fn resolve_profile_name_explicit_takes_precedence() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let result = resolve_profile_name(&cli, Some("my-profile"));
        assert_eq!(result.unwrap(), "my-profile");
    }

    #[test]
    fn resolve_profile_name_defaults_to_active() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();
        let cli = test_cli(dir.path());
        let result = resolve_profile_name(&cli, None);
        assert_eq!(result.unwrap(), "default");
    }

    #[test]
    fn parse_file_spec_plain_path() {
        let (source, target) = super::parse_file_spec("~/.zshrc").unwrap();
        assert_eq!(source, target);
    }

    #[test]
    fn parse_file_spec_source_target() {
        let (source, target) = super::parse_file_spec("./my-config:~/.config/app/config").unwrap();
        assert_eq!(source, std::path::PathBuf::from("./my-config"));
        assert!(target.to_string_lossy().contains(".config/app/config"));
    }

    #[test]
    fn parse_file_spec_empty_source_errors() {
        assert!(super::parse_file_spec(":~/.zshrc").is_err());
    }

    #[test]
    fn parse_file_spec_empty_target_errors() {
        assert!(super::parse_file_spec("~/.zshrc:").is_err());
    }

    #[test]
    fn is_unmanaged_file_nonexistent() {
        let dir = tempfile::tempdir().unwrap();
        let state = StateStore::open_in_memory().unwrap();
        let target = dir.path().join("does-not-exist");
        assert!(!is_unmanaged_file(&target, dir.path(), &state));
    }

    #[test]
    fn is_unmanaged_file_regular_file() {
        let dir = tempfile::tempdir().unwrap();
        let state = StateStore::open_in_memory().unwrap();
        let target = dir.path().join("existing-file");
        std::fs::write(&target, "content").unwrap();
        assert!(is_unmanaged_file(&target, dir.path(), &state));
    }

    #[test]
    #[cfg(unix)]
    fn is_unmanaged_file_cfgd_symlink() {
        let dir = tempfile::tempdir().unwrap();
        let state = StateStore::open_in_memory().unwrap();
        let source = dir.path().join("source-file");
        std::fs::write(&source, "content").unwrap();
        let target = dir.path().join("subdir").join("symlink");
        std::fs::create_dir_all(target.parent().unwrap()).unwrap();
        std::os::unix::fs::symlink(&source, &target).unwrap();
        // Symlink points into config_dir, so it's managed
        assert!(!is_unmanaged_file(&target, dir.path(), &state));
    }

    #[test]
    fn is_unmanaged_file_tracked_in_state() {
        let dir = tempfile::tempdir().unwrap();
        let state = StateStore::open_in_memory().unwrap();
        let target = dir.path().join("tracked-file");
        std::fs::write(&target, "content").unwrap();
        let target_str = target.display().to_string();
        state
            .upsert_managed_resource("file", &target_str, "local", None, None)
            .unwrap();
        assert!(!is_unmanaged_file(&target, dir.path(), &state));
    }

    // --- config get/set/unset helpers ---

    fn make_test_config(dir: &std::path::Path) -> std::path::PathBuf {
        let path = dir.join("cfgd.yaml");
        std::fs::write(
            &path,
            "apiVersion: cfgd.io/v1alpha1\n\
             kind: Config\n\
             metadata:\n\
             \x20 name: test\n\
             spec:\n\
             \x20 profile: work\n\
             \x20 fileStrategy: Symlink\n\
             \x20 theme:\n\
             \x20\x20\x20 name: dracula\n\
             \x20 daemon:\n\
             \x20\x20\x20 enabled: true\n\
             \x20\x20\x20 reconcile:\n\
             \x20\x20\x20\x20\x20 interval: 5m\n\
             \x20\x20\x20\x20\x20 onChange: false\n\
             \x20 aliases:\n\
             \x20\x20\x20 add: 'profile update --file'\n\
             \x20\x20\x20 deploy: 'apply --yes'\n",
        )
        .unwrap();
        path
    }

    #[test]
    fn config_get_scalar() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let val = walk_yaml_path(spec, "profile").unwrap();
        assert_eq!(val.as_str().unwrap(), "work");
    }

    #[test]
    fn config_get_nested() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let val = walk_yaml_path(spec, "daemon.reconcile.interval").unwrap();
        assert_eq!(val.as_str().unwrap(), "5m");
    }

    #[test]
    fn config_get_boolean() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let val = walk_yaml_path(spec, "daemon.enabled").unwrap();
        assert!(val.as_bool().unwrap());
    }

    #[test]
    fn config_get_complex_returns_mapping() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let val = walk_yaml_path(spec, "daemon").unwrap();
        assert!(val.is_mapping());
    }

    #[test]
    fn config_get_missing_key_errors() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let result = walk_yaml_path(spec, "nonexistent");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn config_get_alias() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get("spec").unwrap();

        let val = walk_yaml_path(spec, "aliases.deploy").unwrap();
        assert_eq!(val.as_str().unwrap(), "apply --yes");
    }

    #[test]
    fn config_set_scalar() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get_mut("spec").unwrap();

        let (parent, key) = walk_yaml_path_mut(spec, "profile").unwrap();
        parent.insert(serde_yaml::Value::String(key), parse_yaml_value("personal"));

        let spec = raw.get("spec").unwrap();
        let val = walk_yaml_path(spec, "profile").unwrap();
        assert_eq!(val.as_str().unwrap(), "personal");
    }

    #[test]
    fn config_set_creates_intermediates() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("cfgd.yaml");
        std::fs::write(
            &path,
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: base
"#,
        )
        .unwrap();
        let contents = std::fs::read_to_string(&path).unwrap();
        let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get_mut("spec").unwrap();

        let (parent, key) = walk_yaml_path_mut(spec, "daemon.reconcile.interval").unwrap();
        parent.insert(serde_yaml::Value::String(key), parse_yaml_value("10m"));

        let spec = raw.get("spec").unwrap();
        let val = walk_yaml_path(spec, "daemon.reconcile.interval").unwrap();
        assert_eq!(val.as_str().unwrap(), "10m");
    }

    #[test]
    fn config_set_boolean_value() {
        let val = parse_yaml_value("true");
        assert_eq!(val, serde_yaml::Value::Bool(true));
        let val = parse_yaml_value("false");
        assert_eq!(val, serde_yaml::Value::Bool(false));
    }

    #[test]
    fn config_set_number_value() {
        let val = parse_yaml_value("42");
        assert!(val.is_number());
        assert_eq!(val.as_i64().unwrap(), 42);
    }

    #[test]
    fn config_set_string_value() {
        let val = parse_yaml_value("hello world");
        assert_eq!(val.as_str().unwrap(), "hello world");
    }

    #[test]
    fn config_unset_removes_key() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get_mut("spec").unwrap();

        // Verify theme exists before removal
        assert!(
            walk_yaml_path(spec, "theme").is_ok(),
            "theme key should exist before unset"
        );

        let (parent, key) = walk_yaml_path_mut(spec, "theme").unwrap();
        let yaml_key = serde_yaml::Value::String(key);
        let removed = parent.remove(&yaml_key);
        assert!(removed.is_some(), "remove should return the removed value");

        // Verify theme is actually gone
        let spec = raw.get("spec").unwrap();
        assert!(
            walk_yaml_path(spec, "theme").is_err(),
            "theme key should not exist after unset"
        );
    }

    #[test]
    fn config_unset_nested_alias() {
        let dir = tempfile::tempdir().unwrap();
        let path = make_test_config(dir.path());
        let contents = std::fs::read_to_string(&path).unwrap();
        let mut raw: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
        let spec = raw.get_mut("spec").unwrap();

        // Verify deploy alias exists before removal
        assert!(
            walk_yaml_path(spec, "aliases.deploy").is_ok(),
            "deploy alias should exist before unset"
        );

        let (parent, key) = walk_yaml_path_mut(spec, "aliases.deploy").unwrap();
        let yaml_key = serde_yaml::Value::String(key);
        let removed = parent.remove(&yaml_key);
        assert!(removed.is_some(), "remove should return the removed value");

        // "add" alias should still exist, "deploy" should be gone
        let spec = raw.get("spec").unwrap();
        walk_yaml_path(spec, "aliases.add").unwrap();
        assert!(
            walk_yaml_path(spec, "aliases.deploy").is_err(),
            "deploy alias should not exist after unset"
        );
    }

    #[test]
    fn theme_string_shorthand_deserializes() {
        let yaml = r#"
apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  theme: dracula
"#;
        let cfg = config::parse_config(yaml, std::path::Path::new("cfgd.yaml")).unwrap();
        let theme = cfg.spec.theme.unwrap();
        assert_eq!(theme.name, "dracula");
        assert!(theme.overrides.is_empty());
    }

    #[test]
    fn theme_struct_form_deserializes() {
        let yaml = "apiVersion: cfgd.io/v1alpha1\n\
                     kind: Config\n\
                     metadata:\n\
                     \x20 name: test\n\
                     spec:\n\
                     \x20 theme:\n\
                     \x20\x20\x20 name: dracula\n\
                     \x20\x20\x20 overrides:\n\
                     \x20\x20\x20\x20\x20 success: '#50fa7b'\n";
        let cfg = config::parse_config(yaml, std::path::Path::new("cfgd.yaml")).unwrap();
        let theme = cfg.spec.theme.unwrap();
        assert_eq!(theme.name, "dracula");
        assert_eq!(theme.overrides.success.as_deref(), Some("#50fa7b"));
    }

    // --- parse_file_spec ---

    #[test]
    fn parse_file_spec_with_colon() {
        let (src, tgt) = super::parse_file_spec("/tmp/a:/tmp/b").unwrap();
        assert_eq!(src, PathBuf::from("/tmp/a"));
        assert_eq!(tgt, PathBuf::from("/tmp/b"));
    }

    #[test]
    fn parse_file_spec_no_colon() {
        let (src, tgt) = super::parse_file_spec("/tmp/a").unwrap();
        assert_eq!(src, tgt);
    }

    #[test]
    fn parse_file_spec_empty_source() {
        assert!(super::parse_file_spec(":/tmp/b").is_err());
    }

    #[test]
    fn parse_file_spec_empty_target() {
        assert!(super::parse_file_spec("/tmp/a:").is_err());
    }

    #[test]
    fn parse_file_spec_windows_drive_letter() {
        // C:\Users\foo should NOT be split on the drive-letter colon
        let (src, tgt) = super::parse_file_spec(r"C:\Users\foo").unwrap();
        assert_eq!(src, PathBuf::from(r"C:\Users\foo"));
        assert_eq!(tgt, PathBuf::from(r"C:\Users\foo"));
    }

    #[test]
    fn parse_file_spec_windows_source_target() {
        // source:target where both have drive letters
        let (src, tgt) = super::parse_file_spec(r"/home/a:C:\Users\b").unwrap();
        assert_eq!(src, PathBuf::from("/home/a"));
        assert_eq!(tgt, PathBuf::from(r"C:\Users\b"));
    }

    // --- add_to_gitignore ---

    #[test]
    fn add_to_gitignore_creates_file() {
        let dir = tempfile::tempdir().unwrap();
        super::add_to_gitignore(dir.path(), "secrets/key.enc").unwrap();
        let content = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
        assert!(content.contains("secrets/key.enc"));
    }

    #[test]
    fn add_to_gitignore_no_duplicate() {
        let dir = tempfile::tempdir().unwrap();
        super::add_to_gitignore(dir.path(), "secrets/key.enc").unwrap();
        super::add_to_gitignore(dir.path(), "secrets/key.enc").unwrap();
        let content = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
        let count = content.matches("secrets/key.enc").count();
        assert_eq!(count, 1);
    }

    // --- parse_yaml_value ---

    #[test]
    fn parse_yaml_value_bool_true() {
        assert_eq!(
            super::parse_yaml_value("true"),
            serde_yaml::Value::Bool(true)
        );
    }

    #[test]
    fn parse_yaml_value_bool_false() {
        assert_eq!(
            super::parse_yaml_value("false"),
            serde_yaml::Value::Bool(false)
        );
    }

    #[test]
    fn parse_yaml_value_null() {
        assert_eq!(super::parse_yaml_value("null"), serde_yaml::Value::Null);
        assert_eq!(super::parse_yaml_value("~"), serde_yaml::Value::Null);
    }

    #[test]
    fn parse_yaml_value_integer() {
        assert_eq!(
            super::parse_yaml_value("42"),
            serde_yaml::Value::Number(42.into())
        );
    }

    #[test]
    fn parse_yaml_value_string() {
        assert_eq!(
            super::parse_yaml_value("hello"),
            serde_yaml::Value::String("hello".into())
        );
    }

    // --- walk_yaml_path ---

    #[test]
    fn walk_yaml_path_root() {
        let value = serde_yaml::Value::String("hi".into());
        let result = super::walk_yaml_path(&value, ".").unwrap();
        assert_eq!(result, &value);
    }

    #[test]
    fn walk_yaml_path_nested() {
        let yaml = "a:\n  b: 42\n";
        let value: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
        let result = super::walk_yaml_path(&value, "a.b").unwrap();
        assert_eq!(result.as_i64(), Some(42));
    }

    #[test]
    fn walk_yaml_path_missing_key() {
        let yaml = "a:\n  b: 42\n";
        let value: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
        assert!(super::walk_yaml_path(&value, "a.c").is_err());
    }

    #[test]
    fn walk_yaml_path_empty_segment() {
        let value = serde_yaml::Value::Null;
        assert!(super::walk_yaml_path(&value, "a..b").is_err());
    }

    // --- walk_yaml_path_mut ---

    #[test]
    fn walk_yaml_path_mut_creates_intermediate() {
        let mut value = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        let (parent, leaf) = super::walk_yaml_path_mut(&mut value, "a.b.c").unwrap();
        assert_eq!(leaf, "c");
        parent.insert(
            serde_yaml::Value::String("c".into()),
            serde_yaml::Value::String("val".into()),
        );
        let result = super::walk_yaml_path(&value, "a.b.c").unwrap();
        assert_eq!(result.as_str(), Some("val"));
    }

    // --- scan_profile_names / scan_module_names ---

    #[test]
    fn scan_profile_names_from_dir() {
        let dir = create_test_config_dir();
        let profiles_dir = dir.path().join("profiles");
        let names = super::scan_profile_names(&profiles_dir).unwrap();
        assert!(names.contains(&"default".to_string()));
        assert!(names.contains(&"work".to_string()));
    }

    #[test]
    fn scan_profile_names_empty_dir() {
        let dir = tempfile::tempdir().unwrap();
        let names = super::scan_profile_names(dir.path()).unwrap();
        assert_eq!(
            names,
            Vec::<String>::new(),
            "empty dir should yield empty profile list"
        );
    }

    #[test]
    fn scan_module_names_from_dir() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages: []\n",
        );
        let modules_dir = dir.path().join("modules");
        let names = super::scan_module_names(&modules_dir).unwrap();
        assert_eq!(names, vec!["test-mod"]);
    }

    #[test]
    fn scan_module_names_nonexistent() {
        let dir = tempfile::tempdir().unwrap();
        let names = super::scan_module_names(&dir.path().join("nope")).unwrap();
        assert_eq!(
            names,
            Vec::<String>::new(),
            "nonexistent dir should yield empty module list"
        );
    }

    // --- copy_files_to_dir ---

    #[test]
    fn copy_files_to_dir_copies_and_symlinks() {
        let dir = tempfile::tempdir().unwrap();
        let source = dir.path().join("myfile.txt");
        std::fs::write(&source, "content").unwrap();
        let repo_dir = dir.path().join("repo");

        let results = super::copy_files_to_dir(&[source.display().to_string()], &repo_dir).unwrap();

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].0, "myfile.txt");
        // File should be in repo
        assert!(repo_dir.join("myfile.txt").exists());
        // Original should now be a symlink
        assert!(source.symlink_metadata().unwrap().file_type().is_symlink());
    }

    #[test]
    fn copy_files_to_dir_nonexistent_source_errors() {
        let dir = tempfile::tempdir().unwrap();
        let result = super::copy_files_to_dir(&["/nonexistent-12345/file".into()], dir.path());
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("File not found"),
            "expected 'File not found' error, got: {msg}"
        );
    }

    // --- generate_release_workflow_yaml ---

    #[test]
    fn generate_release_workflow_empty() {
        let yaml = super::generate_release_workflow_yaml(&[], &[]);
        assert!(yaml.contains("placeholder:"));
        assert!(yaml.contains("No modules or profiles to tag yet"));
    }

    #[test]
    fn generate_release_workflow_with_modules() {
        let yaml = super::generate_release_workflow_yaml(&["shell-tools".into()], &[]);
        assert!(yaml.contains("modules/shell-tools/**"));
        assert!(yaml.contains("tag-modules:"));
        assert!(!yaml.contains("placeholder:"));
    }

    #[test]
    fn generate_release_workflow_with_profiles() {
        let yaml = super::generate_release_workflow_yaml(&[], &["work".into()]);
        assert!(yaml.contains("profiles/work.yaml"));
        assert!(yaml.contains("tag-profiles:"));
    }

    #[test]
    fn generate_release_workflow_both() {
        let yaml =
            super::generate_release_workflow_yaml(&["git-tools".into()], &["personal".into()]);
        assert!(yaml.contains("tag-modules:"));
        assert!(yaml.contains("tag-profiles:"));
        assert!(yaml.contains("detect-changes:"));
    }

    // --- cmd_config_get / cmd_config_set / cmd_config_unset ---

    #[test]
    fn config_get_reads_value() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path.clone(),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        let result = super::cmd_config_get(&cli, &printer, "profile");
        assert!(
            result.is_ok(),
            "config get should read profile value without error: {:?}",
            result.err()
        );

        // Verify the config file actually contains the expected profile
        let contents = std::fs::read_to_string(&config_path).unwrap();
        assert!(contents.contains("profile: default"));
    }

    #[test]
    fn cmd_config_get_missing_key_errors() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path,
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        assert!(super::cmd_config_get(&cli, &printer, "nonexistent").is_err());
    }

    #[test]
    fn config_set_and_get_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path.clone(),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        super::cmd_config_set(&cli, &printer, "profile", "work").unwrap();

        let contents = std::fs::read_to_string(&config_path).unwrap();
        assert!(contents.contains("work"));
    }

    #[test]
    fn cmd_config_unset_removes_key() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path.clone(),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        let result = super::cmd_config_unset(&cli, &printer, "profile");
        assert!(
            result.is_ok(),
            "config unset should remove the key successfully: {:?}",
            result.err()
        );

        // Verify the key was actually removed from the config file
        let contents = std::fs::read_to_string(&config_path).unwrap();
        assert!(
            !contents.contains("profile:"),
            "profile key should be removed from config"
        );
    }

    #[test]
    fn cmd_config_unset_missing_key_errors() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path,
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        assert!(super::cmd_config_unset(&cli, &printer, "nope").is_err());
    }

    // --- cmd_config_show ---

    #[test]
    fn config_show_succeeds_with_valid_config() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path,
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        assert!(
            super::cmd_config_show(&cli, &printer).is_ok(),
            "config show should succeed when cfgd.yaml exists and is valid"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Configuration"),
            "output should contain header, got: {output}"
        );
        assert!(
            output.contains("default"),
            "output should show profile, got: {output}"
        );
    }

    #[test]
    fn config_show_errors_without_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            config: dir.path().join("nonexistent.yaml"),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        assert!(super::cmd_config_show(&cli, &printer).is_err());
    }

    // --- secret_backend_from_config ---

    #[test]
    fn secret_backend_defaults_to_sops() {
        let (backend, _) = super::secret_backend_from_config(None);
        assert_eq!(backend, "sops");
    }

    // --- print_apply_result ---

    #[test]
    fn print_apply_result_success() {
        let printer = test_printer();
        let result = cfgd_core::reconciler::ApplyResult {
            status: cfgd_core::state::ApplyStatus::Success,
            action_results: vec![],
            apply_id: 1,
        };
        let status = super::print_apply_result(&result, &printer);
        assert_eq!(status, cfgd_core::state::ApplyStatus::Success);
    }

    #[test]
    fn print_apply_result_partial() {
        let printer = test_printer();
        let result = cfgd_core::reconciler::ApplyResult {
            status: cfgd_core::state::ApplyStatus::Partial,
            action_results: vec![],
            apply_id: 2,
        };
        let status = super::print_apply_result(&result, &printer);
        assert_eq!(status, cfgd_core::state::ApplyStatus::Partial);
    }

    #[test]
    fn print_apply_result_failed() {
        let printer = test_printer();
        let result = cfgd_core::reconciler::ApplyResult {
            status: cfgd_core::state::ApplyStatus::Failed,
            action_results: vec![],
            apply_id: 3,
        };
        let status = super::print_apply_result(&result, &printer);
        assert_eq!(status, cfgd_core::state::ApplyStatus::Failed);
    }

    // --- print_verify_results ---

    #[test]
    fn print_verify_results_all_pass() {
        let printer = test_printer();
        let results = vec![reconciler::VerifyResult {
            resource_type: "package".into(),
            resource_id: "curl".into(),
            expected: "installed".into(),
            actual: "installed".into(),
            matches: true,
        }];
        let (pass, fail) = super::print_verify_results(&results, &printer);
        assert_eq!(pass, 1);
        assert_eq!(fail, 0);
    }

    #[test]
    fn print_verify_results_with_failures() {
        let printer = test_printer();
        let results = vec![
            reconciler::VerifyResult {
                resource_type: "package".into(),
                resource_id: "curl".into(),
                expected: "installed".into(),
                actual: "installed".into(),
                matches: true,
            },
            reconciler::VerifyResult {
                resource_type: "sysctl".into(),
                resource_id: "net.ipv4.ip_forward".into(),
                expected: "1".into(),
                actual: "0".into(),
                matches: false,
            },
        ];
        let (pass, fail) = super::print_verify_results(&results, &printer);
        assert_eq!(pass, 1);
        assert_eq!(fail, 1);
    }

    // --- expand_aliases ---

    #[test]
    fn expand_aliases_passthrough() {
        let args = vec!["cfgd".into(), "status".into()];
        let result = super::expand_aliases(args.clone());
        assert_eq!(result, args);
    }

    #[test]
    fn expand_aliases_no_alias_passthrough() {
        // With empty builtin_aliases, no expansion happens
        let args = vec!["cfgd".into(), "apply".into(), "--dry-run".into()];
        let result = super::expand_aliases(args.clone());
        assert_eq!(result, args);
    }

    // --- extract_config_path ---

    #[test]
    fn extract_config_path_explicit() {
        let args = vec![
            "cfgd".into(),
            "--config".into(),
            "/tmp/my.yaml".into(),
            "status".into(),
        ];
        assert_eq!(
            super::extract_config_path(&args),
            Some(PathBuf::from("/tmp/my.yaml"))
        );
    }

    #[test]
    fn extract_config_path_equals() {
        let args = vec!["cfgd".into(), "--config=/tmp/my.yaml".into()];
        assert_eq!(
            super::extract_config_path(&args),
            Some(PathBuf::from("/tmp/my.yaml"))
        );
    }

    // --- resolve_profile_name ---

    #[test]
    fn resolve_profile_name_explicit_from_name() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let result = super::resolve_profile_name(&cli, Some("work")).unwrap();
        assert_eq!(result, "work");
    }

    // --- parse_package_flag ---

    #[test]
    fn parse_package_flag_known_manager_splits() {
        let known = &["brew", "apt", "cargo"];
        let (mgr, pkg) = super::parse_package_flag("brew:ripgrep", known);
        assert_eq!(mgr, Some("brew".to_string()));
        assert_eq!(pkg, "ripgrep");
    }

    #[test]
    fn parse_package_flag_unknown_manager_passthrough() {
        let known = &["brew", "apt"];
        let (mgr, pkg) = super::parse_package_flag("unknown:ripgrep", known);
        assert!(mgr.is_none());
        assert_eq!(pkg, "unknown:ripgrep");
    }

    #[test]
    fn parse_package_flag_bare_name_passthrough() {
        let known = &["brew"];
        let (mgr, pkg) = super::parse_package_flag("ripgrep", known);
        assert!(mgr.is_none());
        assert_eq!(pkg, "ripgrep");
    }

    // --- list_yaml_stems ---

    #[test]
    fn list_yaml_stems_finds_yaml_files() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("alpha.yaml"), "").unwrap();
        std::fs::write(dir.path().join("beta.yml"), "").unwrap();
        std::fs::write(dir.path().join("not-yaml.txt"), "").unwrap();

        let stems = super::list_yaml_stems(dir.path()).unwrap();
        assert!(stems.contains(&"alpha".to_string()));
        assert!(stems.contains(&"beta".to_string()));
        assert!(!stems.contains(&"not-yaml".to_string()));
    }

    // --- builtin_aliases ---

    #[test]
    fn builtin_aliases_returns_map() {
        let aliases = super::builtin_aliases();
        assert_eq!(
            aliases.len(),
            0,
            "builtin_aliases should return an empty map (no built-in aliases yet)"
        );
    }

    // --- cmd_doctor basic ---

    #[test]
    fn cmd_doctor_with_valid_config() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_doctor(&cli, &printer);
        assert!(result.is_ok(), "doctor failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(output.contains("Doctor"), "missing Doctor header");
        assert!(output.contains("Config file"), "missing config file status");
        assert!(
            output.contains("Package Managers"),
            "missing Package Managers section"
        );
    }

    #[test]
    fn cmd_doctor_without_config() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("nonexistent.yaml");

        let cli = Cli {
            config: config_path,
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_doctor(&cli, &printer);
        assert!(result.is_ok(), "doctor failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(output.contains("Doctor"), "missing Doctor header");
        assert!(
            output.contains("not found"),
            "should report config not found, got: {output}"
        );
    }

    // --- Command handler tests (require state store) ---
    //
    // These test full command handlers that depend on the state store.
    // Each test passes the state dir through the Cli struct (no env vars, no serial needed).

    /// Set up a full test environment: config dir + state dir.
    /// Returns (config_dir_tempdir, state_dir_tempdir).
    /// Use `test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))` to get a Cli
    /// that uses the state dir directly (no env vars, no cross-thread races).
    fn setup_test_env() -> (tempfile::TempDir, tempfile::TempDir) {
        let config_dir = create_test_config_dir();
        std::fs::write(config_dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        // Create modules dir
        std::fs::create_dir_all(config_dir.path().join("modules")).unwrap();

        let state_dir = tempfile::tempdir().unwrap();
        (config_dir, state_dir)
    }

    #[test]
    fn cmd_status_with_empty_state() {
        let h = CliTestHarness::builder().build();
        super::cmd_status(&h.cli(), h.printer(), None).unwrap();
        h.assert_header("Status");
        h.assert_output_contains("No applies recorded yet");
    }

    #[test]
    fn cmd_status_module_not_found() {
        let h = CliTestHarness::builder().build();
        super::cmd_status(&h.cli(), h.printer(), Some("nonexistent")).unwrap();
        h.assert_output_contains("nonexistent");
    }

    #[test]
    fn cmd_status_module_found() {
        let h = CliTestHarness::builder()
            .module("test-mod", SIMPLE_MODULE_YAML)
            .build();
        super::cmd_status(&h.cli(), h.printer(), Some("test-mod")).unwrap();
        h.assert_output_contains("test-mod");
    }

    #[test]
    fn cmd_verify_module() {
        let h = CliTestHarness::builder()
            .module("test-mod", SIMPLE_MODULE_YAML)
            .build();
        super::cmd_verify(&h.cli(), h.printer(), Some("test-mod")).unwrap();
        h.assert_header("Verify");
        h.assert_output_contains("test-mod");
        let output = h.output();
        assert!(
            output.contains("match desired state") || output.contains("healthy"),
            "should report verification result, got: {output}"
        );
    }

    #[test]
    fn cmd_log_with_empty_state() {
        let h = CliTestHarness::builder().build();
        super::cmd_log(h.printer(), 10, None, Some(h.state_path())).unwrap();
        h.assert_header("Apply History");
        h.assert_output_contains("No applies recorded yet");
    }

    #[test]
    fn cmd_apply_dry_run_empty_profile() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        h.assert_header("Plan");
        let output = h.output();
        assert!(
            output.contains("Nothing to do") || output.contains("action(s) planned"),
            "should indicate plan result, got: {output}"
        );

        // Dry-run should NOT create any state store records
        let state = StateStore::open(&h.state_path().join("cfgd.db")).unwrap();
        assert!(
            state.last_apply().unwrap().is_none(),
            "dry-run should not create apply records in state store"
        );
    }

    #[test]
    fn cmd_apply_from_flag_parses() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();
        let args = ApplyArgs {
            from: Some("https://github.com/example/config.git".to_string()),
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: Some("dev-tools".to_string()),
            skip_scripts: false,
        };

        // cmd_apply should attempt to resolve the --from URL.
        // The URL is unreachable so this will fail — but it must fail because
        // the URL was attempted (resolve_from path), not because --from was ignored.
        let result = super::cmd_apply(&cli, &printer, &args);
        // Either succeeds (local config found) or fails on the URL — both prove --from was wired up
        if let Err(ref e) = result {
            let msg = e.to_string();
            // The error should relate to git/clone/network, NOT to missing config or parse errors
            assert!(
                !msg.contains("not found") || msg.contains("clone"),
                "--from error should be about git clone, not missing config: {msg}"
            );
        }
    }

    #[test]
    fn cmd_apply_dry_run_with_phase_filter() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: Some("packages".to_string()),
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        h.assert_header("Plan");
        let output = h.output();
        assert!(
            output.contains("Nothing to do") || output.contains("Packages"),
            "should mention nothing-to-do or the filtered phase, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_dry_run_invalid_phase() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: Some("invalid-phase".to_string()),
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        let result = super::cmd_apply(&h.cli(), h.printer(), &args);
        assert_error_contains(&result, "Unknown phase");
    }

    #[test]
    fn cmd_apply_dry_run_with_skip() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec!["packages".to_string()],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        h.assert_header("Plan");
        let output = h.output();
        assert!(
            output.contains("Nothing to do") || output.contains("Phase:"),
            "should mention plan or nothing to do, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_dry_run_with_only() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec!["files".to_string()],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        h.assert_header("Plan");
        let output = h.output();
        assert!(
            output.contains("Nothing to do") || output.contains("Phase:"),
            "should mention plan or nothing to do, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_real_with_empty_profile() {
        let h = CliTestHarness::builder()
            .config("apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: empty\n")
            .profile("empty", "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: empty\nspec:\n  inherits: []\n  modules: []\n")
            .build();
        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        h.assert_header("Apply");
        h.assert_output_contains("Nothing to do");

        let state = StateStore::open(&h.state_path().join("cfgd.db")).unwrap();
        assert!(
            state.last_apply().unwrap().is_none(),
            "empty profile apply should not create apply records (nothing to do)"
        );
    }

    #[test]
    fn cmd_status_after_apply() {
        // The harness doesn't support buffer clearing, so use raw setup for multi-step tests
        let (config_dir, state_dir) = setup_test_env();
        let empty_profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: empty\nspec:\n  inherits: []\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("empty.yaml"),
            empty_profile,
        )
        .unwrap();
        std::fs::write(config_dir.path().join("cfgd.yaml"), "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: empty\n").unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&cli, &printer, &args).unwrap();
        buf.lock().unwrap().clear();

        super::cmd_status(&cli, &printer, None).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Status"),
            "should contain Status header, got: {output}"
        );
    }

    #[test]
    fn cmd_log_after_apply() {
        let (config_dir, state_dir) = setup_test_env();
        let empty_profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: empty\nspec:\n  inherits: []\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("empty.yaml"),
            empty_profile,
        )
        .unwrap();
        std::fs::write(config_dir.path().join("cfgd.yaml"), "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: empty\n").unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&cli, &printer, &args).unwrap();
        buf.lock().unwrap().clear();

        super::cmd_log(&printer, 10, None, Some(state_dir.path())).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Apply History"),
            "should contain Apply History header, got: {output}"
        );
        assert!(
            output.contains("No applies recorded yet"),
            "empty profile creates no history records, got: {output}"
        );
    }

    #[test]
    fn cmd_verify_empty_profile() {
        let h = CliTestHarness::builder().build();
        super::cmd_verify(&h.cli(), h.printer(), None).unwrap();
        h.assert_header("Verify");
    }

    #[test]
    fn cmd_diff_empty_profile() {
        let h = CliTestHarness::builder().build();
        super::cmd_diff(&h.cli(), h.printer(), None).unwrap();
        h.assert_header("Diff");
    }

    #[test]
    fn cmd_apply_dry_run_with_files() {
        let (config_dir, state_dir) = setup_test_env();

        // Create a source file
        let files_dir = config_dir.path().join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("test.txt"), "hello world").unwrap();

        let target = config_dir.path().join("output").join("test.txt");

        // Profile with a file
        let profile = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: withfile\nspec:\n  inherits: []\n  modules: []\n  files:\n    managed:\n      - source: files/test.txt\n        target: {}\n",
            target.display()
        );
        std::fs::write(
            config_dir.path().join("profiles").join("withfile.yaml"),
            &profile,
        )
        .unwrap();
        let config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: withfile\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "dry-run apply with files should succeed: {:?}",
            result.err()
        );
        // File should NOT be created (dry-run)
        assert!(!target.exists());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan"),
            "should contain Plan header, got: {output}"
        );
        assert!(
            output.contains("create") || output.contains("test.txt"),
            "should show file plan action, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_creates_file() {
        let (config_dir, state_dir) = setup_test_env();

        let files_dir = config_dir.path().join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("test.txt"), "applied content").unwrap();

        let target = config_dir.path().join("output").join("test.txt");

        let profile = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: withfile\nspec:\n  inherits: []\n  modules: []\n  files:\n    managed:\n      - source: files/test.txt\n        target: {}\n        strategy: Copy\n",
            target.display()
        );
        std::fs::write(
            config_dir.path().join("profiles").join("withfile.yaml"),
            &profile,
        )
        .unwrap();
        let config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: withfile\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();
        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "apply should succeed and create the target file: {:?}",
            result.err()
        );
        // File SHOULD be created
        assert!(target.exists());
        assert_eq!(std::fs::read_to_string(&target).unwrap(), "applied content");
    }

    #[test]
    fn cmd_apply_idempotent() {
        let (config_dir, state_dir) = setup_test_env();

        let files_dir = config_dir.path().join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("test.txt"), "content").unwrap();

        let target = config_dir.path().join("output").join("test.txt");

        let profile = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: withfile\nspec:\n  inherits: []\n  modules: []\n  files:\n    managed:\n      - source: files/test.txt\n        target: {}\n        strategy: Copy\n",
            target.display()
        );
        std::fs::write(
            config_dir.path().join("profiles").join("withfile.yaml"),
            &profile,
        )
        .unwrap();
        let config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: withfile\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        // First apply
        super::cmd_apply(&cli, &printer, &args).unwrap();
        assert!(target.exists());

        // Clear buffer before second apply
        buf.lock().unwrap().clear();

        // Second apply — should succeed with nothing to do
        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "second apply (idempotent) should succeed with nothing to do: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Nothing to do"),
            "second apply should say nothing to do, got: {output}"
        );
    }

    #[test]
    fn cmd_diff_with_files() {
        let (config_dir, state_dir) = setup_test_env();

        let files_dir = config_dir.path().join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("test.txt"), "desired content").unwrap();

        let target_dir = config_dir.path().join("output");
        std::fs::create_dir_all(&target_dir).unwrap();
        let target = target_dir.join("test.txt");
        std::fs::write(&target, "current content").unwrap();

        let profile = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: withfile\nspec:\n  inherits: []\n  modules: []\n  files:\n    managed:\n      - source: files/test.txt\n        target: {}\n        strategy: Copy\n",
            target.display()
        );
        std::fs::write(
            config_dir.path().join("profiles").join("withfile.yaml"),
            &profile,
        )
        .unwrap();
        let config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: withfile\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_diff(&cli, &printer, None);
        assert!(result.is_ok(), "diff failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(output.contains("Diff"), "missing Diff header");
        assert!(
            output.contains("-current content") || output.contains("+desired content"),
            "output should contain diff lines showing content change, got: {output}"
        );
    }

    #[test]
    fn cmd_status_structured_output() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_status(&h.cli(), h.printer(), None).unwrap();
        let parsed = h.json_output();
        assert!(
            parsed.get("lastApply").is_some() || parsed.get("modules").is_some(),
            "JSON should contain status fields, got: {parsed}"
        );
    }

    #[test]
    fn cmd_log_structured_output() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_log(h.printer(), 5, None, Some(h.state_path())).unwrap();
        let parsed = h.json_output();
        assert_eq!(
            parsed,
            serde_json::json!({"entries": []}),
            "fresh state should produce exactly {{entries: []}}"
        );
    }

    #[test]
    fn execute_status_command() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Status { module: None });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Status");
    }

    #[test]
    fn execute_log_command() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Log {
            limit: 10,
            show_output: None,
        });
        super::execute(&cli, h.printer()).unwrap();
        let output = h.output();
        assert!(
            output.contains("Apply History") || output.contains("No applies"),
            "execute Log should show history, got: {output}"
        );
    }

    #[test]
    fn execute_verify_command() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Verify { module: None });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Verify");
    }

    #[test]
    fn execute_diff_command() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Diff { module: None });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Diff");
    }

    #[test]
    fn execute_doctor_command() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Doctor);
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Doctor");
    }

    #[test]
    fn execute_profile_list() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Profile {
            command: ProfileCommand::List,
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_output_contains("default");
    }

    #[test]
    fn execute_profile_show() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Profile {
            command: ProfileCommand::Show { name: None },
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_output_contains("default");
    }

    #[test]
    fn execute_config_show() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Config {
            command: ConfigCommand::Show,
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Configuration");
    }

    #[test]
    fn execute_config_get() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Config {
            command: ConfigCommand::Get {
                key: "profile".to_string(),
            },
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_output_contains("default");
    }

    #[test]
    fn execute_config_set() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Config {
            command: ConfigCommand::Set {
                key: "profile".to_string(),
                value: "work".to_string(),
            },
        });
        super::execute(&cli, h.printer()).unwrap();

        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.profile.as_deref(), Some("work"));
    }

    #[test]
    fn execute_apply_dry_run() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Apply(ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        }));
        super::execute(&cli, h.printer()).unwrap();
        let output = h.output();
        assert!(
            output.contains("Plan") || output.contains("Nothing"),
            "execute Apply dry-run should show plan or nothing-to-do, got: {output}"
        );
    }

    #[test]
    fn execute_completions_bash() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Completions {
                shell: clap_complete::Shell::Bash,
            },
            ..test_cli(dir.path())
        };
        // Completions write directly to stdout via clap_complete, not through Printer.
        // We verify execution succeeds; output content is clap_complete's responsibility.
        let printer = test_printer();
        let result = super::execute(&cli, &printer);
        assert!(
            result.is_ok(),
            "bash completions failed: {:?}",
            result.err()
        );
    }

    #[test]
    fn execute_completions_zsh() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Completions {
                shell: clap_complete::Shell::Zsh,
            },
            ..test_cli(dir.path())
        };
        let printer = test_printer();
        let result = super::execute(&cli, &printer);
        assert!(result.is_ok(), "zsh completions failed: {:?}", result.err());
    }

    #[test]
    fn execute_completions_fish() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Completions {
                shell: clap_complete::Shell::Fish,
            },
            ..test_cli(dir.path())
        };
        let printer = test_printer();
        let result = super::execute(&cli, &printer);
        assert!(
            result.is_ok(),
            "fish completions failed: {:?}",
            result.err()
        );
    }

    #[test]
    fn execute_explain_command() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Explain {
                resource: Some("config".to_string()),
                recursive: false,
            },
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Config") || output.contains("cfgd.yaml"),
            "explain config should describe config resource, got: {output}"
        );
    }

    #[test]
    fn execute_explain_profile() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Explain {
                resource: Some("profile".to_string()),
                recursive: false,
            },
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Profile") || output.contains("profile"),
            "explain profile should describe profile resource, got: {output}"
        );
    }

    #[test]
    fn execute_explain_module() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Explain {
                resource: Some("module".to_string()),
                recursive: false,
            },
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Module") || output.contains("module"),
            "explain module should describe module resource, got: {output}"
        );
    }

    #[test]
    fn execute_explain_no_resource() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Explain {
                resource: None,
                recursive: false,
            },
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Available resource types")
                || output.contains("NAME")
                || output.contains("config"),
            "explain (all resources) should list available resource types, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_with_module_filter() {
        let (config_dir, state_dir) = setup_test_env();

        // Create a module
        create_module_in_dir(
            config_dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages:\n    - name: curl\n",
        );

        // Profile referencing the module
        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  modules:\n    - test-mod\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: Some("test-mod".to_string()),
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(result.is_ok(), "apply failed: {:?}", result.err());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("test-mod") || output.contains("Nothing"),
            "apply with module filter should reference module or show plan, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_with_env_vars() {
        let (config_dir, state_dir) = setup_test_env();

        // Profile with env vars
        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  env:\n    - name: EDITOR\n      value: vim\n    - name: PAGER\n      value: less\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "apply should succeed when profile contains env vars: {:?}",
            result.err()
        );

        {
            let output = buf.lock().unwrap();
            assert!(
                output.contains("Apply"),
                "should contain Apply header, got: {output}"
            );
            assert!(
                output.contains("Phase:") || output.contains("Nothing to do"),
                "should mention plan phases or nothing to do, got: {output}"
            );
        }

        // Verify the profile was loaded with env vars by loading config+profile
        let (_, resolved) = super::load_config_and_profile(&cli, &printer).unwrap();
        assert!(
            resolved.merged.env.iter().any(|e| e.name == "EDITOR"),
            "resolved profile should contain EDITOR env var"
        );
        assert!(
            resolved.merged.env.iter().any(|e| e.name == "PAGER"),
            "resolved profile should contain PAGER env var"
        );
    }

    #[test]
    fn cmd_status_with_modules() {
        let (config_dir, state_dir) = setup_test_env();

        create_module_in_dir(
            config_dir.path(),
            "test-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: test-mod\nspec:\n  packages:\n    - name: curl\n",
        );

        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  modules:\n    - test-mod\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        assert!(
            super::cmd_status(&cli, &printer, None).is_ok(),
            "status should succeed when profile references modules"
        );

        let output = buf.lock().unwrap();
        assert!(output.contains("Status"), "missing Status header");
        assert!(
            output.contains("test-mod"),
            "output should list module test-mod, got: {output}"
        );
    }

    #[test]
    fn cmd_status_with_drift_events() {
        let (config_dir, state_dir) = setup_test_env();

        let empty_profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: empty\nspec:\n  inherits: []\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("empty.yaml"),
            empty_profile,
        )
        .unwrap();
        let empty_config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: empty\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), empty_config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&cli, &printer, &args).unwrap();

        // Record a drift event
        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        state
            .record_drift(
                "package",
                "curl",
                Some("installed"),
                Some("missing"),
                "local",
            )
            .unwrap();

        // Clear buffer before status call
        buf.lock().unwrap().clear();

        super::cmd_status(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("curl"),
            "drift output should mention resource 'curl', got: {output}"
        );
    }

    // --- Source command tests ---

    #[test]
    fn cmd_source_list_no_sources() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        assert!(
            super::cmd_source_list(&cli, &printer).is_ok(),
            "source list should succeed when no sources are configured"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Sources") || output.contains("No sources"),
            "output should show Sources header or no-sources message, got: {output}"
        );
    }

    #[test]
    fn cmd_source_list_no_config() {
        let (_config_dir, state_dir) = setup_test_env();

        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            config: dir.path().join("nonexistent.yaml"),
            ..test_cli_with_state(dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        assert!(
            super::cmd_source_list(&cli, &printer).is_ok(),
            "source list should succeed even without cfgd.yaml"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Sources") || output.contains("No sources"),
            "output should show sources info even without config, got: {output}"
        );
    }

    // --- Decide command tests ---

    #[test]
    fn cmd_decide_accept_all_empty() {
        let (_config_dir, state_dir) = setup_test_env();

        let (printer, buf) = Printer::for_test();

        let result =
            super::cmd_decide(&printer, "accept", None, None, true, Some(state_dir.path()));
        assert!(result.is_ok(), "decide failed: {:?}", result.err());

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        assert!(state.pending_decisions().unwrap().is_empty());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending") || output.contains("0 decision"),
            "should report no pending decisions, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_reject_all_empty() {
        let (_config_dir, state_dir) = setup_test_env();

        let (printer, buf) = Printer::for_test();

        let result =
            super::cmd_decide(&printer, "reject", None, None, true, Some(state_dir.path()));
        assert!(result.is_ok(), "decide failed: {:?}", result.err());

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        assert!(state.pending_decisions().unwrap().is_empty());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending") || output.contains("0 decision"),
            "should report no pending decisions, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_invalid_action() {
        let (_config_dir, state_dir) = setup_test_env();

        let printer = test_printer();

        let result = super::cmd_decide(
            &printer,
            "invalid",
            None,
            None,
            false,
            Some(state_dir.path()),
        );
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown action"));
    }

    #[test]
    fn cmd_decide_accept_specific_resource() {
        let (_config_dir, state_dir) = setup_test_env();

        let (printer, buf) = Printer::for_test();

        let result = super::cmd_decide(
            &printer,
            "accept",
            Some("packages.brew.curl"),
            None,
            false,
            Some(state_dir.path()),
        );
        assert!(result.is_ok(), "decide failed: {:?}", result.err());

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let pending = state.pending_decisions().unwrap();
        assert_eq!(pending.len(), 0, "no decisions should remain pending");

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending decision")
                || output.contains("ACCEPTED")
                || output.contains("reconcile"),
            "decide accept should mention acceptance or no-pending state, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_reject_by_source() {
        let (_config_dir, state_dir) = setup_test_env();

        let (printer, buf) = Printer::for_test();

        let result = super::cmd_decide(
            &printer,
            "reject",
            None,
            Some("acme"),
            false,
            Some(state_dir.path()),
        );
        assert!(
            result.is_ok(),
            "decide reject-by-source should succeed (no-op when nothing pending for source): {:?}",
            result.err()
        );

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let pending = state.pending_decisions().unwrap();
        assert_eq!(
            pending.len(),
            0,
            "no decisions should remain pending after reject"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending decisions")
                || output.contains("REJECTED")
                || output.contains("acme"),
            "decide reject should mention rejection or source name, got: {output}"
        );
    }

    // --- Profile commands via execute ---

    // profile create/delete tested via existing module_create tests above
    #[test]
    fn execute_profile_switch() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Profile {
                command: ProfileCommand::Switch {
                    name: "work".to_string(),
                },
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let printer = test_printer();

        assert!(
            super::execute(&cli, &printer).is_ok(),
            "execute should dispatch Profile Switch command successfully"
        );

        // Verify config updated
        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.profile.as_deref(), Some("work"));
    }

    // --- Module commands via execute ---

    #[test]
    fn execute_module_list() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Module {
                command: ModuleCommand::List,
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Modules") || output.contains("No modules"),
            "module list should show modules header, got: {output}"
        );
    }

    #[test]
    fn execute_workflow_generate() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Workflow {
                command: WorkflowCommand::Generate { force: false },
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("workflow")
                || output.contains("Workflow")
                || output.contains("Generated")
                || output.contains("No profiles"),
            "workflow generate should mention workflow or generation result, got: {output}"
        );
    }

    // --- Sync/Pull without sources (no-op) ---

    #[test]
    fn cmd_sync_no_sources() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_sync(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No sources") || output.contains("Sync"),
            "sync with no sources should report no-sources or show header, got: {output}"
        );
    }

    #[test]
    fn cmd_pull_no_sources() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_pull(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No sources")
                || output.contains("Pull")
                || output.contains("no origin"),
            "pull with no sources should report no-sources, got: {output}"
        );
    }

    // --- Apply with all phases ---

    #[test]
    fn cmd_apply_dry_run_each_phase() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, _buf) = Printer::for_test();

        let all_phases = [
            "pre-scripts",
            "env",
            "modules",
            "packages",
            "system",
            "files",
            "secrets",
            "post-scripts",
        ];
        for phase in &all_phases {
            let args = ApplyArgs {
                from: None,
                dry_run: true,
                phase: Some(phase.to_string()),
                yes: true,
                skip: vec![],
                only: vec![],
                module: None,
                skip_scripts: false,
            };
            let result = super::cmd_apply(&cli, &printer, &args);
            assert!(result.is_ok(), "dry-run failed for phase: {}", phase);
        }
        // Verify all 8 phase names are accepted (no unknown-phase errors)
        assert_eq!(all_phases.len(), 8);
    }

    // --- Verify after real apply ---

    #[test]
    fn cmd_verify_after_apply_with_env() {
        let (config_dir, state_dir) = setup_test_env();

        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  env:\n    - name: EDITOR\n      value: vim\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&cli, &printer, &args).unwrap();

        buf.lock().unwrap().clear();
        super::cmd_verify(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Verify"),
            "verify after apply should show Verify header, got: {output}"
        );
    }

    #[test]
    fn output_format_arg_parse_basic() {
        use super::OutputFormatArg;
        let table: OutputFormatArg = "table".parse().unwrap();
        assert_eq!(table.0, cfgd_core::output::OutputFormat::Table);
        let wide: OutputFormatArg = "wide".parse().unwrap();
        assert_eq!(wide.0, cfgd_core::output::OutputFormat::Wide);
        let json: OutputFormatArg = "json".parse().unwrap();
        assert_eq!(json.0, cfgd_core::output::OutputFormat::Json);
        let yaml: OutputFormatArg = "yaml".parse().unwrap();
        assert_eq!(yaml.0, cfgd_core::output::OutputFormat::Yaml);
        let name: OutputFormatArg = "name".parse().unwrap();
        assert_eq!(name.0, cfgd_core::output::OutputFormat::Name);
    }

    #[test]
    fn output_format_arg_parse_data_carrying() {
        use super::OutputFormatArg;
        let jp: OutputFormatArg = "jsonpath=.items[*].name".parse().unwrap();
        assert_eq!(
            jp.0,
            cfgd_core::output::OutputFormat::Jsonpath(".items[*].name".to_string())
        );
        let tmpl: OutputFormatArg = "template={{ name }}".parse().unwrap();
        assert_eq!(
            tmpl.0,
            cfgd_core::output::OutputFormat::Template("{{ name }}".to_string())
        );
        let tf: OutputFormatArg = "template-file=/tmp/report.tera".parse().unwrap();
        assert_eq!(
            tf.0,
            cfgd_core::output::OutputFormat::TemplateFile(std::path::PathBuf::from(
                "/tmp/report.tera"
            ))
        );
    }

    #[test]
    fn output_format_arg_parse_error() {
        use super::OutputFormatArg;
        let result: Result<OutputFormatArg, _> = "invalid".parse();
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.contains("unknown output format"));
    }

    // --- cmd_plan tests ---

    #[test]
    fn cmd_plan_empty_profile() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan should show Plan header or phase info, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_reconcile_context() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "reconcile".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan with reconcile context should show plan info, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_invalid_context() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "bogus".to_string(),
        };

        let result = super::cmd_plan(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown context"));
    }

    #[test]
    fn cmd_plan_with_phase_filter() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: Some("packages".to_string()),
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Packages"),
            "plan with phase filter should show plan, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_invalid_phase() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();
        let args = PlanArgs {
            phase: Some("nonexistent-phase".to_string()),
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        let result = super::cmd_plan(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown phase"));
    }

    #[test]
    fn cmd_plan_with_skip_filter() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec!["packages".to_string()],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan with skip filter should show plan, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_with_only_filter() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec!["files".to_string()],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan with only filter should show plan, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_with_skip_scripts() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: true,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan with skip-scripts should show plan, got: {output}"
        );
    }

    #[test]
    fn cmd_plan_with_module_filter() {
        let (config_dir, state_dir) = setup_test_env();

        create_module_in_dir(
            config_dir.path(),
            "plan-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: plan-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: Some("plan-mod".to_string()),
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("plan-mod"),
            "plan with module filter should reference module, got: {output}"
        );
    }

    // --- cmd_rollback tests ---

    #[test]
    fn cmd_rollback_invalid_id_empty_state() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        let result = super::cmd_rollback(&printer, 9999, true, Some(state_dir.path()));
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("no apply found"));
    }

    #[test]
    fn cmd_rollback_after_file_apply() {
        let (config_dir, state_dir) = setup_test_env();

        let files_dir = config_dir.path().join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("rollback-test.txt"), "rollback content").unwrap();

        let target = config_dir.path().join("output").join("rollback-test.txt");

        let profile = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: withfile\nspec:\n  inherits: []\n  modules: []\n  files:\n    managed:\n      - source: files/rollback-test.txt\n        target: {}\n        strategy: Copy\n",
            target.display()
        );
        std::fs::write(
            config_dir.path().join("profiles").join("withfile.yaml"),
            &profile,
        )
        .unwrap();
        let config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: withfile\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        // Apply to create the file
        let args = ApplyArgs {
            from: None,
            dry_run: false,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };
        super::cmd_apply(&cli, &printer, &args).unwrap();
        assert!(target.exists());

        // Get the apply ID from history
        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let history = state.history(1).unwrap();
        assert!(
            !history.is_empty(),
            "apply should have created a history entry"
        );
        let apply_id = history[0].id;

        // Clear buffer before rollback
        buf.lock().unwrap().clear();

        // Rollback
        let result = super::cmd_rollback(&printer, apply_id, true, Some(state_dir.path()));
        assert!(
            result.is_ok(),
            "rollback should succeed for valid apply ID: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Rollback") || output.contains("restore"),
            "rollback output should mention rollback or restoration, got: {output}"
        );
    }

    // --- cmd_compliance tests ---

    #[test]
    fn cmd_compliance_snapshot_basic() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_compliance_snapshot(&cli, &printer);
        assert!(
            result.is_ok(),
            "compliance snapshot should succeed: {:?}",
            result.err()
        );

        // Verify snapshot was recorded in state store
        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let entries = state.compliance_history(None, 10).unwrap();
        assert!(
            !entries.is_empty(),
            "compliance snapshot should create a history entry in state store"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("Snapshot"),
            "compliance snapshot should mention compliance or snapshot, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_export_basic() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_compliance_export(&cli, &printer);
        assert!(
            result.is_ok(),
            "compliance export failed: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("compliance") || !output.is_empty(),
            "compliance export should produce output, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_history_empty() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_compliance_history(&cli, &printer, None);
        assert!(
            result.is_ok(),
            "compliance history should succeed with no snapshots: {:?}",
            result.err()
        );

        // Verify state store has no compliance entries
        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let entries = state.compliance_history(None, 10).unwrap();
        assert_eq!(
            entries.len(),
            0,
            "compliance history should be empty when no snapshots have been taken"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No compliance snapshots")
                || output.contains("History")
                || output.contains("Compliance"),
            "compliance history should mention no snapshots or show header, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_history_with_since() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_compliance_history(&cli, &printer, Some("7d"));
        assert!(
            result.is_ok(),
            "compliance history should succeed with --since 7d time filter: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("History") || output.contains("No"),
            "compliance history with --since should produce output, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_history_invalid_since() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_compliance_history(&cli, &printer, Some("invalid-duration"));
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("invalid --since value"),
            "expected 'invalid --since value' error, got: {msg}"
        );
    }

    #[test]
    fn cmd_compliance_diff_missing_snapshots() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_compliance_diff(&cli, &printer, 1, 2);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_compliance_diff_after_two_snapshots() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        // Create two snapshots
        super::cmd_compliance_snapshot(&cli, &printer).unwrap();
        super::cmd_compliance_snapshot(&cli, &printer).unwrap();

        // Get snapshot IDs from history — must have exactly 2
        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let entries = state.compliance_history(None, 10).unwrap();
        assert_eq!(
            entries.len(),
            2,
            "two snapshots should create two history entries"
        );
        let result = super::cmd_compliance_diff(&cli, &printer, entries[1].id, entries[0].id);
        assert!(
            result.is_ok(),
            "compliance diff should succeed when comparing two valid snapshots: {:?}",
            result.err()
        );
    }

    #[test]
    fn cmd_compliance_history_after_snapshot() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        // Create a snapshot first
        super::cmd_compliance_snapshot(&cli, &printer).unwrap();

        // Clear buffer before checking history
        buf.lock().unwrap().clear();

        // History should show at least one entry
        let result = super::cmd_compliance_history(&cli, &printer, None);
        assert!(
            result.is_ok(),
            "compliance history should succeed after a snapshot was taken: {:?}",
            result.err()
        );

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        let entries = state.compliance_history(None, 10).unwrap();
        assert_eq!(
            entries.len(),
            1,
            "should have exactly 1 compliance history entry after one snapshot"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("History"),
            "compliance history should display history header, got: {output}"
        );
    }

    // --- empty_resolved_profile tests ---

    #[test]
    fn empty_resolved_profile_contains_module_name() {
        let resolved = super::empty_resolved_profile("my-module");
        assert_eq!(resolved.merged.modules, vec!["my-module".to_string()]);
        assert!(resolved.layers.is_empty());
        assert!(resolved.merged.packages.brew.is_none());
        assert!(resolved.merged.env.is_empty());
        assert!(resolved.merged.secrets.is_empty());
    }

    // --- cmd_log with show_output ---

    #[test]
    fn cmd_log_show_output_nonexistent_apply() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        // show_output for a nonexistent apply ID should fail
        let result = super::cmd_log(&printer, 10, Some(9999), Some(state_dir.path()));
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("no apply found"));
    }

    // --- cmd_apply with skip_scripts ---

    #[test]
    fn cmd_apply_dry_run_with_skip_scripts() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: true,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "dry-run apply should succeed with --skip-scripts flag: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Apply")
                || output.contains("Plan")
                || output.contains("Nothing")
                || output.contains("dry"),
            "dry-run apply with skip-scripts should produce output, got: {output}"
        );
    }

    // --- execute dispatch tests for new commands ---

    #[test]
    fn execute_plan_command() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Plan(PlanArgs {
                phase: None,
                skip: vec![],
                only: vec![],
                module: None,
                skip_scripts: false,
                context: "apply".to_string(),
            }),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "execute Plan should show plan info, got: {output}"
        );
    }

    #[test]
    fn execute_compliance_snapshot() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Compliance { command: None },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("snapshot"),
            "execute Compliance should show compliance info, got: {output}"
        );
    }

    #[test]
    fn execute_compliance_export() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Compliance {
                command: Some(ComplianceCommand::Export),
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance")
                || output.contains("compliance")
                || output.contains("Snapshot"),
            "compliance export should contain compliance data, got: {output}"
        );
    }

    #[test]
    fn execute_compliance_history() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Compliance {
                command: Some(ComplianceCommand::History { since: None }),
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("History") || output.contains("No"),
            "compliance history should show info, got: {output}"
        );
    }

    #[test]
    fn execute_rollback_invalid() {
        let state_dir = tempfile::tempdir().unwrap();
        let dir = tempfile::tempdir().unwrap();

        let cli = Cli {
            command: Command::Rollback {
                apply_id: 9999,
                yes: true,
            },
            ..test_cli_with_state(dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let printer = test_printer();

        let result = super::execute(&cli, &printer);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("no apply found with ID 9999"),
            "expected 'no apply found with ID 9999' error, got: {msg}"
        );
    }

    // --- secret_backend_from_config with config ---

    #[test]
    fn secret_backend_from_config_with_backend() {
        let yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  secrets:
    backend: sops-age
"#;
        let cfg = config::parse_config(yaml, std::path::Path::new("cfgd.yaml")).unwrap();
        let (backend, _) = super::secret_backend_from_config(Some(&cfg));
        assert_eq!(backend, "sops-age");
    }

    // --- known_manager_names ---

    #[test]
    fn known_manager_names_is_not_empty() {
        let names = super::known_manager_names();
        assert!(!names.is_empty());
        // Should at least contain "cargo" which is always available in Rust projects
        assert!(names.contains(&"cargo".to_string()));
    }

    // --- Structured output mode tests ---

    #[test]
    fn cmd_plan_structured_json() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("context").is_some(),
            "plan JSON should have 'context'"
        );
        assert_eq!(parsed["context"], "apply", "plan context should be 'apply'");
        assert!(
            parsed.get("phases").is_some(),
            "plan JSON should have 'phases'"
        );
        assert!(parsed["phases"].is_array(), "phases should be an array");
        assert!(
            parsed.get("totalActions").is_some(),
            "plan JSON should have 'totalActions'"
        );
        assert!(
            parsed["totalActions"].is_u64(),
            "totalActions should be a numeric value"
        );
    }

    #[test]
    fn cmd_verify_structured_json() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_verify(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("results").is_some(),
            "verify JSON should have 'results'"
        );
        assert!(
            parsed.get("passCount").is_some(),
            "verify JSON should have 'passCount'"
        );
        assert!(
            parsed.get("failCount").is_some(),
            "verify JSON should have 'failCount'"
        );
        let results = parsed["results"].as_array().unwrap();
        let pass_count = parsed["passCount"].as_u64().unwrap();
        let fail_count = parsed["failCount"].as_u64().unwrap();
        assert_eq!(
            pass_count + fail_count,
            results.len() as u64,
            "passCount + failCount should equal results array length"
        );
    }

    #[test]
    fn cmd_doctor_structured_json() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_doctor(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert!(
            parsed.get("config").is_some(),
            "doctor JSON should have 'config' field, got: {parsed}"
        );
        assert_eq!(
            parsed["config"]["valid"], true,
            "config should be valid in doctor output"
        );
        assert!(
            parsed.get("git").is_some(),
            "doctor JSON should have 'git' field"
        );
        assert!(
            parsed.get("packageManagers").is_some(),
            "doctor JSON should have 'packageManagers' field"
        );
        assert!(
            parsed["packageManagers"].is_array(),
            "packageManagers should be an array"
        );
    }

    #[test]
    fn cmd_compliance_snapshot_structured_json() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_compliance_snapshot(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("snapshot").is_some(),
            "compliance snapshot JSON should have 'snapshot' field, got: {parsed}"
        );
        let snapshot = &parsed["snapshot"];
        assert!(
            snapshot["timestamp"].is_string(),
            "snapshot.timestamp should be a string"
        );
        assert_eq!(
            snapshot["profile"], "default",
            "snapshot.profile should be 'default'"
        );
        assert!(
            snapshot["checks"].is_array(),
            "snapshot.checks should be an array"
        );
        assert!(
            snapshot["summary"].is_object(),
            "snapshot.summary should be an object"
        );
    }

    #[test]
    fn cmd_compliance_history_structured_json() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_compliance_history(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(
            parsed,
            serde_json::json!({"entries": []}),
            "fresh state should produce exactly {{entries: []}}"
        );
    }

    // --- cmd_diff with module filter ---

    #[test]
    fn cmd_diff_with_module_filter() {
        let (config_dir, state_dir) = setup_test_env();

        create_module_in_dir(
            config_dir.path(),
            "diff-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: diff-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_diff(&cli, &printer, Some("diff-mod"));
        assert!(
            result.is_ok(),
            "diff should succeed when filtering to a specific module: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Diff") || output.contains("diff-mod"),
            "diff with module filter should mention the module, got: {output}"
        );
    }

    // --- cmd_verify with module filter on nonexistent module ---

    #[test]
    fn cmd_verify_module_not_found() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        // Nonexistent module should succeed gracefully (empty results, exit 0)
        let result = super::cmd_verify(&cli, &printer, Some("nonexistent"));
        assert!(
            result.is_ok(),
            "verify should handle nonexistent module gracefully: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Verify") || output.contains("No managed"),
            "verify for nonexistent module should mention verify or no managed resources, got: {output}"
        );
    }

    // --- cmd_plan with module that has dependencies ---

    #[test]
    fn cmd_plan_module_with_packages() {
        let (config_dir, state_dir) = setup_test_env();

        create_module_in_dir(
            config_dir.path(),
            "pkg-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: pkg-mod\nspec:\n  packages:\n    - name: curl\n    - name: wget\n",
        );

        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  modules:\n    - pkg-mod\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        let result = super::cmd_plan(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "plan should succeed when module contains packages: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan")
                || output.contains("Phase")
                || output.contains("Package")
                || output.contains("curl"),
            "plan output should contain plan info or package actions, got: {output}"
        );
    }

    // --- open_state_store ---

    #[test]
    fn open_state_store_creates_dir() {
        let dir = tempfile::tempdir().unwrap();
        let subdir = dir.path().join("nested").join("state");
        let result = super::open_state_store(Some(&subdir));
        assert!(
            result.is_ok(),
            "open_state_store should create nested directories: {:?}",
            result.err()
        );
        assert!(subdir.exists(), "nested state directory should be created");
        assert!(
            subdir.join("cfgd.db").exists(),
            "DB file should exist in the created directory"
        );
    }

    #[test]
    #[serial_test::serial(default_state_store)]
    fn open_state_store_default() {
        // Verify the default path variant does not panic and creates a DB.
        // Serialized against other tests that touch the default DB path so
        // parallel SQLite access doesn't trigger 'database is locked'.
        let result = super::open_state_store(None);
        assert!(
            result.is_ok(),
            "open_state_store with default path should not panic: {:?}",
            result.err()
        );
        // Verify we can actually use the store
        let state = result.unwrap();
        assert!(
            state.history(1).is_ok(),
            "state store should be functional after opening"
        );
    }

    // --- build_registry ---

    #[test]
    fn build_registry_has_package_managers() {
        let registry = super::build_registry();
        assert_eq!(
            registry.package_managers.len(),
            20,
            "registry should have all 20 package managers"
        );
        let names: Vec<&str> = registry.package_managers.iter().map(|m| m.name()).collect();
        assert!(names.contains(&"brew"), "should include brew");
        assert!(names.contains(&"cargo"), "should include cargo");
        assert!(names.contains(&"apt"), "should include apt");
        assert!(names.contains(&"npm"), "should include npm");
    }

    #[test]
    fn build_registry_has_system_configurators() {
        let registry = super::build_registry();
        // On Linux we get: shell, systemd, gsettings, kdeConfig, xfconf, environment, sshKeys,
        // plus conditionally gpg and git (both available in CI/dev). At minimum we should have
        // the unconditional ones.
        assert!(
            registry.system_configurators.len() >= 6,
            "registry should have at least 6 system configurators on Linux, got: {}",
            registry.system_configurators.len()
        );
        let names: Vec<&str> = registry
            .system_configurators
            .iter()
            .map(|c| c.name())
            .collect();
        assert!(
            names.contains(&"shell"),
            "should include shell configurator"
        );
        assert!(
            names.contains(&"sshKeys"),
            "should include sshKeys configurator"
        );
        assert!(
            names.contains(&"environment"),
            "should include environment configurator"
        );
    }

    #[test]
    fn build_registry_has_secret_backend() {
        let registry = super::build_registry();
        assert!(
            registry.secret_backend.is_some(),
            "registry should have a secret backend"
        );
        assert_eq!(
            registry.secret_backend.as_ref().unwrap().name(),
            "sops",
            "default secret backend should be 'sops'"
        );
    }

    // --- config_dir helper ---

    #[test]
    fn config_dir_derives_from_cli_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let result = super::config_dir(&cli);
        assert_eq!(result, dir.path().to_path_buf());
    }

    // --- profiles_dir helper ---

    #[test]
    fn profiles_dir_derives_from_cli() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let result = super::profiles_dir(&cli);
        assert_eq!(result, dir.path().join("profiles"));
    }

    // =========================================================================
    // Module handler tests
    // =========================================================================

    // --- cmd_module_list ---

    #[test]
    fn module_list_empty_config_dir() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join("modules")).unwrap();
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Modules") || output.contains("No modules"),
            "module list with empty dir should show header or no-modules, got: {output}"
        );
    }

    #[test]
    fn module_list_with_modules() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "vim",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: vim\nspec:\n  packages:\n    - name: vim\n",
        );
        create_module_in_dir(
            dir.path(),
            "git",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: git\nspec:\n  packages:\n    - name: git\n    - name: git-lfs\n",
        );

        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(output.contains("vim"), "module list should contain 'vim'");
        assert!(output.contains("git"), "module list should contain 'git'");
    }

    #[test]
    fn module_list_no_modules_dir() {
        let dir = tempfile::tempdir().unwrap();
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No modules") || output.contains("Modules"),
            "module list without modules dir should show header or no-modules, got: {output}"
        );
    }

    #[test]
    fn module_list_with_config_and_profile() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();
        create_module_in_dir(
            dir.path(),
            "bat",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: bat\nspec:\n  packages:\n    - name: bat\n",
        );
        let profile_path = dir.path().join("profiles").join("default.yaml");
        let mut doc = config::load_profile(&profile_path).unwrap();
        doc.spec.modules.push("bat".to_string());
        let yaml = serde_yaml::to_string(&doc).unwrap();
        std::fs::write(&profile_path, &yaml).unwrap();

        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(output.contains("bat"), "module list should contain 'bat'");
    }

    // --- cmd_module_show ---

    #[test]
    fn module_show_not_found() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join("modules")).unwrap();
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let printer = test_printer();

        let result = module::cmd_module_show(&cli, &printer, "nonexistent", false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn module_show_with_packages_and_files() {
        let dir = tempfile::tempdir().unwrap();
        let mod_dir = dir.path().join("modules").join("dev-tools");
        std::fs::create_dir_all(mod_dir.join("files")).unwrap();
        std::fs::write(mod_dir.join("files").join("config.toml"), "content").unwrap();
        std::fs::write(
            mod_dir.join("module.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: dev-tools
  description: Development tools
spec:
  depends:
    - base
  packages:
    - name: ripgrep
    - name: fd
      prefer:
        - cargo
  files:
    - source: files/config.toml
      target: ~/.config/tool/config.toml
  env:
    - name: EDITOR
      value: nvim
  aliases:
    - name: ll
      command: ls -la
"#,
        )
        .unwrap();

        // Verify module was parsed correctly before testing show
        let (doc, _) = module::load_module_document(dir.path(), "dev-tools").unwrap();
        assert_eq!(doc.metadata.name, "dev-tools");
        assert_eq!(
            doc.metadata.description,
            Some("Development tools".to_string())
        );
        assert_eq!(doc.spec.depends, vec!["base"]);
        assert_eq!(doc.spec.packages.len(), 2);
        assert_eq!(doc.spec.packages[0].name, "ripgrep");
        assert_eq!(doc.spec.packages[1].name, "fd");
        assert_eq!(doc.spec.files.len(), 1);
        assert_eq!(doc.spec.env.len(), 1);
        assert_eq!(doc.spec.env[0].name, "EDITOR");
        assert_eq!(doc.spec.aliases.len(), 1);
        assert_eq!(doc.spec.aliases[0].name, "ll");

        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_show(&cli, &printer, "dev-tools", false).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("dev-tools"),
            "show should contain module name"
        );
        assert!(
            output.contains("ripgrep"),
            "show should list package ripgrep"
        );
        assert!(
            output.contains("config.toml"),
            "show should list file config.toml"
        );
        assert!(output.contains("EDITOR"), "show should list env var EDITOR");
    }

    #[test]
    fn module_show_with_values_flag() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "secrets-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: secrets-mod
spec:
  env:
    - name: API_KEY
      value: super-secret-token-123
"#,
        );

        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_show(&cli, &printer, "secrets-mod", false).unwrap();
        {
            let output = buf.lock().unwrap();
            assert!(output.contains("API_KEY"), "show should list env var name");
        }

        // With show_values=true
        buf.lock().unwrap().clear();
        module::cmd_module_show(&cli, &printer, "secrets-mod", true).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("API_KEY"),
            "show with values should list env"
        );
    }

    #[test]
    fn module_show_suggests_available_modules() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "vim",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: vim\nspec: {}\n",
        );
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let printer = test_printer();

        let result = module::cmd_module_show(&cli, &printer, "emacs", false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn module_show_with_scripts() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "scripted",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: scripted
spec:
  packages:
    - name: curl
  scripts:
    postApply:
      - echo "done"
"#,
        );

        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test();

        module::cmd_module_show(&cli, &printer, "scripted", false).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("scripted"),
            "show should contain module name"
        );
        assert!(output.contains("curl"), "show should list package curl");
        assert!(
            output.contains("Scripts") || output.contains("postApply"),
            "show should mention scripts, got: {output}"
        );
    }

    // --- cmd_module_create ---

    #[test]
    fn module_create_minimal() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            description: Some("Minimal module".to_string()),
            ..test_module_create_args("minimal")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        let module_yaml = dir
            .path()
            .join("modules")
            .join("minimal")
            .join("module.yaml");
        assert!(module_yaml.exists());
        let (doc, _) = module::load_module_document(dir.path(), "minimal").unwrap();
        assert_eq!(doc.metadata.name, "minimal");
        assert_eq!(doc.metadata.description, Some("Minimal module".to_string()));
        assert!(doc.spec.packages.is_empty());
        assert!(doc.spec.files.is_empty());
        assert!(doc.spec.depends.is_empty());
    }

    #[test]
    fn module_create_with_env_and_aliases() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            description: Some("Env module".to_string()),
            env: vec!["EDITOR=nvim".to_string(), "PAGER=less".to_string()],
            aliases: vec!["ll=ls -la".to_string(), "gs=git status".to_string()],
            ..test_module_create_args("env-mod")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "env-mod").unwrap();
        assert_eq!(doc.spec.env.len(), 2);
        assert_eq!(doc.spec.env[0].name, "EDITOR");
        assert_eq!(doc.spec.env[0].value, "nvim");
        assert_eq!(doc.spec.env[1].name, "PAGER");
        assert_eq!(doc.spec.env[1].value, "less");
        assert_eq!(doc.spec.aliases.len(), 2);
        assert_eq!(doc.spec.aliases[0].name, "ll");
        assert_eq!(doc.spec.aliases[0].command, "ls -la");
    }

    #[test]
    fn module_create_with_depends() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            depends: vec!["base".to_string(), "core".to_string()],
            ..test_module_create_args("dep-mod")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "dep-mod").unwrap();
        assert_eq!(doc.spec.depends, vec!["base", "core"]);
    }

    #[test]
    fn module_create_with_post_apply_normalizes_escapes() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            post_apply: vec!["echo \\!done".to_string()],
            ..test_module_create_args("script-mod")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "script-mod").unwrap();
        let scripts = doc.spec.scripts.unwrap();
        assert_eq!(scripts.post_apply.len(), 1);
        // \! should be normalized to !
        assert_eq!(scripts.post_apply[0].run_str(), "echo !done");
    }

    #[test]
    fn module_create_rejects_invalid_name() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = test_module_create_args(".bad-name");
        let result = module::cmd_module_create(&cli, &printer, &args);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("cannot start with '.' or '-'"),
            "expected name validation error, got: {msg}"
        );
    }

    #[test]
    fn module_create_with_duplicate_file_basenames_fails() {
        let dir = tempfile::tempdir().unwrap();
        // Create two files with same basename in different directories
        let dir_a = dir.path().join("dir_a");
        let dir_b = dir.path().join("dir_b");
        std::fs::create_dir_all(&dir_a).unwrap();
        std::fs::create_dir_all(&dir_b).unwrap();
        std::fs::write(dir_a.join("config.toml"), "a").unwrap();
        std::fs::write(dir_b.join("config.toml"), "b").unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            files: vec![
                format!("{}:~/.config/a", dir_a.join("config.toml").display()),
                format!("{}:~/.config/b", dir_b.join("config.toml").display()),
            ],
            ..test_module_create_args("dup-files")
        };
        let result = module::cmd_module_create(&cli, &printer, &args);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("Duplicate file basename")
        );
    }

    // --- cmd_module_update_local ---

    #[test]
    fn module_update_add_and_remove_env() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "env-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: env-mod
spec:
  env:
    - name: EDITOR
      value: vim
    - name: PAGER
      value: less
"#,
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            env: vec!["TERM=xterm".to_string(), "-PAGER".to_string()],
            ..empty_module_update_args("env-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "env-mod").unwrap();
        let names: Vec<&str> = doc.spec.env.iter().map(|e| e.name.as_str()).collect();
        assert!(names.contains(&"EDITOR"));
        assert!(names.contains(&"TERM"));
        assert!(!names.contains(&"PAGER"));
    }

    #[test]
    fn module_update_add_and_remove_aliases() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "alias-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: alias-mod
spec:
  aliases:
    - name: ll
      command: ls -la
    - name: gs
      command: git status
"#,
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            aliases: vec!["gd=git diff".to_string(), "-gs".to_string()],
            ..empty_module_update_args("alias-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "alias-mod").unwrap();
        let names: Vec<&str> = doc.spec.aliases.iter().map(|a| a.name.as_str()).collect();
        assert!(names.contains(&"ll"));
        assert!(names.contains(&"gd"));
        assert!(!names.contains(&"gs"));
    }

    #[test]
    fn module_update_add_and_remove_depends() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "dep-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: dep-mod
spec:
  depends:
    - base
    - core
"#,
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            depends: vec!["tools".to_string(), "-core".to_string()],
            ..empty_module_update_args("dep-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "dep-mod").unwrap();
        assert!(doc.spec.depends.contains(&"base".to_string()));
        assert!(doc.spec.depends.contains(&"tools".to_string()));
        assert!(!doc.spec.depends.contains(&"core".to_string()));
    }

    #[test]
    fn module_update_add_and_remove_post_apply() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "script-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: script-mod
spec:
  scripts:
    postApply:
      - echo setup
      - echo cleanup
"#,
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            post_apply: vec!["echo new-step".to_string(), "-echo cleanup".to_string()],
            ..empty_module_update_args("script-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "script-mod").unwrap();
        let scripts = doc.spec.scripts.unwrap();
        let script_strs: Vec<&str> = scripts.post_apply.iter().map(|s| s.run_str()).collect();
        assert!(script_strs.contains(&"echo setup"));
        assert!(script_strs.contains(&"echo new-step"));
        assert!(!script_strs.contains(&"echo cleanup"));
    }

    #[test]
    fn module_update_description() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "desc-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: desc-mod\nspec: {}\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            description: Some("New description".to_string()),
            ..empty_module_update_args("desc-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "desc-mod").unwrap();
        assert_eq!(
            doc.metadata.description,
            Some("New description".to_string())
        );
    }

    #[test]
    fn module_update_clear_description() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "desc-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: desc-mod\n  description: Old desc\nspec: {}\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            description: Some(String::new()),
            ..empty_module_update_args("desc-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "desc-mod").unwrap();
        assert_eq!(doc.metadata.description, None);
    }

    #[test]
    fn module_update_no_changes() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "noop-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: noop-mod\nspec: {}\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // No flags at all — should print "no changes" and succeed
        let args = empty_module_update_args("noop-mod");
        let result = module::cmd_module_update_local(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "module update with no flags should succeed (no-op): {:?}",
            result.err()
        );

        // Verify module YAML is unchanged
        let (doc, _) = module::load_module_document(dir.path(), "noop-mod").unwrap();
        assert_eq!(doc.metadata.name, "noop-mod");
        assert!(doc.spec.packages.is_empty());
    }

    #[test]
    fn module_update_nonexistent_module_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = empty_module_update_args("nonexistent");
        let result = module::cmd_module_update_local(&cli, &printer, &args);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("Module 'nonexistent' not found"),
            "expected module not found error, got: {msg}"
        );
    }

    #[test]
    fn module_update_add_files() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "file-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: file-mod\nspec: {}\n",
        );

        // Create a file to import
        let source_file = dir.path().join("my-config.toml");
        std::fs::write(&source_file, "key = \"value\"").unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            files: vec![format!(
                "{}:~/.config/app/config.toml",
                source_file.display()
            )],
            ..empty_module_update_args("file-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "file-mod").unwrap();
        assert_eq!(doc.spec.files.len(), 1);
        assert!(doc.spec.files[0].source.contains("my-config.toml"));
    }

    #[test]
    fn module_update_remove_files() {
        let dir = tempfile::tempdir().unwrap();
        let target_path = "/tmp/cfgd-test-file-target";
        let module_yaml = format!(
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: rm-file-mod
spec:
  files:
    - source: files/config.toml
      target: {}
"#,
            target_path
        );
        create_module_in_dir(dir.path(), "rm-file-mod", &module_yaml);
        // Create the source file in the module
        std::fs::write(
            dir.path()
                .join("modules")
                .join("rm-file-mod")
                .join("files")
                .join("config.toml"),
            "content",
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleUpdateArgs {
            files: vec![format!("-{}", target_path)],
            ..empty_module_update_args("rm-file-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "rm-file-mod").unwrap();
        assert_eq!(
            doc.spec.files.len(),
            0,
            "module spec should have no files after removal"
        );
        // Source file should also be removed
        assert!(
            !dir.path()
                .join("modules")
                .join("rm-file-mod")
                .join("files")
                .join("config.toml")
                .exists()
        );
    }

    #[test]
    fn module_update_remove_nonexistent_warns() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "warn-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: warn-mod\nspec:\n  packages:\n    - name: curl\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Try removing items that don't exist — should still succeed (just warns)
        let args = ModuleUpdateArgs {
            packages: vec!["-nonexistent".to_string()],
            env: vec!["-MISSING".to_string()],
            aliases: vec!["-gone".to_string()],
            depends: vec!["-nodep".to_string()],
            ..empty_module_update_args("warn-mod")
        };
        let result = module::cmd_module_update_local(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "module update should succeed when removing nonexistent items (warns only): {:?}",
            result.err()
        );

        // Verify original package is still present (removals of non-existent items are no-ops)
        let (doc, _) = module::load_module_document(dir.path(), "warn-mod").unwrap();
        let names: Vec<&str> = doc.spec.packages.iter().map(|p| p.name.as_str()).collect();
        assert!(
            names.contains(&"curl"),
            "original package should still be present"
        );
    }

    // --- cmd_module_delete ---

    #[test]
    fn module_delete_nonexistent() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_delete(&cli, &printer, "nonexistent", true, false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn module_delete_cleans_lockfile() {
        let dir = create_test_config_dir();
        create_module_in_dir(
            dir.path(),
            "remote-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: remote-mod\nspec: {}\n",
        );

        // Create a lockfile with an entry for this module
        let lockfile_content = r#"apiVersion: cfgd.io/v1alpha1
kind: ModuleLock
modules:
  - name: remote-mod
    url: https://github.com/example/mod.git@v1.0
    pinnedRef: v1.0
    commit: abc123
    integrity: sha256-fake
"#;
        std::fs::write(dir.path().join("modules.lock"), lockfile_content).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_delete(&cli, &printer, "remote-mod", true, false).unwrap();

        // Module directory should be gone
        assert!(
            !dir.path().join("modules").join("remote-mod").exists(),
            "module directory should be removed after delete"
        );

        // Lockfile should no longer contain the module
        let lockfile = cfgd_core::modules::load_lockfile(dir.path()).unwrap();
        assert_eq!(
            lockfile.modules.len(),
            0,
            "lockfile should have no module entries after delete"
        );
    }

    #[test]
    #[cfg(unix)]
    fn module_delete_restores_symlinked_files() {
        let dir = create_test_config_dir();

        // Create a target that's a symlink into the module dir
        let target_dir = dir.path().join("targets");
        std::fs::create_dir_all(&target_dir).unwrap();

        let module_dir = dir.path().join("modules").join("link-mod");
        let files_dir = module_dir.join("files");
        std::fs::create_dir_all(&files_dir).unwrap();
        std::fs::write(files_dir.join("config.txt"), "module content").unwrap();

        let target_file = target_dir.join("config.txt");
        // Create symlink from target -> module file
        std::os::unix::fs::symlink(files_dir.join("config.txt"), &target_file).unwrap();

        let module_yaml = format!(
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: link-mod\nspec:\n  files:\n    - source: files/config.txt\n      target: {}\n",
            target_file.display()
        );
        std::fs::write(module_dir.join("module.yaml"), &module_yaml).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_delete(&cli, &printer, "link-mod", true, false).unwrap();

        // Module dir gone
        assert!(!module_dir.exists());
        // Target should have been restored as a regular file
        assert!(target_file.exists());
        assert!(!target_file.is_symlink());
        assert_eq!(
            std::fs::read_to_string(&target_file).unwrap(),
            "module content"
        );
    }

    // --- cmd_module_export ---

    #[test]
    fn module_export_devcontainer_via_wrapper() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "export-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: export-mod
spec:
  packages:
    - name: jq
"#,
        );

        let output = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_export(
            &cli,
            &printer,
            "export-mod",
            &ExportFormat::Devcontainer,
            Some(output.path().to_str().unwrap()),
        );
        assert!(
            result.is_ok(),
            "module export devcontainer should succeed: {:?}",
            result.err()
        );

        let install_sh = output.path().join("export-mod").join("install.sh");
        assert!(install_sh.exists(), "install.sh should be created");
        let install_content = std::fs::read_to_string(&install_sh).unwrap();
        assert!(
            !install_content.is_empty(),
            "install.sh should have content"
        );

        let feature_json = output
            .path()
            .join("export-mod")
            .join("devcontainer-feature.json");
        assert!(
            feature_json.exists(),
            "devcontainer-feature.json should be created"
        );
        let feature_content = std::fs::read_to_string(&feature_json).unwrap();
        assert!(
            feature_content.contains("export-mod"),
            "devcontainer-feature.json should reference the module name"
        );
    }

    #[test]
    fn module_export_nonexistent_module() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join("modules")).unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_export(
            &cli,
            &printer,
            "nonexistent",
            &ExportFormat::Devcontainer,
            None,
        );
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("Module 'nonexistent' not found"),
            "expected module not found error, got: {msg}"
        );
    }

    #[test]
    fn module_export_devcontainer_with_env_and_depends() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "full-mod",
            r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: full-mod
  description: A full module
spec:
  depends:
    - base
  packages:
    - name: curl
    - name: custom-tool
      script: curl -sL https://example.com/install.sh | sh
  env:
    - name: EDITOR
      value: vim
  scripts:
    postApply:
      - echo setup complete
"#,
        );

        let output = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_export(
            &cli,
            &printer,
            "full-mod",
            &ExportFormat::Devcontainer,
            Some(output.path().to_str().unwrap()),
        )
        .unwrap();

        let install =
            std::fs::read_to_string(output.path().join("full-mod").join("install.sh")).unwrap();
        assert!(install.contains("curl"));
        assert!(install.contains("EDITOR"));
        assert!(install.contains("setup complete"));
        assert!(install.contains("curl -sL https://example.com/install.sh | sh"));

        let feature_json = std::fs::read_to_string(
            output
                .path()
                .join("full-mod")
                .join("devcontainer-feature.json"),
        )
        .unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&feature_json).unwrap();
        assert_eq!(parsed["description"], "A full module");
        let installs_after = parsed["installsAfter"].as_array().unwrap();
        assert_eq!(installs_after.len(), 1);
        assert!(installs_after[0].as_str().unwrap().contains("base"));
    }

    // --- cmd_module_registry_list ---

    #[test]
    fn module_registry_list_no_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        module::cmd_module_registry_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No registries") || output.contains("Registries"),
            "registry list without config should show message, got: {output}"
        );
    }

    #[test]
    fn module_registry_list_empty_registries() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        module::cmd_module_registry_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Registries") || output.contains("No registries"),
            "registry list with none should report no registries, got: {output}"
        );
    }

    #[test]
    fn module_registry_list_with_registries() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-modules/community.git
      - name: private
        url: git@github.com:my-org/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        module::cmd_module_registry_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("community"),
            "registry list should contain 'community'"
        );
        assert!(
            output.contains("private"),
            "registry list should contain 'private'"
        );
    }

    // --- cmd_module_registry_add ---

    #[test]
    fn module_registry_add_no_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_registry_add(
            &cli,
            &printer,
            "https://github.com/example/modules.git",
            None,
        );
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No cfgd.yaml"));
    }

    #[test]
    fn module_registry_add_success() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_registry_add(
            &cli,
            &printer,
            "https://github.com/cfgd-community/modules.git",
            Some("community"),
        )
        .unwrap();

        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.unwrap().registries;
        assert_eq!(registries.len(), 1);
        assert_eq!(registries[0].name, "community");
        assert_eq!(
            registries[0].url,
            "https://github.com/cfgd-community/modules.git"
        );
    }

    #[test]
    fn module_registry_add_duplicate() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-community/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Adding the same registry again should succeed (idempotent) but not duplicate
        let result = module::cmd_module_registry_add(
            &cli,
            &printer,
            "https://github.com/cfgd-community/modules.git",
            Some("community"),
        );
        assert!(
            result.is_ok(),
            "adding duplicate registry should succeed (idempotent): {:?}",
            result.err()
        );

        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.unwrap().registries;
        assert_eq!(registries.len(), 1);
    }

    #[test]
    fn module_registry_add_second_registry() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-community/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_registry_add(
            &cli,
            &printer,
            "git@github.com:my-org/private-modules.git",
            Some("private"),
        )
        .unwrap();

        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.unwrap().registries;
        assert_eq!(registries.len(), 2);
        assert_eq!(registries[1].name, "private");
    }

    // --- cmd_module_registry_remove ---

    #[test]
    fn module_registry_remove_no_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_registry_remove(&cli, &printer, "community");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    #[test]
    fn module_registry_remove_success() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-community/modules.git
      - name: private
        url: git@github.com:my-org/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_registry_remove(&cli, &printer, "community").unwrap();

        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.unwrap().registries;
        assert_eq!(registries.len(), 1);
        assert_eq!(registries[0].name, "private");
    }

    #[test]
    fn module_registry_remove_not_found() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        module::cmd_module_registry_remove(&cli, &printer, "nonexistent").unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("not found")
                || output.contains("nonexistent")
                || output.contains("No module registries"),
            "removing non-existent registry should note it, got: {output}"
        );
    }

    #[test]
    fn module_registry_remove_warns_on_profile_references() {
        let dir = tempfile::tempdir().unwrap();
        // Config with a registry
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-community/modules.git
"#,
        )
        .unwrap();
        // Profile that references community/vim
        let profiles_dir = dir.path().join("profiles");
        std::fs::create_dir_all(&profiles_dir).unwrap();
        std::fs::write(
            profiles_dir.join("default.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - community/vim
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        module::cmd_module_registry_remove(&cli, &printer, "community").unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("community"),
            "output should mention removed registry name, got: {output}"
        );
        // Verify registry was actually removed from config
        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.map(|m| m.registries).unwrap_or_default();
        assert!(
            registries.is_empty(),
            "registry should be removed from config"
        );
    }

    // --- cmd_module_registry_rename ---

    #[test]
    fn module_registry_rename_success() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: old-name
        url: https://github.com/example/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_registry_rename(&cli, &printer, "old-name", "new-name").unwrap();

        let cfg = config::load_config(&dir.path().join("cfgd.yaml")).unwrap();
        let registries = cfg.spec.modules.unwrap().registries;
        assert_eq!(registries[0].name, "new-name");
    }

    #[test]
    fn module_registry_rename_cascades_to_profiles() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: old-reg
        url: https://github.com/example/modules.git
"#,
        )
        .unwrap();

        let profiles_dir = dir.path().join("profiles");
        std::fs::create_dir_all(&profiles_dir).unwrap();
        std::fs::write(
            profiles_dir.join("default.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - old-reg/vim
    - old-reg/git
    - local-mod
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        module::cmd_module_registry_rename(&cli, &printer, "old-reg", "new-reg").unwrap();

        // Profile should be updated
        let profile = config::load_profile(&profiles_dir.join("default.yaml")).unwrap();
        assert!(profile.spec.modules.contains(&"new-reg/vim".to_string()));
        assert!(profile.spec.modules.contains(&"new-reg/git".to_string()));
        assert!(profile.spec.modules.contains(&"local-mod".to_string()));
        // Old references should be gone
        assert!(
            !profile
                .spec
                .modules
                .iter()
                .any(|m| m.starts_with("old-reg/"))
        );
    }

    #[test]
    fn module_registry_rename_not_found() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_registry_rename(&cli, &printer, "nonexistent", "new-name");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn module_registry_rename_duplicate_target() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: alpha
        url: https://github.com/alpha/modules.git
      - name: beta
        url: https://github.com/beta/modules.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_registry_rename(&cli, &printer, "alpha", "beta");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("already exists"));
    }

    #[test]
    fn module_registry_rename_no_config() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_registry_rename(&cli, &printer, "old", "new");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    // --- cmd_module_keys_list ---

    #[test]
    fn module_keys_list_no_keys() {
        let (printer, buf) = Printer::for_test();
        module::cmd_module_keys_list(&printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Keys") || output.contains("No") || output.contains("cosign"),
            "keys list should show key info or no-keys, got: {output}"
        );
    }

    #[test]
    fn module_keys_list_with_pub_key() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("cosign.pub"), "fake pub key").unwrap();

        let (printer, buf) = Printer::for_test();
        module::cmd_module_keys_list(&printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Keys") || output.contains("cosign"),
            "keys list should show key info, got: {output}"
        );
    }

    // --- cmd_module_create with manager prefix ---

    #[test]
    fn module_create_with_manager_prefix_packages() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ModuleCreateArgs {
            packages: vec!["brew:ripgrep".to_string(), "cargo:bat".to_string()],
            ..test_module_create_args("mgr-mod")
        };
        module::cmd_module_create(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "mgr-mod").unwrap();
        assert_eq!(doc.spec.packages.len(), 2);
        assert_eq!(doc.spec.packages[0].name, "ripgrep");
        assert_eq!(doc.spec.packages[0].prefer, vec!["brew"]);
        assert_eq!(doc.spec.packages[1].name, "bat");
        assert_eq!(doc.spec.packages[1].prefer, vec!["cargo"]);
    }

    // --- structured output mode ---

    #[test]
    fn module_list_structured_output_empty() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join("modules")).unwrap();
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        module::cmd_module_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON output: {e}, got: {output}"));
        assert!(parsed.is_array(), "JSON should be an array");
    }

    #[test]
    fn module_show_structured_output() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "json-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: json-mod\nspec:\n  packages:\n    - name: curl\n",
        );
        let state_dir = dir.path().join("state");
        let cli = test_cli_with_state(dir.path(), Some(state_dir));
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        module::cmd_module_show(&cli, &printer, "json-mod", false).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(parsed["name"], "json-mod", "JSON should have module name");
    }

    #[test]
    fn module_registry_list_structured_output() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  modules:
    registries:
      - name: community
        url: https://github.com/cfgd-modules/community.git
"#,
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        module::cmd_module_registry_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert!(parsed.is_array(), "JSON should be an array of registries");
        assert_eq!(parsed[0]["name"], "community");
    }

    // ===================================================================
    // Additional coverage tests — untested command handlers & helpers
    // ===================================================================

    // --- load_config_and_profile ---

    #[test]
    fn load_config_and_profile_default_profile() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::load_config_and_profile(&cli, &printer);
        assert!(
            result.is_ok(),
            "loading config and default profile should succeed: {:?}",
            result.err()
        );
        let (cfg, resolved) = result.unwrap();
        assert_eq!(cfg.spec.profile.as_deref(), Some("default"));
        // The resolved profile should contain the env var from default profile
        assert!(resolved.merged.env.iter().any(|e| e.name == "editor"));
    }

    #[test]
    fn load_config_and_profile_with_override() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let mut cli = test_cli(dir.path());
        cli.profile = Some("work".to_string());
        let printer = test_printer();

        let result = super::load_config_and_profile(&cli, &printer);
        assert!(
            result.is_ok(),
            "loading config with profile override should succeed: {:?}",
            result.err()
        );
        let (_cfg, resolved) = result.unwrap();
        // Work profile overrides editor to 'code'
        let editor = resolved.merged.env.iter().find(|e| e.name == "editor");
        assert!(editor.is_some());
        assert_eq!(editor.unwrap().value, "code");
    }

    #[test]
    fn load_config_and_profile_missing_config_errors() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::load_config_and_profile(&cli, &printer);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("config file not found"),
            "expected 'config file not found' error, got: {msg}"
        );
    }

    #[test]
    fn load_config_and_profile_missing_profile_errors() {
        let dir = create_test_config_dir();
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: nonexistent\n",
        )
        .unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::load_config_and_profile(&cli, &printer);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("profile not found: nonexistent"),
            "expected 'profile not found: nonexistent' error, got: {msg}"
        );
    }

    // --- print_apply_result for InProgress status ---

    #[test]
    fn print_apply_result_in_progress() {
        let printer = test_printer();
        let result = cfgd_core::reconciler::ApplyResult {
            status: cfgd_core::state::ApplyStatus::InProgress,
            action_results: vec![],
            apply_id: 4,
        };
        let status = super::print_apply_result(&result, &printer);
        assert_eq!(status, cfgd_core::state::ApplyStatus::InProgress);
    }

    // --- add_to_gitignore edge cases ---

    #[test]
    fn add_to_gitignore_appends_to_existing() {
        let dir = tempfile::tempdir().unwrap();
        // Pre-populate .gitignore with existing content (no trailing newline)
        std::fs::write(dir.path().join(".gitignore"), "*.log").unwrap();

        super::add_to_gitignore(dir.path(), "secrets/").unwrap();

        let content = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
        assert!(content.contains("*.log"));
        assert!(content.contains("secrets/"));
        // Ensure a newline was added between old and new content
        assert!(content.contains("*.log\n"));
    }

    #[test]
    fn add_to_gitignore_preserves_trailing_newline() {
        let dir = tempfile::tempdir().unwrap();
        // Pre-populate with trailing newline
        std::fs::write(dir.path().join(".gitignore"), "*.log\n").unwrap();

        super::add_to_gitignore(dir.path(), "secrets/").unwrap();

        let content = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
        // Should not double-newline
        assert!(!content.contains("\n\n"));
        assert!(content.contains("secrets/\n"));
    }

    // --- copy_files_to_dir edge cases ---

    #[test]
    #[cfg(unix)]
    fn copy_files_to_dir_with_source_target_spec() {
        let dir = tempfile::tempdir().unwrap();
        let source = dir.path().join("my-config.txt");
        std::fs::write(&source, "config data").unwrap();
        let repo_dir = dir.path().join("repo");

        let target = dir.path().join("deploy").join("app.conf");
        let spec = format!("{}:{}", source.display(), target.display());
        let results = super::copy_files_to_dir(&[spec], &repo_dir).unwrap();

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].0, "my-config.txt");
        assert_eq!(results[0].1, target);
        // File should be in repo
        assert!(repo_dir.join("my-config.txt").exists());
        // Original should now be a symlink
        assert!(source.symlink_metadata().unwrap().file_type().is_symlink());
    }

    #[test]
    #[cfg(unix)]
    fn copy_files_to_dir_directory_source() {
        let dir = tempfile::tempdir().unwrap();
        let source_dir = dir.path().join("dotfiles");
        std::fs::create_dir_all(&source_dir).unwrap();
        std::fs::write(source_dir.join("file1.txt"), "content1").unwrap();
        std::fs::write(source_dir.join("file2.txt"), "content2").unwrap();

        let repo_dir = dir.path().join("repo");
        let results =
            super::copy_files_to_dir(&[source_dir.display().to_string()], &repo_dir).unwrap();

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].0, "dotfiles");
        // Directory should be recursively copied
        assert!(repo_dir.join("dotfiles").join("file1.txt").exists());
        assert!(repo_dir.join("dotfiles").join("file2.txt").exists());
        // Source should be a symlink
        assert!(
            source_dir
                .symlink_metadata()
                .unwrap()
                .file_type()
                .is_symlink()
        );
    }

    #[test]
    fn copy_files_to_dir_rejects_system_directories() {
        let dir = tempfile::tempdir().unwrap();
        let repo_dir = dir.path().join("repo");

        // /etc/passwd exists on Linux, and it should be rejected
        if std::path::Path::new("/etc/passwd").exists() {
            let result = super::copy_files_to_dir(&["/etc/passwd".into()], &repo_dir);
            assert!(result.is_err());
            assert!(result.unwrap_err().to_string().contains("system directory"));
        }
    }

    // --- action_type_str ---

    #[test]
    fn action_type_str_file_variants() {
        use cfgd_core::reconciler::Action;

        assert_eq!(
            super::action_type_str(&Action::File(FileAction::Create {
                source: "/a".into(),
                target: "/b".into(),
                origin: "local".into(),
                strategy: cfgd_core::config::FileStrategy::default(),
                source_hash: None,
            })),
            "create"
        );

        assert_eq!(
            super::action_type_str(&Action::File(FileAction::Update {
                source: "/a".into(),
                target: "/b".into(),
                diff: String::new(),
                origin: "local".into(),
                strategy: cfgd_core::config::FileStrategy::default(),
                source_hash: None,
            })),
            "update"
        );

        assert_eq!(
            super::action_type_str(&Action::File(FileAction::Delete {
                target: "/b".into(),
                origin: "local".into(),
            })),
            "delete"
        );

        assert_eq!(
            super::action_type_str(&Action::File(FileAction::SetPermissions {
                target: "/b".into(),
                mode: 0o644,
                origin: "local".into(),
            })),
            "chmod"
        );

        assert_eq!(
            super::action_type_str(&Action::File(FileAction::Skip {
                target: "/b".into(),
                reason: "test".into(),
                origin: "local".into(),
            })),
            "skip"
        );
    }

    #[test]
    fn action_type_str_package_variants() {
        use cfgd_core::reconciler::Action;

        assert_eq!(
            super::action_type_str(&Action::Package(PackageAction::Install {
                manager: "brew".into(),
                packages: vec!["curl".into()],
                origin: "local".into(),
            })),
            "install"
        );

        assert_eq!(
            super::action_type_str(&Action::Package(PackageAction::Uninstall {
                manager: "brew".into(),
                packages: vec!["curl".into()],
                origin: "local".into(),
            })),
            "uninstall"
        );

        assert_eq!(
            super::action_type_str(&Action::Package(PackageAction::Bootstrap {
                manager: "brew".into(),
                method: "curl".into(),
                origin: "local".into(),
            })),
            "bootstrap"
        );

        assert_eq!(
            super::action_type_str(&Action::Package(PackageAction::Skip {
                manager: "brew".into(),
                reason: "test".into(),
                origin: "local".into(),
            })),
            "skip"
        );
    }

    #[test]
    fn action_type_str_secret_variants() {
        use cfgd_core::reconciler::Action;

        assert_eq!(
            super::action_type_str(&Action::Secret(SecretAction::Decrypt {
                source: "a.enc".into(),
                target: "/b".into(),
                backend: "sops-age".into(),
                origin: "local".into(),
            })),
            "decrypt"
        );

        assert_eq!(
            super::action_type_str(&Action::Secret(SecretAction::Resolve {
                provider: "onepassword".into(),
                reference: "op://vault/item".into(),
                target: "/b".into(),
                origin: "local".into(),
            })),
            "resolve"
        );

        assert_eq!(
            super::action_type_str(&Action::Secret(SecretAction::Skip {
                source: "a".into(),
                reason: "test".into(),
                origin: "local".into(),
            })),
            "skip"
        );
    }

    #[test]
    fn action_type_str_env_variants() {
        use cfgd_core::reconciler::{Action, EnvAction};

        assert_eq!(
            super::action_type_str(&Action::Env(EnvAction::WriteEnvFile {
                path: "/tmp/env".into(),
                content: String::new(),
            })),
            "write"
        );

        assert_eq!(
            super::action_type_str(&Action::Env(EnvAction::InjectSourceLine {
                rc_path: "/tmp/rc".into(),
                line: "source /tmp/env".into(),
            })),
            "inject"
        );
    }

    #[test]
    fn action_type_str_system_variants() {
        use cfgd_core::reconciler::{Action, SystemAction};

        assert_eq!(
            super::action_type_str(&Action::System(SystemAction::SetValue {
                configurator: "shell".into(),
                key: "/bin/zsh".into(),
                desired: "/bin/zsh".into(),
                current: "/bin/bash".into(),
                origin: "local".into(),
            })),
            "set"
        );

        assert_eq!(
            super::action_type_str(&Action::System(SystemAction::Skip {
                configurator: "shell".into(),
                reason: "test".into(),
                origin: "local".into(),
            })),
            "skip"
        );
    }

    #[test]
    fn action_type_str_module_variants() {
        use cfgd_core::reconciler::{Action, ModuleAction, ModuleActionKind};

        assert_eq!(
            super::action_type_str(&Action::Module(ModuleAction {
                module_name: "m".into(),
                kind: ModuleActionKind::InstallPackages { resolved: vec![] },
            })),
            "install"
        );

        assert_eq!(
            super::action_type_str(&Action::Module(ModuleAction {
                module_name: "m".into(),
                kind: ModuleActionKind::DeployFiles { files: vec![] },
            })),
            "deploy"
        );

        assert_eq!(
            super::action_type_str(&Action::Module(ModuleAction {
                module_name: "m".into(),
                kind: ModuleActionKind::RunScript {
                    script: cfgd_core::config::ScriptEntry::Simple("echo hi".into()),
                    phase: cfgd_core::reconciler::ScriptPhase::PostApply,
                },
            })),
            "run"
        );

        assert_eq!(
            super::action_type_str(&Action::Module(ModuleAction {
                module_name: "m".into(),
                kind: ModuleActionKind::Skip {
                    reason: "test".into()
                },
            })),
            "skip"
        );
    }

    #[test]
    fn action_type_str_script() {
        use cfgd_core::reconciler::{Action, ScriptAction};

        assert_eq!(
            super::action_type_str(&Action::Script(ScriptAction::Run {
                entry: cfgd_core::config::ScriptEntry::Simple("echo done".into()),
                phase: cfgd_core::reconciler::ScriptPhase::PostApply,
                origin: "local".into(),
            })),
            "run"
        );
    }

    // --- build_plan_output ---

    #[test]
    fn build_plan_output_empty_plan() {
        let plan = reconciler::Plan {
            phases: vec![],
            warnings: vec![],
        };
        let output = super::build_plan_output(&plan, "apply", None);
        assert_eq!(output.context, "apply");
        assert_eq!(output.total_actions, 0);
        assert!(output.phases.is_empty());
    }

    #[test]
    fn build_plan_output_with_actions() {
        let plan = reconciler::Plan {
            phases: vec![reconciler::Phase {
                name: reconciler::PhaseName::Packages,
                actions: vec![reconciler::Action::Package(PackageAction::Install {
                    manager: "brew".into(),
                    packages: vec!["curl".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec!["something".into()],
        };
        let output = super::build_plan_output(&plan, "reconcile", None);
        assert_eq!(output.context, "reconcile");
        assert_eq!(output.total_actions, 1);
        assert_eq!(output.phases.len(), 1);
        assert_eq!(output.warnings, vec!["something".to_string()]);
    }

    #[test]
    fn build_plan_output_with_phase_filter() {
        let plan = reconciler::Plan {
            phases: vec![
                reconciler::Phase {
                    name: reconciler::PhaseName::Packages,
                    actions: vec![reconciler::Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["curl".into()],
                        origin: "local".into(),
                    })],
                },
                reconciler::Phase {
                    name: reconciler::PhaseName::Files,
                    actions: vec![reconciler::Action::File(FileAction::Create {
                        source: "/a".into(),
                        target: "/b".into(),
                        origin: "local".into(),
                        strategy: cfgd_core::config::FileStrategy::default(),
                        source_hash: None,
                    })],
                },
            ],
            warnings: vec![],
        };
        // Filter to only Files phase
        let output = super::build_plan_output(&plan, "apply", Some(&reconciler::PhaseName::Files));
        assert_eq!(output.total_actions, 1);
        assert_eq!(output.phases.len(), 1);
        assert_eq!(output.phases[0].phase, "Files");
    }

    // --- strip_scripts_from_plan ---

    #[test]
    fn strip_scripts_removes_script_phases() {
        use cfgd_core::reconciler::{Phase, PhaseName, Plan, ScriptAction};

        let mut plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::PreScripts,
                    actions: vec![reconciler::Action::Script(ScriptAction::Run {
                        entry: cfgd_core::config::ScriptEntry::Simple("echo pre".into()),
                        phase: cfgd_core::reconciler::ScriptPhase::PreApply,
                        origin: "local".into(),
                    })],
                },
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![reconciler::Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["curl".into()],
                        origin: "local".into(),
                    })],
                },
                Phase {
                    name: PhaseName::PostScripts,
                    actions: vec![reconciler::Action::Script(ScriptAction::Run {
                        entry: cfgd_core::config::ScriptEntry::Simple("echo post".into()),
                        phase: cfgd_core::reconciler::ScriptPhase::PostApply,
                        origin: "local".into(),
                    })],
                },
            ],
            warnings: vec![],
        };

        super::strip_scripts_from_plan(&mut plan);

        // Pre and post script phases should be removed
        assert_eq!(plan.phases.len(), 1);
        assert_eq!(plan.phases[0].name, PhaseName::Packages);
    }

    #[test]
    fn strip_scripts_removes_module_run_script_actions() {
        use cfgd_core::reconciler::{ModuleAction, ModuleActionKind, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Modules,
                actions: vec![
                    reconciler::Action::Module(ModuleAction {
                        module_name: "m".into(),
                        kind: ModuleActionKind::InstallPackages { resolved: vec![] },
                    }),
                    reconciler::Action::Module(ModuleAction {
                        module_name: "m".into(),
                        kind: ModuleActionKind::RunScript {
                            script: cfgd_core::config::ScriptEntry::Simple("echo hello".into()),
                            phase: cfgd_core::reconciler::ScriptPhase::PostApply,
                        },
                    }),
                    reconciler::Action::Module(ModuleAction {
                        module_name: "m".into(),
                        kind: ModuleActionKind::DeployFiles { files: vec![] },
                    }),
                ],
            }],
            warnings: vec![],
        };

        super::strip_scripts_from_plan(&mut plan);

        // RunScript should be removed, other actions kept
        assert_eq!(plan.phases[0].actions.len(), 2);
    }

    // --- filter_plan edge cases ---

    #[test]
    fn filter_plan_skip_file_by_target() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Files,
                actions: vec![
                    Action::File(FileAction::Create {
                        source: "/tmp/a".into(),
                        target: "/etc/foo".into(),
                        origin: "local".into(),
                        strategy: cfgd_core::config::FileStrategy::default(),
                        source_hash: None,
                    }),
                    Action::File(FileAction::Create {
                        source: "/tmp/b".into(),
                        target: "/etc/bar".into(),
                        origin: "local".into(),
                        strategy: cfgd_core::config::FileStrategy::default(),
                        source_hash: None,
                    }),
                ],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["files:/etc/foo".into()], &[]);
        assert_eq!(plan.phases[0].actions.len(), 1);
    }

    #[test]
    fn filter_plan_empty_skip_and_only_noop() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Install {
                    manager: "brew".into(),
                    packages: vec!["curl".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &[]);
        assert_eq!(plan.phases[0].actions.len(), 1);
    }

    #[test]
    fn filter_plan_skip_uninstall_packages() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Uninstall {
                    manager: "brew".into(),
                    packages: vec!["old-tool".into(), "keep-me".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["packages.brew.old-tool".into()], &[]);

        match &plan.phases[0].actions[0] {
            reconciler::Action::Package(PackageAction::Uninstall { packages, .. }) => {
                assert_eq!(packages, &["keep-me".to_string()]);
            }
            _ => panic!("expected Uninstall action"),
        }
    }

    #[test]
    fn filter_plan_only_with_uninstall() {
        use cfgd_core::reconciler::{Action, Phase, PhaseName, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Uninstall {
                    manager: "apt".into(),
                    packages: vec!["vim".into(), "nano".into()],
                    origin: "local".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &["packages.apt.vim".into()]);

        match &plan.phases[0].actions[0] {
            reconciler::Action::Package(PackageAction::Uninstall { packages, .. }) => {
                assert_eq!(packages, &["vim".to_string()]);
            }
            _ => panic!("expected Uninstall action"),
        }
    }

    // --- cmd_config_set and cmd_config_unset via full commands ---

    #[test]
    fn cmd_config_set_creates_nested_key() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(&config_path, TEST_CONFIG_YAML).unwrap();

        let cli = Cli {
            config: config_path.clone(),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        // Set a nested key
        super::cmd_config_set(&cli, &printer, "daemon.enabled", "true").unwrap();

        let contents = std::fs::read_to_string(&config_path).unwrap();
        assert!(contents.contains("daemon"));
        assert!(contents.contains("enabled"));
    }

    #[test]
    fn cmd_config_set_no_config_errors() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            config: dir.path().join("nonexistent.yaml"),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        let result = super::cmd_config_set(&cli, &printer, "profile", "work");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No cfgd.yaml"));
    }

    #[test]
    fn cmd_config_unset_no_config_errors() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            config: dir.path().join("nonexistent.yaml"),
            ..test_cli(dir.path())
        };
        let printer = test_printer();

        let result = super::cmd_config_unset(&cli, &printer, "profile");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No cfgd.yaml"));
    }

    // --- cmd_source_list with sources configured ---

    #[test]
    fn cmd_source_list_with_sources_configured() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team-config
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        // Verify config loaded with the source
        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.sources.len(), 1);
        assert_eq!(cfg.spec.sources[0].name, "team-config");

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_list(&cli, &printer);
        assert!(
            result.is_ok(),
            "source list should succeed when sources are configured in cfgd.yaml: {:?}",
            result.err()
        );
    }

    #[test]
    fn cmd_source_list_structured_output() {
        let (config_dir, state_dir) = setup_test_env();

        // Write config with a source so we can verify the source name appears in output
        let config_with_source = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: default\n  sources:\n    - name: team-config\n      origin:\n        url: https://github.com/team/config\n        branch: main\n        type: Git\n      subscription:\n        priority: 100\n";
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_source_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        let arr = parsed
            .as_array()
            .expect("source list JSON should be an array");
        assert_eq!(arr.len(), 1, "should have exactly one source");
        assert_eq!(
            arr[0]["name"], "team-config",
            "source name should be 'team-config'"
        );
    }

    // --- cmd_source_show ---

    #[test]
    fn cmd_source_show_not_found() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_show(&cli, &printer, "nonexistent");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_source_show_with_source() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team-config
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        // Verify the source exists in config
        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert!(
            cfg.spec.sources.iter().any(|s| s.name == "team-config"),
            "precondition: config should contain team-config source"
        );

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_source_show(&cli, &printer, "team-config").unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("team-config"),
            "source show should display source name, got: {output}"
        );
    }

    // --- cmd_source_remove ---

    #[test]
    fn cmd_source_remove_not_found() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_remove(&cli, &printer, "nonexistent", true, false);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_source_remove_keep_all_and_remove_all_conflict() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_remove(&cli, &printer, "anything", true, true);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("cannot use --keep-all and --remove-all together")
        );
    }

    // --- cmd_source_override ---

    #[test]
    fn cmd_source_override_source_not_found() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result =
            super::cmd_source_override(&cli, &printer, "nonexistent", "reject", "env.FOO", None);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_source_override_invalid_action() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_override(&cli, &printer, "team", "invalid", "env.FOO", None);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown action"));
    }

    #[test]
    fn cmd_source_override_set_requires_value() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_override(&cli, &printer, "team", "set", "env.FOO", None);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("requires a value"));
    }

    // --- cmd_source_priority ---

    #[test]
    fn cmd_source_priority_not_found() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_priority(&cli, &printer, "nonexistent", None);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_source_priority_view() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 200
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_source_priority(&cli, &printer, "team", None).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("team") || output.contains("200"),
            "source priority should show source name or priority, got: {output}"
        );
    }

    #[test]
    fn cmd_source_priority_update() {
        let (config_dir, state_dir) = setup_test_env();

        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 200
"#;
        std::fs::write(config_dir.path().join("cfgd.yaml"), config_with_source).unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_priority(&cli, &printer, "team", Some(500));
        assert!(
            result.is_ok(),
            "source priority update should succeed: {:?}",
            result.err()
        );

        // Verify the config was updated
        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        let source = cfg.spec.sources.iter().find(|s| s.name == "team").unwrap();
        assert_eq!(source.subscription.priority, 500);
    }

    // --- cmd_decide edge cases ---

    #[test]
    fn cmd_decide_no_args_shows_pending() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();

        super::cmd_decide(
            &printer,
            "accept",
            None,
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending") || output.contains("Pending"),
            "decide should show pending decisions info, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_with_pending_decision() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        state
            .upsert_pending_decision(
                "team-config",
                "packages.brew.curl",
                "recommended",
                "install",
                "Install curl via brew",
            )
            .unwrap();

        let result = super::cmd_decide(
            &printer,
            "accept",
            Some("packages.brew.curl"),
            None,
            false,
            Some(state_dir.path()),
        );
        assert!(
            result.is_ok(),
            "decide accept should succeed for a pending decision: {:?}",
            result.err()
        );

        let pending = state.pending_decisions().unwrap();
        assert!(
            pending.is_empty(),
            "accepted decision should no longer be pending"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("curl") || output.contains("Accepted"),
            "output should mention the accepted resource, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_accept_all_with_pending() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        state
            .upsert_pending_decision(
                "team",
                "packages.brew.curl",
                "recommended",
                "install",
                "Install curl via brew",
            )
            .unwrap();
        state
            .upsert_pending_decision("team", "env.EDITOR", "recommended", "set", "Set EDITOR")
            .unwrap();

        let result =
            super::cmd_decide(&printer, "accept", None, None, true, Some(state_dir.path()));
        assert!(
            result.is_ok(),
            "decide accept-all should succeed and resolve all pending decisions: {:?}",
            result.err()
        );

        let pending = state.pending_decisions().unwrap();
        assert!(pending.is_empty());

        let output = buf.lock().unwrap();
        assert!(
            output.contains("ACCEPTED") || output.contains("2 item"),
            "accept-all should mention accepted decisions, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_reject_by_source_with_pending() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, _buf) = Printer::for_test();

        let state = super::open_state_store(Some(state_dir.path())).unwrap();
        state
            .upsert_pending_decision(
                "team",
                "packages.brew.curl",
                "recommended",
                "install",
                "Install curl via brew",
            )
            .unwrap();
        state
            .upsert_pending_decision("other", "env.EDITOR", "recommended", "set", "Set EDITOR")
            .unwrap();

        let result = super::cmd_decide(
            &printer,
            "reject",
            None,
            Some("team"),
            false,
            Some(state_dir.path()),
        );
        assert!(
            result.is_ok(),
            "decide reject-by-source should succeed and only reject matching source: {:?}",
            result.err()
        );

        // Only the "other" source's decision should remain pending
        let pending = state.pending_decisions().unwrap();
        assert_eq!(pending.len(), 1);
        assert_eq!(pending[0].source, "other");
    }

    // --- cmd_workflow_generate ---

    #[test]
    #[cfg(unix)] // prompt_confirm hangs on Windows CI (no /dev/null stdin fallback)
    fn cmd_workflow_generate_no_overwrite_without_force() {
        let dir = create_test_config_dir();
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // First generate
        super::cmd_workflow_generate(&cli, &printer, false).unwrap();
        let path = dir.path().join(".github/workflows/cfgd-release.yml");
        assert!(path.exists());

        // Write custom content
        std::fs::write(&path, "custom content").unwrap();

        // Generate without force — should NOT overwrite
        super::cmd_workflow_generate(&cli, &printer, false).unwrap();

        let contents = std::fs::read_to_string(&path).unwrap();
        assert_eq!(
            contents, "custom content",
            "should not overwrite without --force"
        );
    }

    // --- cmd_log_show_output ---

    #[test]
    fn cmd_log_show_output_no_entries() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        // Nonexistent apply ID should fail
        let result = super::cmd_log_show_output(&printer, &state, 9999);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("no apply found"));
    }

    #[test]
    fn cmd_compliance_history_structured() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_compliance_history(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(
            parsed,
            serde_json::json!({"entries": []}),
            "fresh state should produce exactly {{entries: []}}"
        );
    }

    // --- cmd_compliance_diff ---

    #[test]
    fn cmd_compliance_diff_missing_snapshot() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_compliance_diff(&cli, &printer, 1, 2);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    // --- cmd_apply module-only mode (no profile configured) ---

    #[test]
    fn cmd_apply_module_only_no_profile() {
        let dir = tempfile::tempdir().unwrap();
        let state_dir = tempfile::tempdir().unwrap();

        // Minimal config with no profile
        std::fs::write(
            dir.path().join("cfgd.yaml"),
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec: {}\n",
        )
        .unwrap();

        // Create a module
        create_module_in_dir(
            dir.path(),
            "standalone-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: standalone-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli_with_state(dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: Some("standalone-mod".to_string()),
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "dry-run apply should succeed with --module flag and no profile configured: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("standalone-mod")
                || output.contains("Apply")
                || output.contains("Nothing"),
            "apply with module-only should mention the module, got: {output}"
        );
    }

    // --- cmd_plan module-only mode (no profile) ---

    #[test]
    fn cmd_plan_module_only_no_profile() {
        let dir = tempfile::tempdir().unwrap();
        let state_dir = tempfile::tempdir().unwrap();

        std::fs::write(
            dir.path().join("cfgd.yaml"),
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec: {}\n",
        )
        .unwrap();

        create_module_in_dir(
            dir.path(),
            "solo",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: solo\nspec:\n  packages: []\n",
        );

        let cli = test_cli_with_state(dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: Some("solo".to_string()),
            skip_scripts: false,
            context: "apply".to_string(),
        };

        let result = super::cmd_plan(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "plan should succeed with --module flag and no profile configured: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("solo") || output.contains("Nothing"),
            "plan with module-only should mention the module or plan, got: {output}"
        );
    }

    // --- empty_resolved_profile ---

    #[test]
    fn empty_resolved_profile_has_module() {
        let resolved = super::empty_resolved_profile("my-mod");
        assert_eq!(resolved.merged.modules, vec!["my-mod".to_string()]);
        assert!(resolved.merged.env.is_empty());
        assert!(resolved.layers.is_empty());
    }

    // --- known_manager_names ---

    #[test]
    fn known_manager_names_not_empty() {
        let names = super::known_manager_names();
        assert!(!names.is_empty());
        // Should contain at least "cargo" since it's always available
        assert!(
            names.contains(&"cargo".to_string()),
            "should contain 'cargo' manager"
        );
    }

    // --- secret_backend_from_config with config ---

    #[test]
    fn secret_backend_from_config_with_custom_backend() {
        let cfg = config::parse_config(
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  secrets:
    backend: sops-age
    sops:
      ageKey: /tmp/test-key.txt
"#,
            std::path::Path::new("cfgd.yaml"),
        )
        .unwrap();

        let (backend, key) = super::secret_backend_from_config(Some(&cfg));
        assert_eq!(backend, "sops-age");
        assert_eq!(key, Some(PathBuf::from("/tmp/test-key.txt")));
    }

    #[test]
    fn secret_backend_from_config_no_secrets_section() {
        let cfg = config::parse_config(
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: test\nspec:\n  profile: default\n",
            std::path::Path::new("cfgd.yaml"),
        )
        .unwrap();

        let (backend, key) = super::secret_backend_from_config(Some(&cfg));
        assert_eq!(backend, "sops");
        assert!(key.is_none());
    }

    // --- build_registry_with_config ---

    #[test]
    fn build_registry_with_config_applies_file_strategy() {
        let cfg = config::parse_config(
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  fileStrategy: Copy
"#,
            std::path::Path::new("cfgd.yaml"),
        )
        .unwrap();

        let registry = super::build_registry_with_config(Some(&cfg));
        assert_eq!(
            registry.default_file_strategy,
            cfgd_core::config::FileStrategy::Copy
        );
    }

    // --- execute dispatch for more commands ---

    #[test]
    fn execute_explain_recursive() {
        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Explain {
                resource: Some("config".to_string()),
                recursive: true,
            },
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Config") || output.contains("config") || output.contains("spec"),
            "explain recursive for config should describe config resource, got: {output}"
        );
    }

    #[test]
    fn execute_compliance_command() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Compliance { command: None },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Compliance") || output.contains("snapshot"),
            "compliance dispatch should produce output, got: {output}"
        );
    }

    #[test]
    fn execute_source_list() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Source {
                command: SourceCommand::List,
            },
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Sources") || output.contains("No sources"),
            "source list should produce output, got: {output}"
        );
    }

    #[test]
    fn execute_decide_accept_all() {
        let (_config_dir, state_dir) = setup_test_env();

        let dir = tempfile::tempdir().unwrap();
        let cli = Cli {
            command: Command::Decide {
                action: "accept".to_string(),
                resource: None,
                source: None,
                all: true,
            },
            state_dir: Some(state_dir.path().to_path_buf()),
            ..test_cli(dir.path())
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("No pending")
                || output.contains("0 decision")
                || output.contains("ACCEPTED"),
            "decide dispatch should produce output, got: {output}"
        );
    }

    #[test]
    fn execute_sync_command() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Sync,
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Sync") || output.contains("No sources"),
            "sync dispatch should produce output, got: {output}"
        );
    }

    #[test]
    fn execute_pull_command() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            command: Command::Pull,
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test();

        super::execute(&cli, &printer).unwrap();
        let output = buf.lock().unwrap();
        assert!(
            output.contains("Pull")
                || output.contains("No sources")
                || output.contains("no origin"),
            "pull dispatch should produce output, got: {output}"
        );
    }

    // --- cmd_apply with aliases ---

    #[test]
    fn cmd_apply_with_aliases() {
        let (config_dir, state_dir) = setup_test_env();

        let profile = "apiVersion: cfgd.io/v1alpha1\nkind: Profile\nmetadata:\n  name: default\nspec:\n  aliases:\n    - name: ll\n      command: ls -la\n  modules: []\n";
        std::fs::write(
            config_dir.path().join("profiles").join("default.yaml"),
            profile,
        )
        .unwrap();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "dry-run apply should succeed when profile contains shell aliases: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan"),
            "should contain Plan header, got: {output}"
        );
        assert!(
            output.contains("Phase:") || output.contains("Nothing to do"),
            "should mention plan phases or nothing to do, got: {output}"
        );
    }

    // --- cmd_status structured output for module ---

    #[test]
    fn cmd_status_module_structured_output() {
        let (config_dir, state_dir) = setup_test_env();

        let mod_dir = config_dir.path().join("modules").join("json-mod");
        std::fs::create_dir_all(&mod_dir).unwrap();
        std::fs::write(
            mod_dir.join("module.yaml"),
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: json-mod\nspec:\n  packages: []\n",
        )
        .unwrap();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_status(&cli, &printer, Some("json-mod")).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert_eq!(parsed["name"], "json-mod", "should contain module name");
        assert_eq!(
            parsed["status"], "not applied",
            "fresh module should be 'not applied'"
        );
        assert_eq!(parsed["packages"], 0, "empty module should have 0 packages");
    }

    // --- cmd_verify structured output ---

    #[test]
    fn cmd_verify_structured_output() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_verify(&cli, &printer, None).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("results").is_some(),
            "verify JSON should have 'results'"
        );
        assert!(
            parsed.get("passCount").is_some(),
            "verify JSON should have 'passCount'"
        );
        assert!(
            parsed.get("failCount").is_some(),
            "verify JSON should have 'failCount'"
        );
        let total = parsed["passCount"].as_u64().unwrap() + parsed["failCount"].as_u64().unwrap();
        assert_eq!(
            total,
            parsed["results"].as_array().unwrap().len() as u64,
            "pass + fail should equal total results"
        );
    }

    // --- cmd_plan structured output ---

    #[test]
    fn cmd_plan_structured_output() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("context").is_some(),
            "plan JSON should have 'context'"
        );
        assert_eq!(parsed["context"], "apply", "plan context should be 'apply'");
        assert!(
            parsed.get("totalActions").is_some(),
            "plan JSON should have 'totalActions'"
        );
        assert!(
            parsed["totalActions"].is_u64(),
            "totalActions should be a numeric value"
        );
    }

    // --- cmd_log with show_output ---

    #[test]
    fn cmd_log_show_output_for_nonexistent_apply() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        // Nonexistent apply ID should fail
        let result = super::cmd_log(&printer, 10, Some(999), Some(state_dir.path()));
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("no apply found"));
    }

    // --- validate_resource_name edge cases ---

    #[test]
    fn validate_resource_name_max_length() {
        // 128 chars should be valid
        let name = "a".repeat(128);
        assert!(super::validate_resource_name(&name, "Module").is_ok());

        // 129 chars should be invalid
        let name = "a".repeat(129);
        assert!(super::validate_resource_name(&name, "Module").is_err());
    }

    #[test]
    fn validate_resource_name_underscores_and_dots() {
        // Unwrap to ensure success; not just is_ok()
        super::validate_resource_name("my_module.v2", "Module").unwrap();
    }

    // --- display_plan_table ---

    #[test]
    fn display_plan_table_empty_plan() {
        let plan = reconciler::Plan {
            phases: vec![],
            warnings: vec![],
        };
        let (printer, buf) = Printer::for_test();
        super::display_plan_table(&plan, &printer, None);

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Plan") || output.contains("Nothing") || output.is_empty(),
            "empty plan should show plan header or nothing, got: {output}"
        );
    }

    #[test]
    fn display_plan_table_with_phase_filter() {
        let plan = reconciler::Plan {
            phases: vec![
                reconciler::Phase {
                    name: reconciler::PhaseName::Packages,
                    actions: vec![reconciler::Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["curl".into()],
                        origin: "local".into(),
                    })],
                },
                reconciler::Phase {
                    name: reconciler::PhaseName::Files,
                    actions: vec![],
                },
            ],
            warnings: vec![],
        };

        let (printer, buf) = Printer::for_test();
        super::display_plan_table(&plan, &printer, Some(&reconciler::PhaseName::Files));

        let output = buf.lock().unwrap();
        // Packages phase should be filtered out
        assert!(
            !output.contains("curl"),
            "filtered plan should not show curl from Packages phase, got: {output}"
        );
    }

    // --- infer_source_name edge cases ---

    #[test]
    fn infer_source_name_plain_url() {
        let name = super::infer_source_name("https://example.com/config");
        assert_eq!(name, "config");
    }

    #[test]
    fn infer_source_name_with_git_suffix() {
        let name = super::infer_source_name("https://github.com/org/repo.git");
        assert_eq!(name, "repo");
    }

    // --- OutputFormatArg From impl ---

    #[test]
    fn output_format_arg_into_os_str() {
        use super::OutputFormatArg;
        let arg = OutputFormatArg(cfgd_core::output::OutputFormat::Json);
        let os_str: clap::builder::OsStr = arg.into();
        assert_eq!(os_str, "table");
    }

    // --- set_nested_yaml_value ---

    #[test]
    fn set_nested_yaml_value_overwrites_existing() {
        let mut root: serde_yaml::Value = serde_yaml::from_str("a:\n  b: old\n").unwrap();
        super::set_nested_yaml_value(&mut root, "a.b", &serde_yaml::Value::String("new".into()))
            .unwrap();

        let val = root
            .get("a")
            .and_then(|v| v.get("b"))
            .and_then(|v| v.as_str());
        assert_eq!(val, Some("new"));
    }

    #[test]
    fn set_nested_yaml_value_creates_deep_path() {
        let mut root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        super::set_nested_yaml_value(
            &mut root,
            "a.b.c.d",
            &serde_yaml::Value::String("deep".into()),
        )
        .unwrap();

        let val = root
            .get("a")
            .and_then(|v| v.get("b"))
            .and_then(|v| v.get("c"))
            .and_then(|v| v.get("d"))
            .and_then(|v| v.as_str());
        assert_eq!(val, Some("deep"));
    }

    // --- Profile update with files ---

    #[test]
    fn profile_update_add_and_remove_files() {
        let dir = create_test_config_dir();

        // Create a test file to import
        let test_file = dir.path().join("testfile.conf");
        std::fs::write(&test_file, "test content").unwrap();

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add file to profile
        let args = ProfileUpdateArgs {
            files: vec![format!(
                "{}:{}",
                test_file.display(),
                dir.path().join("deploy").join("testfile.conf").display()
            )],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let managed = &doc.spec.files.as_ref().unwrap().managed;
        assert_eq!(
            managed.len(),
            1,
            "profile should have exactly 1 managed file after add"
        );
        assert!(
            managed[0].source.contains("testfile.conf"),
            "managed file source should reference testfile.conf"
        );

        // Remove file from profile
        let target_path = dir.path().join("deploy").join("testfile.conf");
        let args = ProfileUpdateArgs {
            files: vec![format!("-{}", target_path.display())],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let file_count = doc
            .spec
            .files
            .as_ref()
            .map(|f| f.managed.len())
            .unwrap_or(0);
        assert_eq!(
            file_count, 0,
            "profile should have no managed files after removal"
        );
    }

    // --- Profile update env add/remove ---

    #[test]
    fn profile_update_env_add_and_remove() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add env var
        let args = ProfileUpdateArgs {
            env: vec!["CUSTOM_VAR=hello".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(doc.spec.env.iter().any(|e| e.name == "CUSTOM_VAR"));

        // Remove env var
        let args = ProfileUpdateArgs {
            env: vec!["-CUSTOM_VAR".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(!doc.spec.env.iter().any(|e| e.name == "CUSTOM_VAR"));
    }

    // --- Profile update alias add/remove ---

    #[test]
    fn profile_update_alias_add_and_remove() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add alias
        let args = ProfileUpdateArgs {
            aliases: vec!["ll=ls -la".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(doc.spec.aliases.iter().any(|a| a.name == "ll"));

        // Remove alias
        let args = ProfileUpdateArgs {
            aliases: vec!["-ll".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(!doc.spec.aliases.iter().any(|a| a.name == "ll"));
    }

    // --- Profile update modules add/remove ---

    #[test]
    fn profile_update_modules_add_and_remove() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add module
        let args = ProfileUpdateArgs {
            modules: vec!["neovim".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(doc.spec.modules.contains(&"neovim".to_string()));

        // Remove module
        let args = ProfileUpdateArgs {
            modules: vec!["-neovim".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        assert!(!doc.spec.modules.contains(&"neovim".to_string()));
    }

    // --- Profile update packages add/remove ---

    #[test]
    fn profile_update_packages_add_and_remove() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add package
        let args = ProfileUpdateArgs {
            packages: vec!["brew:jq".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let brew = doc.spec.packages.as_ref().unwrap().brew.as_ref().unwrap();
        assert!(brew.formulae.contains(&"jq".to_string()));

        // Remove package
        let args = ProfileUpdateArgs {
            packages: vec!["-brew:jq".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let brew = doc.spec.packages.as_ref().unwrap().brew.as_ref().unwrap();
        assert!(!brew.formulae.contains(&"jq".to_string()));
    }

    // --- Profile create with aliases, secrets, scripts ---

    #[test]
    fn profile_create_with_aliases() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileCreateArgs {
            aliases: vec!["ll=ls -la".to_string(), "gs=git status".to_string()],
            ..test_profile_create_args("alias-profile")
        };
        profile::cmd_profile_create(&cli, &printer, &args).unwrap();

        let doc =
            config::load_profile(&dir.path().join("profiles").join("alias-profile.yaml")).unwrap();
        assert_eq!(doc.spec.aliases.len(), 2);
        assert!(doc.spec.aliases.iter().any(|a| a.name == "ll"));
        assert!(doc.spec.aliases.iter().any(|a| a.name == "gs"));
    }

    #[test]
    fn profile_create_with_secrets() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileCreateArgs {
            secrets: vec!["secrets/api.enc:~/.config/app/key".to_string()],
            ..test_profile_create_args("secret-profile")
        };
        profile::cmd_profile_create(&cli, &printer, &args).unwrap();

        let doc =
            config::load_profile(&dir.path().join("profiles").join("secret-profile.yaml")).unwrap();
        assert_eq!(doc.spec.secrets.len(), 1);
        assert_eq!(doc.spec.secrets[0].source, "secrets/api.enc");
    }

    #[test]
    fn profile_create_with_scripts() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileCreateArgs {
            pre_apply: vec!["scripts/pre.sh".to_string()],
            post_apply: vec!["scripts/post.sh".to_string()],
            on_change: vec!["scripts/change.sh".to_string()],
            on_drift: vec!["scripts/drift.sh".to_string()],
            ..test_profile_create_args("script-profile")
        };
        profile::cmd_profile_create(&cli, &printer, &args).unwrap();

        let doc =
            config::load_profile(&dir.path().join("profiles").join("script-profile.yaml")).unwrap();
        let scripts = doc.spec.scripts.as_ref().unwrap();
        assert_eq!(scripts.pre_apply.len(), 1);
        assert_eq!(scripts.post_apply.len(), 1);
        assert_eq!(scripts.on_change.len(), 1);
        assert_eq!(scripts.on_drift.len(), 1);
    }

    // --- Profile update on_drift scripts ---

    #[test]
    fn profile_update_on_drift_scripts() {
        let dir = create_test_config_dir();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let args = ProfileUpdateArgs {
            on_drift: vec!["scripts/drift.sh".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let scripts = doc.spec.scripts.as_ref().unwrap();
        assert_eq!(
            scripts.on_drift,
            vec![config::ScriptEntry::Simple("scripts/drift.sh".to_string())]
        );

        // Remove
        let args = ProfileUpdateArgs {
            on_drift: vec!["-scripts/drift.sh".to_string()],
            ..empty_profile_update_args()
        };
        profile::cmd_profile_update(&cli, &printer, "default", &args).unwrap();

        let doc = config::load_profile(&dir.path().join("profiles").join("default.yaml")).unwrap();
        let scripts = doc.spec.scripts.as_ref().unwrap();
        assert!(scripts.on_drift.is_empty());
    }

    // --- Module update add/remove env ---

    #[test]
    fn module_update_env_add_and_remove() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "env-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: env-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add env
        let args = ModuleUpdateArgs {
            env: vec!["MY_VAR=hello".to_string()],
            ..empty_module_update_args("env-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "env-mod").unwrap();
        assert!(doc.spec.env.iter().any(|e| e.name == "MY_VAR"));

        // Remove env
        let args = ModuleUpdateArgs {
            env: vec!["-MY_VAR".to_string()],
            ..empty_module_update_args("env-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "env-mod").unwrap();
        assert!(!doc.spec.env.iter().any(|e| e.name == "MY_VAR"));
    }

    // --- Module update add/remove aliases ---

    #[test]
    fn module_update_alias_add_and_remove() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "alias-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: alias-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add alias
        let args = ModuleUpdateArgs {
            aliases: vec!["ll=ls -la".to_string()],
            ..empty_module_update_args("alias-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "alias-mod").unwrap();
        assert!(doc.spec.aliases.iter().any(|a| a.name == "ll"));

        // Remove alias
        let args = ModuleUpdateArgs {
            aliases: vec!["-ll".to_string()],
            ..empty_module_update_args("alias-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "alias-mod").unwrap();
        assert!(!doc.spec.aliases.iter().any(|a| a.name == "ll"));
    }

    // --- Module update add/remove depends ---

    #[test]
    fn module_update_depends_add_and_remove() {
        let dir = tempfile::tempdir().unwrap();
        create_module_in_dir(
            dir.path(),
            "dep-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: dep-mod\nspec:\n  packages: []\n",
        );

        let cli = test_cli(dir.path());
        let printer = test_printer();

        // Add dependency
        let args = ModuleUpdateArgs {
            depends: vec!["base".to_string()],
            ..empty_module_update_args("dep-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "dep-mod").unwrap();
        assert!(doc.spec.depends.contains(&"base".to_string()));

        // Remove dependency
        let args = ModuleUpdateArgs {
            depends: vec!["-base".to_string()],
            ..empty_module_update_args("dep-mod")
        };
        module::cmd_module_update_local(&cli, &printer, &args).unwrap();

        let (doc, _) = module::load_module_document(dir.path(), "dep-mod").unwrap();
        assert!(!doc.spec.depends.contains(&"base".to_string()));
    }

    // --- source_cache_dir ---

    #[test]
    fn source_cache_dir_with_state_dir() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let result = super::source_cache_dir(&cli);
        assert!(
            result.is_ok(),
            "source_cache_dir should return a valid path: {:?}",
            result.err()
        );
        let path = result.unwrap();
        assert!(path.to_string_lossy().contains("sources"));
    }

    // --- cmd_apply with both skip and only ---

    #[test]
    fn cmd_apply_dry_run_with_skip_and_only() {
        let (config_dir, state_dir) = setup_test_env();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();
        let args = ApplyArgs {
            from: None,
            dry_run: true,
            phase: None,
            yes: true,
            skip: vec!["packages.cargo".to_string()],
            only: vec!["packages".to_string()],
            module: None,
            skip_scripts: false,
        };

        let result = super::cmd_apply(&cli, &printer, &args);
        assert!(
            result.is_ok(),
            "dry-run apply should succeed with both --skip and --only filters: {:?}",
            result.err()
        );

        // Dry-run should NOT create any state store records
        let state = StateStore::open(&state_dir.path().join("cfgd.db")).unwrap();
        assert!(
            state.last_apply().unwrap().is_none(),
            "dry-run with skip+only filters should not create apply records"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Apply")
                || output.contains("Plan")
                || output.contains("Nothing")
                || output.contains("dry"),
            "dry-run apply with skip+only should produce output, got: {output}"
        );
    }

    // --- cmd_plan with module filter and structured output ---

    #[test]
    fn cmd_plan_module_structured_output() {
        let (config_dir, state_dir) = setup_test_env();

        create_module_in_dir(
            config_dir.path(),
            "struct-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: struct-mod\nspec:\n  packages: []\n",
        );

        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: Some("struct-mod".to_string()),
            skip_scripts: false,
            context: "apply".to_string(),
        };

        super::cmd_plan(&cli, &printer, &args).unwrap();

        let output = buf.lock().unwrap();
        let parsed = extract_json(&output);
        assert!(
            parsed.get("context").is_some(),
            "plan JSON should have 'context'"
        );
        assert_eq!(parsed["context"], "apply", "plan context should be 'apply'");
        assert!(
            parsed.get("phases").is_some(),
            "plan JSON should have 'phases'"
        );
        assert!(parsed["phases"].is_array(), "phases should be an array");
    }

    // --- Phase 1: New test coverage ---

    fn setup_rich_test_env() -> (tempfile::TempDir, tempfile::TempDir) {
        let (config_dir, state_dir) = setup_test_env();
        std::fs::write(config_dir.path().join("cfgd.yaml"), RICH_CONFIG_YAML).unwrap();
        (config_dir, state_dir)
    }

    // --- cmd_config_show ---

    #[test]
    fn cmd_config_show_with_rich_config() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_config_show(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Configuration"),
            "missing Configuration header"
        );
        assert!(output.contains("team-config"), "missing source name");
        assert!(output.contains("Daemon"), "missing Daemon section");
    }

    #[test]
    fn cmd_config_show_structured_json() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_config_show(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(
            parsed["metadata"]["name"], "my-machine",
            "config show JSON should include metadata.name"
        );
        assert!(
            parsed.get("spec").is_some(),
            "config show JSON should include spec"
        );
    }

    #[test]
    fn cmd_config_show_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::cmd_config_show(&cli, &printer);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No cfgd.yaml"));
    }

    // --- cmd_config_get ---

    #[test]
    fn cmd_config_get_reads_profile() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_config_get(&cli, &printer, "profile").unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("default"),
            "config get profile should return 'default', got: {output}"
        );
    }

    #[test]
    fn cmd_config_get_nested_key() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_config_get(&cli, &printer, "daemon.enabled").unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("true"),
            "config get daemon.enabled should return 'true', got: {output}"
        );
    }

    #[test]
    fn cmd_config_get_structured_json() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_config_get(&cli, &printer, "profile").unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(parsed, "default", "profile should be 'default'");
    }

    #[test]
    fn cmd_config_get_missing_key_fails() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_config_get(&cli, &printer, "nonexistent.path");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("not found in config"),
            "expected 'not found in config' error, got: {msg}"
        );
    }

    #[test]
    fn cmd_config_get_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::cmd_config_get(&cli, &printer, "profile");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No cfgd.yaml"));
    }

    // --- cmd_config_set ---

    #[test]
    fn cmd_config_set_updates_value() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_config_set(&cli, &printer, "profile", "work");
        assert!(
            result.is_ok(),
            "config set should succeed: {:?}",
            result.err()
        );

        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.profile, Some("work".to_string()));
    }

    #[test]
    fn cmd_config_set_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::cmd_config_set(&cli, &printer, "profile", "work");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    // --- cmd_config_unset ---

    #[test]
    fn cmd_config_unset_missing_key_fails() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_config_unset(&cli, &printer, "nonexistent");
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("not found"));
    }

    #[test]
    fn cmd_config_unset_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = super::cmd_config_unset(&cli, &printer, "profile");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    // --- cmd_doctor ---

    #[test]
    fn cmd_doctor_without_config_succeeds() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join("profiles")).unwrap();
        let cli = test_cli(dir.path());
        let (printer, buf) = Printer::for_test();

        super::cmd_doctor(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(output.contains("Doctor"), "missing Doctor header");
    }

    #[test]
    fn cmd_doctor_with_rich_config() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_doctor(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        assert!(output.contains("Doctor"), "missing Doctor header");
        assert!(
            output.contains("Package Managers"),
            "missing Package Managers section"
        );
    }

    // --- load_config_and_profile ---

    #[test]
    fn load_config_and_profile_returns_correct_config() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let (cfg, resolved) = super::load_config_and_profile(&cli, &printer).unwrap();
        assert_eq!(cfg.metadata.name, "t");
        assert!(
            !resolved.merged.env.is_empty(),
            "default profile should have env vars"
        );
    }

    #[test]
    fn load_config_and_profile_missing_profile_fails() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = Cli {
            profile: Some("nonexistent".to_string()),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let printer = test_printer();

        let result = super::load_config_and_profile(&cli, &printer);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("profile not found: nonexistent"),
            "expected 'profile not found: nonexistent' error, got: {msg}"
        );
    }

    // --- expand_aliases ---

    #[test]
    fn expand_aliases_config_flag_inline_value() {
        let args = vec![
            "cfgd".into(),
            "--config=/tmp/cfgd.yaml".into(),
            "status".into(),
        ];
        let result = super::expand_aliases(args.clone());
        assert_eq!(result, args);
    }

    // --- cmd_plan ---

    #[test]
    fn cmd_plan_invalid_phase_name_fails() {
        let h = CliTestHarness::builder().build();
        let args = PlanArgs {
            phase: Some("invalid_phase".to_string()),
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };
        let result = super::cmd_plan(&h.cli(), h.printer(), &args);
        assert_error_contains(&result, "Unknown phase");
    }

    #[test]
    fn cmd_plan_invalid_context_fails() {
        let h = CliTestHarness::builder().build();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "invalid".to_string(),
        };
        let result = super::cmd_plan(&h.cli(), h.printer(), &args);
        assert_error_contains(&result, "Unknown context");
    }

    #[test]
    fn cmd_plan_with_skip_filters_actions() {
        let h = CliTestHarness::builder().build();
        let args = PlanArgs {
            phase: None,
            skip: vec!["packages".to_string()],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };
        super::cmd_plan(&h.cli(), h.printer(), &args).unwrap();
        let output = h.output();
        assert!(
            output.contains("Plan") || output.contains("Phase"),
            "plan with skip should show plan, got: {output}"
        );
    }

    // --- cmd_diff ---

    #[test]
    fn cmd_diff_module_not_found_succeeds() {
        let h = CliTestHarness::builder().build();
        super::cmd_diff(&h.cli(), h.printer(), Some("nonexistent")).unwrap();
        let output = h.output();
        assert!(
            output.contains("not found") || output.contains("Diff"),
            "diff nonexistent module should show not-found or diff header, got: {output}"
        );
    }

    #[test]
    fn cmd_diff_with_module() {
        let h = CliTestHarness::builder()
            .module("diff-mod", "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: diff-mod\nspec:\n  packages: []\n")
            .build();
        super::cmd_diff(&h.cli(), h.printer(), Some("diff-mod")).unwrap();
        let output = h.output();
        assert!(
            output.contains("Diff") || output.contains("diff-mod"),
            "diff with module should show diff header or module name, got: {output}"
        );
    }

    // --- cmd_verify ---

    #[test]
    fn cmd_verify_with_module_filter() {
        let h = CliTestHarness::builder()
            .module("verify-mod", "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: verify-mod\nspec:\n  packages: []\n")
            .build();
        super::cmd_verify(&h.cli(), h.printer(), Some("verify-mod")).unwrap();
        let output = h.output();
        assert!(
            output.contains("Verify") || output.contains("verify-mod"),
            "verify with module should show header, got: {output}"
        );
    }

    // --- cmd_log ---

    #[test]
    fn cmd_log_empty_state_succeeds() {
        let h = CliTestHarness::builder().build();
        super::cmd_log(h.printer(), 10, None, Some(h.state_path())).unwrap();
        let output = h.output();
        assert!(
            output.contains("Apply History") || output.contains("No applies"),
            "log empty state should show history header, got: {output}"
        );
    }

    #[test]
    fn cmd_log_structured_json_output() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_log(h.printer(), 10, None, Some(h.state_path())).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["entries"]);
        assert_eq!(
            parsed["entries"],
            serde_json::json!([]),
            "fresh state should have no log entries"
        );
    }

    // --- cmd_apply ---

    #[test]
    fn cmd_apply_real_records_state() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            dry_run: false,
            yes: true,
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            from: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();

        let state = StateStore::open(&h.state_path().join("cfgd.db")).unwrap();
        let last = state.last_apply().unwrap();
        assert!(last.is_some(), "should have recorded an apply in state");
        let record = last.unwrap();
        assert_eq!(record.profile, "default");
        assert!(
            matches!(record.status, cfgd_core::state::ApplyStatus::Success),
            "apply record status should be Success, got: {:?}",
            record.status
        );
    }

    #[test]
    fn cmd_apply_invalid_phase_fails() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            dry_run: true,
            yes: true,
            phase: Some("bogus_phase".to_string()),
            skip: vec![],
            only: vec![],
            module: None,
            from: None,
            skip_scripts: false,
        };
        let result = super::cmd_apply(&h.cli(), h.printer(), &args);
        assert_error_contains(&result, "Unknown phase");
    }

    #[test]
    fn cmd_apply_with_skip_and_only() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            dry_run: true,
            yes: true,
            phase: None,
            skip: vec!["packages".to_string()],
            only: vec![],
            module: None,
            from: None,
            skip_scripts: false,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        let output = h.output();
        assert!(
            output.contains("Apply")
                || output.contains("Plan")
                || output.contains("Nothing")
                || output.contains("dry"),
            "apply dry-run with skip should produce output, got: {output}"
        );
    }

    #[test]
    fn cmd_apply_skip_scripts_flag() {
        let h = CliTestHarness::builder().build();
        let args = ApplyArgs {
            dry_run: true,
            yes: true,
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            from: None,
            skip_scripts: true,
        };
        super::cmd_apply(&h.cli(), h.printer(), &args).unwrap();
        let output = h.output();
        assert!(
            output.contains("Apply")
                || output.contains("Plan")
                || output.contains("Nothing")
                || output.contains("dry"),
            "apply dry-run with skip-scripts should produce output, got: {output}"
        );
    }

    // --- cmd_compliance ---

    #[test]
    fn cmd_compliance_history_invalid_since_fails() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_compliance_history(&h.cli(), h.printer(), Some("invalid"));
        assert_error_contains(&result, "invalid --since value");
    }

    #[test]
    fn cmd_compliance_diff_missing_snapshots_fails() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_compliance_diff(&h.cli(), h.printer(), 1, 2);
        assert_error_contains(&result, "not found");
    }

    // --- cmd_decide ---

    #[test]
    fn cmd_decide_invalid_action_fails() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        let result = super::cmd_decide(
            &printer,
            "invalid",
            None,
            None,
            true,
            Some(state_dir.path()),
        );
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown action"));
    }

    // --- cmd_source_remove ---

    #[test]
    fn cmd_source_remove_existing_removes_from_config() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.sources.len(), 1);

        let result = super::cmd_source_remove(&cli, &printer, "team-config", false, true);
        assert!(
            result.is_ok(),
            "source remove should succeed: {:?}",
            result.err()
        );

        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert!(cfg.spec.sources.is_empty());
    }

    #[test]
    fn cmd_source_remove_nonexistent_fails() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_remove(&cli, &printer, "nonexistent", false, true);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("Source 'nonexistent' not found"),
            "expected 'Source not found' error, got: {msg}"
        );
    }

    // --- cmd_source_override ---

    #[test]
    fn cmd_source_override_reject_succeeds() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_source_override(
            &cli,
            &printer,
            "team-config",
            "reject",
            "packages.brew",
            None,
        )
        .unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Rejected") && output.contains("packages.brew"),
            "should confirm rejection of packages.brew, got: {output}"
        );
    }

    #[test]
    fn cmd_source_override_set_succeeds() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        super::cmd_source_override(
            &cli,
            &printer,
            "team-config",
            "set",
            "packages.brew.ripgrep",
            Some("true"),
        )
        .unwrap();

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Override set") && output.contains("packages.brew.ripgrep"),
            "should confirm override set for packages.brew.ripgrep, got: {output}"
        );
    }

    #[test]
    fn cmd_source_override_invalid_action_fails() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_override(
            &cli,
            &printer,
            "team-config",
            "invalid",
            "packages.brew",
            None,
        );
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown action"));
    }

    #[test]
    fn cmd_source_override_nonexistent_source_fails() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_override(
            &cli,
            &printer,
            "nonexistent",
            "reject",
            "packages.brew",
            None,
        );
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("Source 'nonexistent' not found"),
            "expected 'Source not found' error, got: {msg}"
        );
    }

    // --- cmd_source_priority ---

    #[test]
    fn cmd_source_priority_nonexistent_fails() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_priority(&cli, &printer, "nonexistent", None);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("source 'nonexistent' not found"),
            "expected 'source not found' error, got: {msg}"
        );
    }

    #[test]
    fn cmd_source_priority_updates_config() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = super::cmd_source_priority(&cli, &printer, "team-config", Some(200));
        assert!(result.is_ok(), "source priority update: {:?}", result.err());

        let cfg = config::load_config(&config_dir.path().join("cfgd.yaml")).unwrap();
        assert_eq!(cfg.spec.sources[0].subscription.priority, 200);
    }

    // --- cmd_source_show ---

    #[test]
    fn cmd_source_show_exists() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_source_show(&cli, &printer, "team-config");
        assert!(
            result.is_ok(),
            "source show should succeed: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("team-config") || output.contains("Source"),
            "source show should display source info, got: {output}"
        );
    }

    #[test]
    fn cmd_source_show_structured_json() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_source_show(&cli, &printer, "team-config").unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        assert_eq!(parsed["name"], "team-config");
        assert_eq!(parsed["url"], "https://github.com/team/config");
        assert_eq!(parsed["priority"], 100);
    }

    // --- cmd_source_create ---

    #[test]
    fn cmd_source_create_initializes_manifest() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        // source create writes manifest in the config directory itself
        let result = super::cmd_source_create(
            &cli,
            &printer,
            Some("my-source"),
            Some("A test source"),
            Some("1.0.0"),
        );
        assert!(
            result.is_ok(),
            "source create should succeed: {:?}",
            result.err()
        );

        // Verify the manifest file was created with expected content
        let manifest_path = config_dir.path().join("cfgd-source.yaml");
        assert!(manifest_path.exists(), "cfgd-source.yaml should be created");
        let content = std::fs::read_to_string(&manifest_path).unwrap();
        assert!(
            content.contains("my-source"),
            "manifest should contain source name, got: {content}"
        );
    }

    // --- cmd_source_list ---

    #[test]
    fn cmd_source_list_with_sources_shows_entries() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_source_list(&cli, &printer);
        assert!(
            result.is_ok(),
            "source list with sources: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("team-config") || output.contains("Config Sources"),
            "source list should show source entries, got: {output}"
        );
    }

    #[test]
    fn cmd_source_list_structured_json() {
        let (config_dir, state_dir) = setup_rich_test_env();
        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        super::cmd_source_list(&cli, &printer).unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        let arr = parsed
            .as_array()
            .expect("source list JSON should be an array");
        assert_eq!(arr.len(), 1, "rich config has one source");
        assert_eq!(arr[0]["name"], "team-config");
        assert_eq!(arr[0]["url"], "https://github.com/team/config");
    }

    // --- cmd_workflow_generate ---

    #[test]
    fn cmd_workflow_generate_with_git_repo() {
        let (config_dir, state_dir) = setup_test_env();
        std::process::Command::new("git")
            .args(["init"])
            .current_dir(config_dir.path())
            .output()
            .ok();

        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        let result = super::cmd_workflow_generate(&cli, &printer, true);
        assert!(
            result.is_ok(),
            "workflow generate with git repo should succeed: {:?}",
            result.err()
        );

        let workflow_dir = config_dir.path().join(".github").join("workflows");
        assert!(
            workflow_dir.exists(),
            "workflow directory should be created"
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("Generated") || output.contains("workflow"),
            "workflow generate should mention generated workflow, got: {output}"
        );
    }

    // --- cmd_rollback ---

    #[test]
    fn cmd_rollback_missing_apply_id_fails() {
        let state_dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        let result = super::cmd_rollback(&printer, 99, true, Some(state_dir.path()));
        assert!(result.is_err(), "rollback with no history should fail");
    }

    // --- open_state_store ---

    #[test]
    fn open_state_store_creates_db_file() {
        let state_dir = tempfile::tempdir().unwrap();

        let result = super::open_state_store(Some(state_dir.path()));
        assert!(
            result.is_ok(),
            "open_state_store should succeed: {:?}",
            result.err()
        );

        let db_path = state_dir.path().join("cfgd.db");
        assert!(db_path.exists(), "cfgd.db file should be created");
        assert!(
            std::fs::metadata(&db_path).unwrap().len() > 0,
            "cfgd.db should not be empty"
        );
    }

    // --- module commands ---

    #[test]
    fn cmd_module_search_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result = module::cmd_module_search(&cli, &printer, "test");
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No config found"),
            "expected 'No config found' error, got: {msg}"
        );
    }

    #[test]
    fn cmd_module_search_no_registries() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let (printer, buf) = Printer::for_test();

        // Config exists but has no module registries
        let result = module::cmd_module_search(&cli, &printer, "test");
        assert!(
            result.is_ok(),
            "search with no registries should succeed: {:?}",
            result.err()
        );

        let output = buf.lock().unwrap();
        assert!(
            output.contains("No module registries") || output.contains("Search"),
            "search with no registries should say no registries, got: {output}"
        );
    }

    #[test]
    fn cmd_module_search_no_registries_structured() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = Cli {
            output: OutputFormatArg(cfgd_core::output::OutputFormat::Json),
            ..test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()))
        };
        let (printer, buf) = Printer::for_test_with_format(cfgd_core::output::OutputFormat::Json);

        module::cmd_module_search(&cli, &printer, "test").unwrap();

        let output = buf.lock().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        let arr = parsed.as_array().expect("search JSON should be an array");
        assert_eq!(arr.len(), 0, "no registries should yield zero results");
    }

    #[test]
    fn cmd_module_build_no_module_yaml_fails() {
        let dir = tempfile::tempdir().unwrap();
        let printer = test_printer();

        let result = module::cmd_module_build(
            &printer,
            &dir.path().display().to_string(),
            None,
            None,
            None,
            false,
            None,
        );
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("does not contain a module.yaml")
        );
    }

    #[test]
    fn cmd_module_keys_generate_no_cosign_fails() {
        if cfgd_core::command_available("cosign") {
            // Skip test if cosign is actually available
            return;
        }
        let printer = test_printer();

        let result = module::cmd_module_keys_generate(&printer, None);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("cosign not found"));
    }

    #[test]
    fn cmd_module_keys_rotate_no_cosign_fails() {
        if cfgd_core::command_available("cosign") {
            return;
        }
        let printer = test_printer();

        let result = module::cmd_module_keys_rotate(&printer, None, &[]);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("cosign not found"));
    }

    #[test]
    fn cmd_module_add_from_registry_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();

        let result =
            module::cmd_module_add_from_registry(&cli, &printer, "myregistry/mymod", false, false);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("No cfgd.yaml found"),
            "expected 'No cfgd.yaml found' error, got: {msg}"
        );
    }

    #[test]
    fn cmd_module_add_from_registry_invalid_ref_fails() {
        let (config_dir, state_dir) = setup_test_env();
        let cli = test_cli_with_state(config_dir.path(), Some(state_dir.path().to_path_buf()));
        let printer = test_printer();

        let result = module::cmd_module_add_from_registry(&cli, &printer, "no-slash", false, false);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("Invalid registry reference")
        );
    }

    #[test]
    fn cmd_module_add_from_registry_not_configured_fails() {
        let h = CliTestHarness::builder().build();
        let result = module::cmd_module_add_from_registry(
            &h.cli(),
            h.printer(),
            "myregistry/mymod@v1.0",
            false,
            false,
        );
        assert_error_contains(&result, "not configured");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_status_module
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_status_module_not_found_output() {
        let h = CliTestHarness::builder().build();
        super::cmd_status_module(&h.cli(), h.printer(), "nonexistent").unwrap();
        h.assert_output_contains("nonexistent");
        h.assert_output_contains("not found");
    }

    #[test]
    fn cmd_status_module_not_found_json() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_status_module(&h.cli(), h.printer(), "ghost-mod").unwrap();
        let parsed = h.json_output();
        assert_eq!(parsed["name"], "ghost-mod");
        assert_eq!(parsed["status"], "not found");
        assert_eq!(parsed["packages"], 0);
        assert_eq!(parsed["files"], 0);
    }

    #[test]
    fn cmd_status_module_found_output() {
        let h = CliTestHarness::builder()
            .module("my-mod", "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: my-mod\nspec:\n  packages:\n    - name: ripgrep\n  files: []\n")
            .build();
        super::cmd_status_module(&h.cli(), h.printer(), "my-mod").unwrap();
        h.assert_output_contains("my-mod");
        // Status shows package count, not individual package names
        h.assert_output_contains("1");
    }

    #[test]
    fn cmd_status_module_found_json() {
        let h = CliTestHarness::builder()
            .json()
            .module("my-mod", "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: my-mod\nspec:\n  packages:\n    - name: ripgrep\n  files: []\n  depends:\n    - base\n")
            .build();
        super::cmd_status_module(&h.cli(), h.printer(), "my-mod").unwrap();
        let parsed = h.json_output();
        assert_eq!(parsed["name"], "my-mod");
        assert_eq!(parsed["packages"], 1);
        assert_eq!(parsed["files"], 0);
        assert_eq!(parsed["status"], "not applied");
        assert_json_has_fields(
            &parsed,
            &[
                "name",
                "packages",
                "files",
                "depends",
                "status",
                "lastApplied",
            ],
        );
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_source_add error paths
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_add_duplicate_fails() {
        let h = CliTestHarness::builder().rich_config().build();
        let args = SourceAddArgs {
            url: "https://github.com/team/config".to_string(),
            name: Some("team-config".to_string()),
            branch: None,
            profile: None,
            accept_recommended: false,
            priority: None,
            opt_in: vec![],
            sync_interval: None,
            auto_apply: false,
            pin_version: None,
            yes: true,
        };
        let result = super::cmd_source_add(&h.cli(), h.printer(), &args);
        assert_error_contains(&result, "already exists");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_pull / cmd_sync output
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_pull_non_git_dir_shows_warning() {
        let h = CliTestHarness::builder().build();
        // config dir is not a git repo, so git_pull_sync will fail gracefully
        super::cmd_pull(&h.cli(), h.printer()).unwrap();
        h.assert_header("Pull");
        // Should either show "up to date" or "Pull failed" — both are OK
        let output = h.output();
        assert!(
            output.contains("Pull failed") || output.contains("up to date"),
            "pull in non-git dir should warn or show up-to-date, got: {output}"
        );
    }

    #[test]
    fn cmd_sync_non_git_dir_shows_output() {
        let h = CliTestHarness::builder().build();
        super::cmd_sync(&h.cli(), h.printer()).unwrap();
        h.assert_header("Sync");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_config_edit error path
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_config_edit_no_config_fails() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let printer = test_printer();
        let result = super::cmd_config_edit(&cli, &printer);
        assert!(result.is_err());
        assert_error_contains(&result, "No cfgd.yaml");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_source_update error paths
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_update_no_sources_succeeds() {
        let h = CliTestHarness::builder().build();
        super::cmd_source_update(&h.cli(), h.printer(), None).unwrap();
        h.assert_header("Update Sources");
        h.assert_output_contains("No sources configured");
    }

    #[test]
    fn cmd_source_update_named_not_found_fails() {
        // Need a config with sources so it doesn't take the "no sources" early return
        let h = CliTestHarness::builder().rich_config().build();
        let result = super::cmd_source_update(&h.cli(), h.printer(), Some("nonexistent"));
        assert_error_contains(&result, "not found");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_source_replace error path
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_replace_nonexistent_fails() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_source_replace(
            &h.cli(),
            h.printer(),
            "nonexistent",
            "https://github.com/new/config.git",
        );
        assert_error_contains(&result, "not found");
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_compliance_snapshot / cmd_compliance_export
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_compliance_snapshot_empty_state() {
        let h = CliTestHarness::builder().build();
        super::cmd_compliance_snapshot(&h.cli(), h.printer()).unwrap();
        let output = h.output();
        assert!(
            output.contains("Compliance") || output.contains("Snapshot"),
            "compliance snapshot should produce compliance-related output, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_export_empty_state() {
        let h = CliTestHarness::builder().build();
        super::cmd_compliance_export(&h.cli(), h.printer()).unwrap();
        // export writes to a file and prints success message
        let output = h.output();
        assert!(
            output.contains("snapshot") || output.contains("Compliance"),
            "compliance export should mention snapshot, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // New coverage: cmd_compliance structured output
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_compliance_snapshot_json() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_compliance_snapshot(&h.cli(), h.printer()).unwrap();
        let parsed = h.json_output();
        // Compliance snapshot JSON wraps a snapshot object
        assert_json_has_fields(&parsed, &["snapshot"]);
    }

    #[test]
    fn cmd_compliance_history_json() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_compliance_history(&h.cli(), h.printer(), None).unwrap();
        let output = h.output();
        // History output may be an array or object — just verify it's valid JSON
        let _parsed: serde_json::Value = serde_json::from_str(&output)
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
    }

    // -----------------------------------------------------------------------
    // New coverage: execute dispatch for remaining commands
    // -----------------------------------------------------------------------

    // execute dispatch tests for plan/sync/pull/compliance already exist above

    // -----------------------------------------------------------------------
    // JSON schema tests for --output json commands
    // -----------------------------------------------------------------------

    #[test]
    fn json_schema_status() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_status(&h.cli(), h.printer(), None).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(
            &parsed,
            &[
                "lastApply",
                "drift",
                "modules",
                "sources",
                "pendingDecisions",
            ],
        );
        assert_json_field_type(&parsed, "modules", "array");
        assert_json_field_type(&parsed, "sources", "array");
        assert_json_field_type(&parsed, "pendingDecisions", "array");
    }

    #[test]
    fn json_schema_status_module() {
        // Module dir name must match metadata.name in module.yaml
        let h = CliTestHarness::builder()
            .json()
            .module("test-mod", SIMPLE_MODULE_YAML)
            .build();
        super::cmd_status_module(&h.cli(), h.printer(), "test-mod").unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(
            &parsed,
            &[
                "name",
                "packages",
                "files",
                "depends",
                "status",
                "lastApplied",
            ],
        );
        assert_json_field_type(&parsed, "name", "string");
        assert_json_field_type(&parsed, "packages", "number");
        assert_json_field_type(&parsed, "files", "number");
        assert_json_field_type(&parsed, "depends", "array");
        assert_json_field_type(&parsed, "status", "string");
    }

    #[test]
    fn json_schema_log() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_log(h.printer(), 10, None, Some(h.state_path())).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["entries"]);
        assert_json_field_type(&parsed, "entries", "array");
    }

    #[test]
    fn json_schema_plan() {
        let h = CliTestHarness::builder().json().build();
        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };
        super::cmd_plan(&h.cli(), h.printer(), &args).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["context", "phases", "totalActions"]);
        assert_json_field_type(&parsed, "context", "string");
        assert_json_field_type(&parsed, "phases", "array");
        assert_json_field_type(&parsed, "totalActions", "number");
    }

    #[test]
    fn json_schema_config_show() {
        let h = CliTestHarness::builder().json().rich_config().build();
        super::cmd_config_show(&h.cli(), h.printer()).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["metadata", "spec"]);
        assert_json_field_type(&parsed, "metadata", "object");
        assert_json_field_type(&parsed, "spec", "object");
    }

    #[test]
    fn json_schema_doctor() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_doctor(&h.cli(), h.printer()).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(
            &parsed,
            &["config", "packageManagers", "systemConfigurators"],
        );
        assert_json_field_type(&parsed, "config", "object");
        assert_json_field_type(&parsed, "packageManagers", "array");
        assert_json_field_type(&parsed, "systemConfigurators", "array");
    }

    #[test]
    fn json_schema_verify() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_verify(&h.cli(), h.printer(), None).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["passCount", "failCount", "results"]);
        assert_json_field_type(&parsed, "passCount", "number");
        assert_json_field_type(&parsed, "failCount", "number");
        assert_json_field_type(&parsed, "results", "array");
    }

    #[test]
    fn json_schema_source_list() {
        let h = CliTestHarness::builder().json().rich_config().build();
        super::cmd_source_list(&h.cli(), h.printer()).unwrap();
        let output = h.output();
        // Source list writes a JSON array; output may have trailing newlines from capture()
        // Use serde_json::from_str on trimmed output, finding the JSON portion
        let start = output
            .find('[')
            .unwrap_or_else(|| panic!("no JSON array in output: {output}"));
        // Find the matching closing bracket
        let json_substr = &output[start..];
        let end = json_substr.rfind(']').expect("no closing ] in JSON") + 1;
        let parsed: serde_json::Value = serde_json::from_str(&json_substr[..end])
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {json_substr}"));
        assert!(parsed.is_array(), "source list JSON should be an array");
        let arr = parsed.as_array().unwrap();
        assert_eq!(arr.len(), 1, "rich config has 1 source");
        assert_json_has_fields(&arr[0], &["name", "url", "priority", "status"]);
    }

    #[test]
    fn json_schema_source_show() {
        let h = CliTestHarness::builder().json().rich_config().build();
        super::cmd_source_show(&h.cli(), h.printer(), "team-config").unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(&parsed, &["name", "url"]);
    }

    // -----------------------------------------------------------------------
    // format_bytes — pure function boundary tests
    // -----------------------------------------------------------------------

    #[test]
    fn format_bytes_zero() {
        assert_eq!(super::format_bytes(0), "0 B");
    }

    #[test]
    fn format_bytes_small_value() {
        assert_eq!(super::format_bytes(512), "512 B");
    }

    #[test]
    fn format_bytes_just_below_kb_boundary() {
        assert_eq!(super::format_bytes(1023), "1023 B");
    }

    #[test]
    fn format_bytes_exact_kb_boundary() {
        assert_eq!(super::format_bytes(1024), "1.0 KB");
    }

    #[test]
    fn format_bytes_fractional_kb() {
        // 1536 bytes = 1.5 KB
        assert_eq!(super::format_bytes(1536), "1.5 KB");
    }

    #[test]
    fn format_bytes_just_below_mb_boundary() {
        // 1024*1024 - 1 = 1048575
        assert_eq!(super::format_bytes(1048575), "1024.0 KB");
    }

    #[test]
    fn format_bytes_exact_mb_boundary() {
        assert_eq!(super::format_bytes(1024 * 1024), "1.0 MB");
    }

    #[test]
    fn format_bytes_large_mb_value() {
        // 52428800 = 50 MB
        assert_eq!(super::format_bytes(52_428_800), "50.0 MB");
    }

    #[test]
    fn format_bytes_fractional_mb() {
        // 1.5 MB = 1572864
        assert_eq!(super::format_bytes(1_572_864), "1.5 MB");
    }

    // -----------------------------------------------------------------------
    // cmd_secret_init — creates age key and .sops.yaml
    // -----------------------------------------------------------------------

    #[test]
    fn secret_init_prints_header_and_key_path() {
        // cmd_secret_init calls secrets::init_age_key which shells out to age-keygen.
        // If age-keygen is not installed, the error message should mention it.
        let h = CliTestHarness::builder().build();
        let result = super::cmd_secret_init(&h.cli(), h.printer());

        match result {
            Ok(()) => {
                // age-keygen was available: verify output mentions key path and completion
                let output = h.output();
                assert!(
                    output.contains("Secret Init"),
                    "expected 'Secret Init' header, got: {output}"
                );
                assert!(
                    output.contains("Age key:"),
                    "expected key path in output, got: {output}"
                );
                assert!(
                    output.contains("Secrets setup complete"),
                    "expected completion message, got: {output}"
                );
            }
            Err(e) => {
                // age-keygen not installed: error should mention it
                let msg = e.to_string();
                assert!(
                    msg.contains("age-keygen"),
                    "expected error mentioning age-keygen, got: {msg}"
                );
            }
        }
    }

    // -----------------------------------------------------------------------
    // resolve_secret_backend / get_secret_backend — error paths
    // -----------------------------------------------------------------------

    #[test]
    fn resolve_secret_backend_file_not_found() {
        let h = CliTestHarness::builder().rich_config().build();
        let nonexistent = h.config_path().join("does-not-exist.yaml");
        let result = super::resolve_secret_backend(&h.cli(), &nonexistent);
        assert_error_contains(&result.map(|_| ()), "File not found");
    }

    #[test]
    fn get_secret_backend_file_not_found() {
        let h = CliTestHarness::builder().rich_config().build();
        let nonexistent = h.config_path().join("nonexistent-secret.yaml");
        let result = super::get_secret_backend(&h.cli(), &nonexistent);
        assert_error_contains(&result.map(|_| ()), "File not found");
    }

    #[test]
    fn resolve_secret_backend_no_config_file_errors() {
        // When no config file exists, resolve_secret_backend should fail because
        // load_config can't find the config file.
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli(dir.path());
        let nonexistent = dir.path().join("secret.enc.yaml");
        let result = super::resolve_secret_backend(&cli, &nonexistent);
        // Config file missing: load_config will fail
        match result {
            Ok(_) => panic!("expected error when config file is missing"),
            Err(e) => {
                let msg = e.to_string();
                assert!(
                    msg.contains("config file not found") || msg.contains("File not found"),
                    "expected config or file error, got: {msg}"
                );
            }
        }
    }

    #[test]
    fn cmd_secret_encrypt_file_not_found() {
        let h = CliTestHarness::builder().rich_config().build();
        let nonexistent = h.config_path().join("missing.enc.yaml");
        let result = super::cmd_secret_encrypt(&h.cli(), h.printer(), &nonexistent);
        assert_error_contains(&result, "File not found");
    }

    #[test]
    fn cmd_secret_decrypt_file_not_found() {
        let h = CliTestHarness::builder().rich_config().build();
        let nonexistent = h.config_path().join("missing.enc.yaml");
        let result = super::cmd_secret_decrypt(&h.cli(), h.printer(), &nonexistent);
        assert_error_contains(&result, "File not found");
    }

    // -----------------------------------------------------------------------
    // cmd_daemon_status — no daemon running
    // -----------------------------------------------------------------------

    #[test]
    fn daemon_status_no_daemon_running_human_output() {
        let h = CliTestHarness::builder().build();
        super::cmd_daemon_status(h.printer()).unwrap();
        let output = h.output();
        assert!(
            output.contains("Daemon Status"),
            "expected 'Daemon Status' header, got: {output}"
        );
        assert!(
            output.contains("not running"),
            "expected 'not running' message, got: {output}"
        );
        assert!(
            output.contains("cfgd daemon"),
            "expected start hint, got: {output}"
        );
        assert!(
            output.contains("cfgd daemon install"),
            "expected install hint, got: {output}"
        );
    }

    #[test]
    fn daemon_status_no_daemon_running_json_output() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_daemon_status(h.printer()).unwrap();
        let parsed = h.json_output();
        assert_json_has_fields(
            &parsed,
            &["running", "pid", "uptimeSecs", "driftCount", "sources"],
        );
        assert_json_field_type(&parsed, "running", "bool");
        assert!(
            !parsed.get("running").unwrap().as_bool().unwrap(),
            "running should be false when no daemon is present"
        );
        assert_eq!(
            parsed.get("pid").unwrap().as_u64().unwrap(),
            0,
            "pid should be 0 when no daemon is present"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_daemon_uninstall — output and completion
    // -----------------------------------------------------------------------

    #[test]
    fn daemon_uninstall_prints_platform_info_and_succeeds() {
        let (printer, buf) = Printer::for_test();
        // On Linux (CI/test env), uninstall_service just removes the unit file
        // if present; in a clean test env there is nothing to remove, so it succeeds.
        let result = super::cmd_daemon_uninstall(&printer);
        let output = buf.lock().unwrap().clone();

        assert!(
            output.contains("Uninstall Daemon Service"),
            "expected header, got: {output}"
        );

        // On Linux, the function prints the systemctl stop message
        #[cfg(target_os = "linux")]
        assert!(
            output.contains("systemctl --user disable --now cfgd.service"),
            "expected systemctl uninstall message on Linux, got: {output}"
        );

        // The call should succeed (no unit file to remove in test env)
        assert!(result.is_ok(), "expected success, got: {:?}", result.err());

        // After success, the completion message is printed
        let output_after = buf.lock().unwrap().clone();
        assert!(
            output_after.contains("Daemon service removed"),
            "expected completion message, got: {output_after}"
        );
    }

    // -----------------------------------------------------------------------
    // print_daemon_install_success — platform-specific messages
    // -----------------------------------------------------------------------

    #[cfg(unix)]
    #[test]
    fn print_daemon_install_success_linux_messages() {
        let (printer, buf) = Printer::for_test();
        super::print_daemon_install_success(&printer);
        let output = buf.lock().unwrap().clone();

        if cfg!(target_os = "macos") {
            assert!(
                output.contains("launchd service"),
                "expected launchd message on macOS, got: {output}"
            );
            assert!(
                output.contains("launchctl load"),
                "expected launchctl load hint on macOS, got: {output}"
            );
        } else {
            assert!(
                output.contains("systemd user service"),
                "expected systemd message on Linux, got: {output}"
            );
            assert!(
                output.contains("systemctl --user enable --now cfgd.service"),
                "expected systemctl enable hint on Linux, got: {output}"
            );
        }
    }

    // -----------------------------------------------------------------------
    // cmd_checkin — error when config has no profile
    // -----------------------------------------------------------------------

    #[test]
    fn checkin_fails_when_no_profile_configured() {
        let no_profile_config =
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec: {}\n";
        let h = CliTestHarness::builder().config(no_profile_config).build();
        let result = super::cmd_checkin(&h.cli(), h.printer(), "http://localhost:8080", None, None);
        assert_error_contains(&result, "no profile configured");
    }

    #[test]
    fn checkin_fails_when_config_file_missing() {
        let dir = tempfile::tempdir().unwrap();
        // Don't write any config file
        let cli = test_cli(dir.path());
        let printer = test_printer();
        let result = super::cmd_checkin(&cli, &printer, "http://localhost:8080", None, None);
        assert_error_contains(&result, "config file not found");
    }

    #[test]
    fn checkin_fails_when_profile_does_not_exist() {
        let bad_profile_config = "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: t\nspec:\n  profile: nonexistent\n";
        let h = CliTestHarness::builder().config(bad_profile_config).build();
        let result = super::cmd_checkin(&h.cli(), h.printer(), "http://localhost:8080", None, None);
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(
            msg.contains("profile not found: nonexistent"),
            "expected 'profile not found' error, got: {msg}"
        );
    }

    // -----------------------------------------------------------------------
    // display_policy_items tests
    // -----------------------------------------------------------------------

    #[test]
    fn display_policy_items_shows_packages() {
        let (printer, buf) = Printer::for_test();
        let items = config::PolicyItems {
            packages: Some(config::PackagesSpec {
                brew: Some(config::BrewSpec {
                    file: None,
                    taps: vec![],
                    formulae: vec!["git".into(), "curl".into()],
                    casks: vec!["firefox".into()],
                }),
                apt: Some(config::AptSpec {
                    file: None,
                    packages: vec!["build-essential".into()],
                }),
                cargo: Some(config::CargoSpec {
                    file: None,
                    packages: vec!["ripgrep".into()],
                }),
                pipx: vec!["black".into()],
                dnf: vec!["vim".into()],
                npm: Some(config::NpmSpec {
                    file: None,
                    global: vec!["typescript".into()],
                }),
                ..Default::default()
            }),
            ..Default::default()
        };
        super::display_policy_items(&printer, &items, "  ");
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("brew formula: git"),
            "missing 'brew formula: git' in: {output}"
        );
        assert!(
            output.contains("brew formula: curl"),
            "missing 'brew formula: curl' in: {output}"
        );
        assert!(
            output.contains("brew cask: firefox"),
            "missing 'brew cask: firefox' in: {output}"
        );
        assert!(
            output.contains("apt: build-essential"),
            "missing 'apt: build-essential' in: {output}"
        );
        assert!(
            output.contains("cargo: ripgrep"),
            "missing 'cargo: ripgrep' in: {output}"
        );
        assert!(
            output.contains("pipx: black"),
            "missing 'pipx: black' in: {output}"
        );
        assert!(
            output.contains("dnf: vim"),
            "missing 'dnf: vim' in: {output}"
        );
        assert!(
            output.contains("npm: typescript"),
            "missing 'npm: typescript' in: {output}"
        );
    }

    #[test]
    fn display_policy_items_shows_files_env_system() {
        let (printer, buf) = Printer::for_test();
        let mut system = std::collections::HashMap::new();
        system.insert("sysctl".into(), serde_yaml::Value::Null);
        let items = config::PolicyItems {
            files: vec![config::ManagedFileSpec {
                source: "bashrc".into(),
                target: std::path::PathBuf::from("/home/user/.bashrc"),
                strategy: None,
                private: false,
                origin: None,
                encryption: None,
                permissions: None,
            }],
            env: vec![config::EnvVar {
                name: "EDITOR".into(),
                value: "vim".into(),
            }],
            system,
            ..Default::default()
        };
        super::display_policy_items(&printer, &items, "");
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("file: /home/user/.bashrc"),
            "missing file line in: {output}"
        );
        assert!(
            output.contains("env: EDITOR"),
            "missing env line in: {output}"
        );
        assert!(
            output.contains("system: sysctl"),
            "missing system line in: {output}"
        );
    }

    #[test]
    fn display_policy_items_empty_prints_nothing() {
        let (printer, buf) = Printer::for_test();
        let items = config::PolicyItems::default();
        super::display_policy_items(&printer, &items, "  ");
        let output = buf.lock().unwrap().clone();
        assert!(
            output.is_empty(),
            "expected empty output for empty items, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // display_pending_decisions tests
    // -----------------------------------------------------------------------

    fn make_decision(
        id: i64,
        source: &str,
        resource: &str,
        tier: &str,
        action: &str,
        summary: &str,
    ) -> cfgd_core::state::PendingDecision {
        cfgd_core::state::PendingDecision {
            id,
            source: source.into(),
            resource: resource.into(),
            tier: tier.into(),
            action: action.into(),
            summary: summary.into(),
            created_at: "2026-01-01T00:00:00Z".into(),
            resolved_at: None,
            resolution: None,
        }
    }

    #[test]
    fn display_pending_decisions_groups_by_source() {
        let (printer, buf) = Printer::for_test();
        let decisions = vec![
            make_decision(1, "alpha", "pkg/git", "required", "install", "Install git"),
            make_decision(
                2,
                "alpha",
                "pkg/curl",
                "recommended",
                "install",
                "Install curl",
            ),
            make_decision(3, "beta", "env/EDITOR", "optional", "set", "Set EDITOR"),
        ];
        super::display_pending_decisions(&printer, &decisions);
        let output = buf.lock().unwrap().clone();
        // alpha has 2 items (plural)
        assert!(
            output.contains("alpha: 2 pending items"),
            "missing 'alpha: 2 pending items' in: {output}"
        );
        // beta has 1 item (singular)
        assert!(
            output.contains("beta: 1 pending item"),
            "missing 'beta: 1 pending item' in: {output}"
        );
        // Check individual items are listed
        assert!(
            output.contains("required pkg/git"),
            "missing 'required pkg/git' in: {output}"
        );
        assert!(
            output.contains("recommended pkg/curl"),
            "missing 'recommended pkg/curl' in: {output}"
        );
        assert!(
            output.contains("optional env/EDITOR"),
            "missing 'optional env/EDITOR' in: {output}"
        );
        // Verify summary and action format
        assert!(
            output.contains("Install git (install)"),
            "missing decision detail in: {output}"
        );
        assert!(
            output.contains("Set EDITOR (set)"),
            "missing decision detail in: {output}"
        );
    }

    #[test]
    fn display_pending_decisions_single_item_singular() {
        let (printer, buf) = Printer::for_test();
        let decisions = vec![make_decision(
            1,
            "solo-source",
            "file/bashrc",
            "required",
            "create",
            "Create bashrc",
        )];
        super::display_pending_decisions(&printer, &decisions);
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("solo-source: 1 pending item\n")
                || output.contains("solo-source: 1 pending item"),
            "expected singular 'item' not 'items' in: {output}"
        );
        assert!(
            !output.contains("pending items"),
            "should not contain plural 'items' in: {output}"
        );
        assert!(
            output.contains("required file/bashrc"),
            "missing item detail in: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // add_source_to_config tests
    // -----------------------------------------------------------------------

    #[test]
    fn add_source_to_config_appends_to_existing_sources() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(
            &config_path,
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
  sources:
    - name: existing
      origin:
        type: Git
        url: https://example.com/existing
        branch: main
"#,
        )
        .unwrap();

        let source = config::SourceSpec {
            name: "new-source".into(),
            origin: config::OriginSpec {
                origin_type: config::OriginType::Git,
                url: "https://example.com/new".into(),
                branch: "main".into(),
                auth: None,
                ssh_strict_host_key_checking: config::SshHostKeyPolicy::AcceptNew,
            },
            subscription: config::SubscriptionSpec::default(),
            sync: config::SourceSyncSpec::default(),
        };

        let result = super::add_source_to_config(&config_path, &source);
        assert!(
            result.is_ok(),
            "add_source_to_config failed: {:?}",
            result.err()
        );

        let written = std::fs::read_to_string(&config_path).unwrap();
        let parsed: serde_yaml::Value = serde_yaml::from_str(&written).unwrap();
        let sources = parsed["spec"]["sources"].as_sequence().unwrap();
        assert_eq!(
            sources.len(),
            2,
            "expected 2 sources after append, got {}",
            sources.len()
        );
        assert_eq!(sources[0]["name"].as_str().unwrap(), "existing");
        assert_eq!(sources[1]["name"].as_str().unwrap(), "new-source");
        assert_eq!(
            sources[1]["origin"]["url"].as_str().unwrap(),
            "https://example.com/new"
        );
    }

    #[test]
    fn add_source_to_config_errors_on_missing_file() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("nonexistent.yaml");
        let source = config::SourceSpec {
            name: "test".into(),
            origin: config::OriginSpec {
                origin_type: config::OriginType::Git,
                url: "https://example.com".into(),
                branch: "main".into(),
                auth: None,
                ssh_strict_host_key_checking: config::SshHostKeyPolicy::AcceptNew,
            },
            subscription: config::SubscriptionSpec::default(),
            sync: config::SourceSyncSpec::default(),
        };

        let result = super::add_source_to_config(&config_path, &source);
        assert!(result.is_err(), "expected error for missing file");
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("Config file not found"),
            "expected 'Config file not found', got: {msg}"
        );
    }

    #[test]
    fn add_source_to_config_creates_sources_array_when_absent() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(
            &config_path,
            r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: test
spec:
  profile: default
"#,
        )
        .unwrap();

        let source = config::SourceSpec {
            name: "first-source".into(),
            origin: config::OriginSpec {
                origin_type: config::OriginType::Git,
                url: "https://example.com/repo".into(),
                branch: "master".into(),
                auth: None,
                ssh_strict_host_key_checking: config::SshHostKeyPolicy::AcceptNew,
            },
            subscription: config::SubscriptionSpec::default(),
            sync: config::SourceSyncSpec::default(),
        };

        let result = super::add_source_to_config(&config_path, &source);
        assert!(
            result.is_ok(),
            "add_source_to_config failed: {:?}",
            result.err()
        );

        let written = std::fs::read_to_string(&config_path).unwrap();
        let parsed: serde_yaml::Value = serde_yaml::from_str(&written).unwrap();
        let sources = parsed["spec"]["sources"].as_sequence().unwrap();
        assert_eq!(sources.len(), 1, "expected 1 source, got {}", sources.len());
        assert_eq!(sources[0]["name"].as_str().unwrap(), "first-source");
        assert_eq!(
            sources[0]["origin"]["url"].as_str().unwrap(),
            "https://example.com/repo"
        );
    }

    #[test]
    fn add_source_to_config_errors_when_spec_missing() {
        let dir = tempfile::tempdir().unwrap();
        let config_path = dir.path().join("cfgd.yaml");
        std::fs::write(
            &config_path,
            "apiVersion: cfgd.io/v1alpha1\nkind: Config\nmetadata:\n  name: test\n",
        )
        .unwrap();

        let source = config::SourceSpec {
            name: "src".into(),
            origin: config::OriginSpec {
                origin_type: config::OriginType::Git,
                url: "https://example.com".into(),
                branch: "main".into(),
                auth: None,
                ssh_strict_host_key_checking: config::SshHostKeyPolicy::AcceptNew,
            },
            subscription: config::SubscriptionSpec::default(),
            sync: config::SourceSyncSpec::default(),
        };

        let result = super::add_source_to_config(&config_path, &source);
        assert!(result.is_err(), "expected error when spec is missing");
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("config missing 'spec'"),
            "expected 'config missing spec', got: {msg}"
        );
    }

    // -----------------------------------------------------------------------
    // WorkstationDaemonHooks tests
    // -----------------------------------------------------------------------

    #[test]
    fn workstation_daemon_hooks_build_registry_returns_populated_registry() {
        use cfgd_core::daemon::DaemonHooks;
        let hooks = super::WorkstationDaemonHooks;
        let cfg = cfgd_core::config::CfgdConfig {
            api_version: cfgd_core::API_VERSION.into(),
            kind: "Config".into(),
            metadata: cfgd_core::config::ConfigMetadata {
                name: "test".into(),
            },
            spec: cfgd_core::config::ConfigSpec::default(),
        };
        let registry = hooks.build_registry(&cfg);
        assert!(
            !registry.package_managers.is_empty(),
            "build_registry should return a registry with at least one package manager"
        );
        assert!(
            !registry.system_configurators.is_empty(),
            "build_registry should return a registry with at least one system configurator"
        );
    }

    #[test]
    fn workstation_daemon_hooks_expand_tilde() {
        use cfgd_core::daemon::DaemonHooks;
        let hooks = super::WorkstationDaemonHooks;
        let expanded = hooks.expand_tilde(std::path::Path::new("~/test/file"));
        // Should not start with ~ after expansion
        assert!(
            !expanded.to_string_lossy().starts_with('~'),
            "expand_tilde should expand ~ to home directory, got: {}",
            expanded.display()
        );
        // Should end with the path suffix
        assert!(
            expanded.to_string_lossy().ends_with("test/file"),
            "expand_tilde should preserve path suffix, got: {}",
            expanded.display()
        );
    }

    #[test]
    fn workstation_daemon_hooks_expand_tilde_absolute_unchanged() {
        use cfgd_core::daemon::DaemonHooks;
        let hooks = super::WorkstationDaemonHooks;
        let abs = std::path::Path::new("/absolute/path");
        let expanded = hooks.expand_tilde(abs);
        assert_eq!(
            expanded, abs,
            "expand_tilde should not modify absolute paths"
        );
    }

    #[test]
    fn workstation_daemon_hooks_plan_files_empty_profile() {
        use cfgd_core::daemon::DaemonHooks;
        let hooks = super::WorkstationDaemonHooks;
        let dir = tempfile::tempdir().unwrap();
        // Write a minimal config so load_config succeeds
        std::fs::write(dir.path().join("cfgd.yaml"), TEST_CONFIG_YAML).unwrap();
        let profiles_dir = dir.path().join("profiles");
        std::fs::create_dir_all(&profiles_dir).unwrap();
        std::fs::write(profiles_dir.join("default.yaml"), DEFAULT_PROFILE_YAML).unwrap();

        let resolved = cfgd_core::config::ResolvedProfile {
            layers: vec![],
            merged: cfgd_core::config::MergedProfile::default(),
        };
        let result = hooks.plan_files(dir.path(), &resolved);
        assert!(
            result.is_ok(),
            "plan_files with empty profile should succeed: {:?}",
            result.err()
        );
        let actions = result.unwrap();
        assert!(
            actions.is_empty(),
            "plan_files with empty profile should produce no actions, got {}",
            actions.len()
        );
    }

    // -----------------------------------------------------------------------
    // backup_file — exercises rename + printer output
    // -----------------------------------------------------------------------

    #[test]
    fn backup_file_renames_and_prints_success() {
        let dir = tempfile::tempdir().unwrap();
        let target = dir.path().join("my-config.conf");
        std::fs::write(&target, "original content").unwrap();

        let (printer, buf) = Printer::for_test();
        let result = super::backup_file(&target, &printer);
        assert!(
            result.is_ok(),
            "backup_file should succeed: {:?}",
            result.err()
        );

        // Original file should no longer exist
        assert!(
            !target.exists(),
            "original file should be removed after backup"
        );

        // Backup file should exist with original content
        let backup = dir.path().join("my-config.conf.cfgd-backup");
        assert!(backup.exists(), "backup file should exist at .cfgd-backup");
        let content = std::fs::read_to_string(&backup).unwrap();
        assert_eq!(
            content, "original content",
            "backup should preserve original content"
        );

        // Printer should have success message with backup path
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Backed up to"),
            "expected 'Backed up to' in output, got: {output}"
        );
        assert!(
            output.contains(".cfgd-backup"),
            "expected backup path in output, got: {output}"
        );
    }

    #[test]
    fn backup_file_errors_on_missing_file() {
        let dir = tempfile::tempdir().unwrap();
        let target = dir.path().join("nonexistent.txt");

        let (printer, _buf) = Printer::for_test();
        let result = super::backup_file(&target, &printer);
        assert!(result.is_err(), "backup_file should fail for missing file");
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("Failed to backup"),
            "error should mention 'Failed to backup', got: {msg}"
        );
    }

    // -----------------------------------------------------------------------
    // apply_backup_choice — exercises skip and backup branches
    // -----------------------------------------------------------------------

    #[test]
    fn apply_backup_choice_skip_converts_action_to_skip() {
        let dir = tempfile::tempdir().unwrap();
        let target = dir.path().join("file.txt");
        std::fs::write(&target, "data").unwrap();
        let source = dir.path().join("source.txt");
        std::fs::write(&source, "new data").unwrap();

        let (printer, _buf) = Printer::for_test();
        let mut action = reconciler::Action::File(FileAction::Create {
            source: source.clone(),
            target: target.clone(),
            origin: "profile".to_string(),
            strategy: config::FileStrategy::Symlink,
            source_hash: None,
        });

        let result = super::apply_backup_choice("Skip this file", &target, &mut action, &printer);
        assert!(result.is_ok(), "skip choice should succeed");

        // Action should now be a Skip
        match &action {
            reconciler::Action::File(FileAction::Skip { reason, origin, .. }) => {
                assert!(
                    reason.contains("skipped by user"),
                    "reason should indicate user skip, got: {reason}"
                );
                assert_eq!(origin, "profile", "origin should be preserved from Create");
            }
            other => panic!("expected Skip action, got: {:?}", other),
        }
    }

    #[test]
    fn apply_backup_choice_backup_moves_file() {
        let dir = tempfile::tempdir().unwrap();
        let target = dir.path().join("managed.conf");
        std::fs::write(&target, "existing config").unwrap();
        let source = dir.path().join("src.conf");
        std::fs::write(&source, "new config").unwrap();

        let (printer, buf) = Printer::for_test();
        let mut action = reconciler::Action::File(FileAction::Update {
            source: source.clone(),
            target: target.clone(),
            diff: "diff text".into(),
            origin: "module-x".to_string(),
            strategy: config::FileStrategy::Copy,
            source_hash: None,
        });

        let result =
            super::apply_backup_choice("Backup and continue", &target, &mut action, &printer);
        assert!(result.is_ok(), "backup choice should succeed");

        // Original file should be moved
        assert!(!target.exists(), "original file should be renamed");
        let backup = dir.path().join("managed.conf.cfgd-backup");
        assert!(backup.exists(), "backup should exist");
        assert_eq!(std::fs::read_to_string(&backup).unwrap(), "existing config");

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Backed up to"),
            "should print backup success, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // print_apply_result — all four status branches
    // -----------------------------------------------------------------------

    #[test]
    fn print_apply_result_success_message() {
        let (printer, buf) = Printer::for_test();
        let result = cfgd_core::reconciler::ApplyResult {
            action_results: vec![
                cfgd_core::reconciler::ActionResult {
                    phase: "packages".into(),
                    description: "install cargo ripgrep".into(),
                    success: true,
                    error: None,
                    changed: true,
                },
                cfgd_core::reconciler::ActionResult {
                    phase: "files".into(),
                    description: "create ~/.bashrc".into(),
                    success: true,
                    error: None,
                    changed: true,
                },
            ],
            status: cfgd_core::state::ApplyStatus::Success,
            apply_id: 1,
        };
        let status = super::print_apply_result(&result, &printer);
        assert!(
            matches!(status, cfgd_core::state::ApplyStatus::Success),
            "should return Success status"
        );
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Apply complete") && output.contains("2 action(s) succeeded"),
            "expected success message with count, got: {output}"
        );
    }

    #[test]
    fn print_apply_result_partial_message() {
        let (printer, buf) = Printer::for_test();
        let result = cfgd_core::reconciler::ApplyResult {
            action_results: vec![
                cfgd_core::reconciler::ActionResult {
                    phase: "packages".into(),
                    description: "install brew foo".into(),
                    success: true,
                    error: None,
                    changed: true,
                },
                cfgd_core::reconciler::ActionResult {
                    phase: "files".into(),
                    description: "create /tmp/fail".into(),
                    success: false,
                    error: Some("permission denied".into()),
                    changed: false,
                },
            ],
            status: cfgd_core::state::ApplyStatus::Partial,
            apply_id: 2,
        };
        let status = super::print_apply_result(&result, &printer);
        assert!(
            matches!(status, cfgd_core::state::ApplyStatus::Partial),
            "should return Partial status"
        );
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("partially complete")
                && output.contains("1 succeeded")
                && output.contains("1 failed"),
            "expected partial message, got: {output}"
        );
    }

    #[test]
    fn print_apply_result_failed_message() {
        let (printer, buf) = Printer::for_test();
        let result = cfgd_core::reconciler::ApplyResult {
            action_results: vec![cfgd_core::reconciler::ActionResult {
                phase: "system".into(),
                description: "set sysctl".into(),
                success: false,
                error: Some("not root".into()),
                changed: false,
            }],
            status: cfgd_core::state::ApplyStatus::Failed,
            apply_id: 3,
        };
        let status = super::print_apply_result(&result, &printer);
        assert!(
            matches!(status, cfgd_core::state::ApplyStatus::Failed),
            "should return Failed status"
        );
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Apply failed") && output.contains("1 action(s) failed"),
            "expected failed message, got: {output}"
        );
    }

    #[test]
    fn print_apply_result_in_progress_message() {
        let (printer, buf) = Printer::for_test();
        let result = cfgd_core::reconciler::ApplyResult {
            action_results: vec![],
            status: cfgd_core::state::ApplyStatus::InProgress,
            apply_id: 4,
        };
        let status = super::print_apply_result(&result, &printer);
        assert!(
            matches!(status, cfgd_core::state::ApplyStatus::InProgress),
            "should return InProgress status"
        );
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("still in progress"),
            "expected in-progress warning, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // validate_resource_name — all validation branches
    // -----------------------------------------------------------------------

    #[test]
    fn validate_resource_name_valid_names() {
        assert!(super::validate_resource_name("my-module", "Module").is_ok());
        assert!(super::validate_resource_name("mod_v2", "Module").is_ok());
        assert!(super::validate_resource_name("a.b.c", "Module").is_ok());
        assert!(super::validate_resource_name("X", "Module").is_ok());
        assert!(super::validate_resource_name("a123", "Module").is_ok());
    }

    #[test]
    fn validate_resource_name_empty_fails() {
        let result = super::validate_resource_name("", "Module");
        assert!(result.is_err());
        assert!(
            result.unwrap_err().to_string().contains("cannot be empty"),
            "should mention empty name"
        );
    }

    #[test]
    fn validate_resource_name_too_long_fails() {
        let name = "a".repeat(129);
        let result = super::validate_resource_name(&name, "Module");
        assert!(result.is_err());
        assert!(
            result.unwrap_err().to_string().contains("too long"),
            "should mention too long"
        );
    }

    #[test]
    fn validate_resource_name_leading_dot_fails() {
        let result = super::validate_resource_name(".hidden", "Profile");
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("cannot start with"),
            "should mention leading character restriction"
        );
    }

    #[test]
    fn validate_resource_name_leading_hyphen_fails() {
        let result = super::validate_resource_name("-bad", "Profile");
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("cannot start with"),
            "should mention leading character restriction"
        );
    }

    #[test]
    fn validate_resource_name_invalid_chars_fails() {
        let result = super::validate_resource_name("my module!", "Module");
        assert!(result.is_err());
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("invalid characters"),
            "should mention invalid characters, got: {msg}"
        );
    }

    // -----------------------------------------------------------------------
    // scan_profile_names and scan_module_names
    // -----------------------------------------------------------------------

    #[test]
    fn scan_profile_names_finds_all_profiles() {
        let dir = tempfile::tempdir().unwrap();
        let profiles_dir = dir.path().join("profiles");
        std::fs::create_dir_all(&profiles_dir).unwrap();

        std::fs::write(profiles_dir.join("default.yaml"), DEFAULT_PROFILE_YAML).unwrap();
        std::fs::write(profiles_dir.join("work.yaml"), WORK_PROFILE_YAML).unwrap();
        // Non-yaml file should be ignored
        std::fs::write(profiles_dir.join("readme.txt"), "not a profile").unwrap();

        let names = super::scan_profile_names(&profiles_dir).unwrap();
        assert!(
            names.contains(&"default".to_string()),
            "should find default profile, got: {:?}",
            names
        );
        assert!(
            names.contains(&"work".to_string()),
            "should find work profile, got: {:?}",
            names
        );
        assert!(
            !names.contains(&"readme".to_string()),
            "should not include non-yaml files"
        );
    }

    #[test]
    fn scan_profile_names_nonexistent_dir_returns_empty() {
        let dir = tempfile::tempdir().unwrap();
        let profiles_dir = dir.path().join("no-such-dir");
        let names = super::scan_profile_names(&profiles_dir).unwrap();
        assert!(
            names.is_empty(),
            "nonexistent profiles dir should return empty list"
        );
    }

    #[test]
    fn scan_module_names_finds_modules() {
        let dir = tempfile::tempdir().unwrap();
        let modules_dir = dir.path().join("modules");
        std::fs::create_dir_all(&modules_dir).unwrap();

        // Create two valid modules
        create_module_in_dir(dir.path(), "alpha-mod", SIMPLE_MODULE_YAML);
        create_module_in_dir(dir.path(), "beta-mod", SIMPLE_MODULE_YAML);

        // Create a dir without module.yaml (should be ignored)
        std::fs::create_dir_all(modules_dir.join("not-a-module")).unwrap();

        let names = super::scan_module_names(&modules_dir).unwrap();
        assert_eq!(
            names.len(),
            2,
            "should find exactly 2 modules, got: {:?}",
            names
        );
        assert_eq!(names[0], "alpha-mod", "should be sorted alphabetically");
        assert_eq!(names[1], "beta-mod", "should be sorted alphabetically");
    }

    #[test]
    fn scan_module_names_nonexistent_dir_returns_empty() {
        let dir = tempfile::tempdir().unwrap();
        let modules_dir = dir.path().join("no-modules");
        let names = super::scan_module_names(&modules_dir).unwrap();
        assert!(
            names.is_empty(),
            "nonexistent modules dir should return empty list"
        );
    }

    // -----------------------------------------------------------------------
    // walk_yaml_path — exercises all branches including error paths
    // -----------------------------------------------------------------------

    #[test]
    fn walk_yaml_path_root_dot_returns_whole_value() {
        let val: serde_yaml::Value = serde_yaml::from_str("foo: bar\nbaz: 42").unwrap();
        let result = super::walk_yaml_path(&val, ".").unwrap();
        assert!(result.is_mapping(), "root should be a mapping");
    }

    #[test]
    fn walk_yaml_path_nested_key() {
        let val: serde_yaml::Value = serde_yaml::from_str("a:\n  b:\n    c: hello").unwrap();
        let result = super::walk_yaml_path(&val, "a.b.c").unwrap();
        assert_eq!(
            result.as_str().unwrap(),
            "hello",
            "should reach nested value"
        );
    }

    #[test]
    fn walk_yaml_path_missing_key_errors() {
        let val: serde_yaml::Value = serde_yaml::from_str("a:\n  b: 1").unwrap();
        let result = super::walk_yaml_path(&val, "a.z");
        assert!(result.is_err());
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("not found"),
            "should report key not found, got: {msg}"
        );
    }

    #[test]
    fn walk_yaml_path_empty_segment_errors() {
        let val: serde_yaml::Value = serde_yaml::from_str("a: 1").unwrap();
        let result = super::walk_yaml_path(&val, "a..b");
        assert!(result.is_err());
        assert!(
            result.unwrap_err().to_string().contains("empty segment"),
            "should report empty segment"
        );
    }

    #[test]
    fn walk_yaml_path_traverse_into_scalar_errors() {
        let val: serde_yaml::Value = serde_yaml::from_str("a: 1").unwrap();
        let result = super::walk_yaml_path(&val, "a.b");
        assert!(result.is_err());
        let msg = result.unwrap_err().to_string();
        assert!(
            msg.contains("not a mapping"),
            "should report not a mapping, got: {msg}"
        );
    }

    // -----------------------------------------------------------------------
    // walk_yaml_path_mut — creates intermediate mappings
    // -----------------------------------------------------------------------

    #[test]
    fn walk_yaml_path_mut_creates_intermediate_maps() {
        let mut val: serde_yaml::Value = serde_yaml::from_str("root: {}").unwrap();
        let (parent, leaf) = super::walk_yaml_path_mut(&mut val, "root.new.nested.key").unwrap();
        assert_eq!(leaf, "key");
        // Insert a value to verify parent is the right map
        parent.insert(
            serde_yaml::Value::String(leaf),
            serde_yaml::Value::String("value".into()),
        );
        let result = super::walk_yaml_path(&val, "root.new.nested.key").unwrap();
        assert_eq!(result.as_str().unwrap(), "value");
    }

    #[test]
    fn walk_yaml_path_mut_empty_path_errors() {
        let mut val: serde_yaml::Value = serde_yaml::from_str("a: 1").unwrap();
        let result = super::walk_yaml_path_mut(&mut val, "");
        assert!(result.is_err());
        assert!(
            result.unwrap_err().to_string().contains("empty segment"),
            "should reject empty path"
        );
    }

    // -----------------------------------------------------------------------
    // parse_yaml_value — type inference branches
    // -----------------------------------------------------------------------

    #[test]
    fn parse_yaml_value_all_types() {
        assert_eq!(
            super::parse_yaml_value("true"),
            serde_yaml::Value::Bool(true)
        );
        assert_eq!(
            super::parse_yaml_value("false"),
            serde_yaml::Value::Bool(false)
        );
        assert_eq!(super::parse_yaml_value("null"), serde_yaml::Value::Null);
        assert_eq!(super::parse_yaml_value("~"), serde_yaml::Value::Null);
        assert_eq!(
            super::parse_yaml_value("42"),
            serde_yaml::Value::Number(42i64.into())
        );
        assert_eq!(
            super::parse_yaml_value("hello"),
            serde_yaml::Value::String("hello".into())
        );
        // Float
        let float_val = super::parse_yaml_value("3.14");
        assert!(float_val.is_number(), "3.14 should parse as number");
    }

    // -----------------------------------------------------------------------
    // compose_with_sources — no-sources early return
    // -----------------------------------------------------------------------

    #[test]
    fn compose_with_sources_no_sources_returns_local_profile() {
        let h = CliTestHarness::builder().build();
        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        let resolved =
            config::resolve_profile("default", &h.config_path().join("profiles")).unwrap();

        let result = super::compose_with_sources(&h.cli(), &cfg, &resolved, h.printer());
        assert!(
            result.is_ok(),
            "compose_with_sources with no sources should succeed: {:?}",
            result.err()
        );
        let composition = result.unwrap();
        assert!(
            composition.conflicts.is_empty(),
            "no conflicts expected when no sources"
        );
        assert!(
            composition.source_env.is_empty(),
            "no source_env expected when no sources"
        );
        assert!(
            composition.source_commits.is_empty(),
            "no source_commits expected when no sources"
        );
        // resolved profile should be the same as local
        assert_eq!(
            composition.resolved.merged.modules.len(),
            resolved.merged.modules.len(),
            "resolved should match local profile"
        );
    }

    // -----------------------------------------------------------------------
    // filter_plan — skip and only filters on package and non-package actions
    // -----------------------------------------------------------------------

    #[test]
    fn filter_plan_skip_removes_matching_packages() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![
                    Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["ripgrep".into(), "fd".into(), "bat".into()],
                        origin: "profile".into(),
                    }),
                    Action::Package(PackageAction::Install {
                        manager: "cargo".into(),
                        packages: vec!["tokei".into()],
                        origin: "profile".into(),
                    }),
                ],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["packages.brew.fd".to_string()], &[]);

        // brew install should remain but without fd
        let brew_action = &plan.phases[0].actions[0];
        match brew_action {
            Action::Package(PackageAction::Install { packages, .. }) => {
                assert!(
                    !packages.contains(&"fd".to_string()),
                    "fd should be filtered out"
                );
                assert!(
                    packages.contains(&"ripgrep".to_string()),
                    "ripgrep should remain"
                );
                assert!(packages.contains(&"bat".to_string()), "bat should remain");
            }
            other => panic!("expected Install action, got: {:?}", other),
        }
        // cargo should be untouched
        assert_eq!(
            plan.phases[0].actions.len(),
            2,
            "cargo action should remain"
        );
    }

    #[test]
    fn filter_plan_only_keeps_matching_phase() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let mut plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["git".into()],
                        origin: "profile".into(),
                    })],
                },
                Phase {
                    name: PhaseName::Files,
                    actions: vec![Action::File(FileAction::Create {
                        source: PathBuf::from("/src"),
                        target: PathBuf::from("/dst"),
                        origin: "profile".into(),
                        strategy: config::FileStrategy::Copy,
                        source_hash: None,
                    })],
                },
            ],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &["packages".to_string()]);

        // Packages phase should keep its action
        assert_eq!(
            plan.phases[0].actions.len(),
            1,
            "packages phase should retain action"
        );
        // Files phase should have its action removed by only filter
        assert_eq!(
            plan.phases[1].actions.len(),
            0,
            "files phase should be empty after only=packages"
        );
    }

    #[test]
    fn filter_plan_skip_uninstall_packages_env() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Uninstall {
                    manager: "npm".into(),
                    packages: vec!["left-pad".into(), "is-odd".into()],
                    origin: "profile".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &["packages.npm.left-pad".to_string()], &[]);

        match &plan.phases[0].actions[0] {
            Action::Package(PackageAction::Uninstall { packages, .. }) => {
                assert_eq!(packages, &vec!["is-odd".to_string()]);
            }
            other => panic!("expected Uninstall, got: {:?}", other),
        }
    }

    #[test]
    fn filter_plan_empty_filters_is_noop() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let mut plan = Plan {
            phases: vec![Phase {
                name: PhaseName::Packages,
                actions: vec![Action::Package(PackageAction::Install {
                    manager: "apt".into(),
                    packages: vec!["vim".into()],
                    origin: "profile".into(),
                })],
            }],
            warnings: vec![],
        };

        super::filter_plan(&mut plan, &[], &[]);

        assert_eq!(
            plan.phases[0].actions.len(),
            1,
            "empty filters should not change anything"
        );
    }

    // -----------------------------------------------------------------------
    // strip_scripts_from_plan — removes script phases and module script actions
    // -----------------------------------------------------------------------

    #[test]
    fn strip_scripts_from_plan_removes_script_phases() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let mut plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::PreScripts,
                    actions: vec![],
                },
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["git".into()],
                        origin: "profile".into(),
                    })],
                },
                Phase {
                    name: PhaseName::PostScripts,
                    actions: vec![],
                },
            ],
            warnings: vec![],
        };

        super::strip_scripts_from_plan(&mut plan);

        assert_eq!(
            plan.phases.len(),
            1,
            "should only have packages phase remaining"
        );
        assert_eq!(
            plan.phases[0].name,
            PhaseName::Packages,
            "remaining phase should be Packages"
        );
    }

    // -----------------------------------------------------------------------
    // action_path — all action type variants
    // -----------------------------------------------------------------------

    #[test]
    fn action_path_package_install() {
        let action = reconciler::Action::Package(PackageAction::Install {
            manager: "brew".into(),
            packages: vec!["git".into()],
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Packages, &action);
        assert_eq!(path, "packages.brew");
    }

    #[test]
    fn action_path_file_create() {
        let action = reconciler::Action::File(FileAction::Create {
            source: PathBuf::from("/src/bashrc"),
            target: PathBuf::from("/home/user/.bashrc"),
            origin: "profile".into(),
            strategy: config::FileStrategy::Copy,
            source_hash: None,
        });
        let path = super::action_path(&PhaseName::Files, &action);
        assert_eq!(path, "files:/home/user/.bashrc");
    }

    #[test]
    fn action_path_module() {
        let action = reconciler::Action::Module(reconciler::ModuleAction {
            module_name: "dev-tools".into(),
            kind: reconciler::ModuleActionKind::InstallPackages { resolved: vec![] },
        });
        let path = super::action_path(&PhaseName::Modules, &action);
        assert_eq!(path, "modules.dev-tools");
    }

    #[test]
    fn action_path_env_write() {
        let action = reconciler::Action::Env(reconciler::EnvAction::WriteEnvFile {
            path: PathBuf::from("/home/user/.config/cfgd/env.sh"),
            content: String::new(),
        });
        let path = super::action_path(&PhaseName::Env, &action);
        assert_eq!(path, "env:/home/user/.config/cfgd/env.sh");
    }

    // -----------------------------------------------------------------------
    // print_package_drift — exercises all PackageAction display branches
    // -----------------------------------------------------------------------

    #[test]
    fn print_package_drift_no_drift() {
        let (printer, buf) = Printer::for_test();
        let actions = vec![PackageAction::Skip {
            manager: "brew".into(),
            reason: "up to date".into(),
            origin: "profile".into(),
        }];
        super::print_package_drift(&actions, &printer);
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("No package drift"),
            "all-skip should show no drift, got: {output}"
        );
    }

    #[test]
    fn print_package_drift_missing_packages() {
        let (printer, buf) = Printer::for_test();
        let actions = vec![
            PackageAction::Install {
                manager: "cargo".into(),
                packages: vec!["ripgrep".into(), "fd-find".into()],
                origin: "profile".into(),
            },
            PackageAction::Uninstall {
                manager: "npm".into(),
                packages: vec!["left-pad".into()],
                origin: "profile".into(),
            },
            PackageAction::Bootstrap {
                manager: "pipx".into(),
                method: "pip install pipx".into(),
                origin: "profile".into(),
            },
        ];
        super::print_package_drift(&actions, &printer);
        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("cargo: missing") && output.contains("ripgrep"),
            "should show missing cargo packages, got: {output}"
        );
        assert!(
            output.contains("npm: extra") && output.contains("left-pad"),
            "should show extra npm packages, got: {output}"
        );
        assert!(
            output.contains("pipx: not installed") && output.contains("bootstrap"),
            "should show bootstrap need, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_plan with module containing packages, env, and files
    // Exercises: module loading, plan generation, display_plan_preview
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_plan_rich_module_with_packages_env_and_files() {
        let rich_module = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: dev-tools
spec:
  packages:
    - name: ripgrep
      manager: cargo
    - name: fd-find
      manager: cargo
    - name: bat
  env:
    - name: EDITOR
      value: nvim
    - name: PAGER
      value: less
  files:
    - source: gitconfig
      target: /tmp/cfgd-test-gitconfig
"#;
        let profile_with_module = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  env:
    - name: SHELL
      value: /bin/zsh
  packages:
    cargo:
      - tokei
  modules:
    - dev-tools
"#;

        let h = CliTestHarness::builder()
            .profile("default", profile_with_module)
            .module("dev-tools", rich_module)
            .build();

        // Create the module file referenced by the module
        let module_files_dir = h
            .config_path()
            .join("modules")
            .join("dev-tools")
            .join("files");
        std::fs::write(
            module_files_dir.join("gitconfig"),
            "[user]\n  name = Test\n",
        )
        .unwrap();

        let cli = h.cli_with_command(Command::Plan(PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        }));
        let result = super::cmd_plan(
            &cli,
            h.printer(),
            &PlanArgs {
                phase: None,
                skip: vec![],
                only: vec![],
                module: None,
                skip_scripts: false,
                context: "apply".to_string(),
            },
        );
        assert!(
            result.is_ok(),
            "cmd_plan with rich module should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Plan"),
            "should show Plan header, got: {output}"
        );
        // Should mention actions or nothing-to-do
        assert!(
            output.contains("action(s) planned")
                || output.contains("Nothing to do")
                || output.contains("Phase"),
            "should show plan summary, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_plan with --module filter (module-only mode)
    // Exercises: module-only path, empty_resolved_profile, module resolution
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_plan_module_only_mode() {
        let module_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: standalone
spec:
  packages:
    - name: jq
    - name: yq
"#;
        let h = CliTestHarness::builder()
            .module("standalone", module_yaml)
            .build();

        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: Some("standalone".to_string()),
            skip_scripts: false,
            context: "apply".to_string(),
        };
        let result = super::cmd_plan(&h.cli(), h.printer(), &args);
        assert!(
            result.is_ok(),
            "module-only plan should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Plan"),
            "should show Plan header, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_plan JSON structured output with module
    // Exercises: build_plan_output, structured serialization
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_plan_json_output_with_module() {
        let module_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: json-mod
spec:
  packages:
    - name: curl
    - name: wget
"#;
        let profile_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - json-mod
  packages:
    cargo:
      - bat
"#;
        let h = CliTestHarness::builder()
            .json()
            .profile("default", profile_yaml)
            .module("json-mod", module_yaml)
            .build();

        let args = PlanArgs {
            phase: None,
            skip: vec![],
            only: vec![],
            module: None,
            skip_scripts: false,
            context: "apply".to_string(),
        };
        let result = super::cmd_plan(&h.cli(), h.printer(), &args);
        assert!(
            result.is_ok(),
            "JSON plan should succeed: {:?}",
            result.err()
        );

        let json = h.json_output();
        assert_json_has_fields(&json, &["context", "totalActions", "phases"]);
        assert_json_field_type(&json, "totalActions", "number");
        assert_json_field_type(&json, "phases", "array");
    }

    // -----------------------------------------------------------------------
    // cmd_status with module configured — exercises module status display
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_status_with_module_displays_module_info() {
        let module_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: status-mod
spec:
  packages:
    - name: git
    - name: curl
  files:
    - source: config.txt
      target: /tmp/cfgd-test-config.txt
  depends:
    - base-mod
"#;
        let profile_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - status-mod
"#;
        let h = CliTestHarness::builder()
            .profile("default", profile_yaml)
            .module("status-mod", module_yaml)
            .build();

        // Also create the base-mod so dependency display works
        create_module_in_dir(
            h.config_path(),
            "base-mod",
            "apiVersion: cfgd.io/v1alpha1\nkind: Module\nmetadata:\n  name: base-mod\nspec:\n  packages: []\n",
        );

        let result = super::cmd_status_module(&h.cli(), h.printer(), "status-mod");
        assert!(
            result.is_ok(),
            "cmd_status_module should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Status: status-mod"),
            "should show module name in header, got: {output}"
        );
        assert!(
            output.contains("Packages") && output.contains("2"),
            "should show package count, got: {output}"
        );
        assert!(
            output.contains("Files") && output.contains("1"),
            "should show file count, got: {output}"
        );
        assert!(
            output.contains("Dependencies") && output.contains("base-mod"),
            "should show dependencies, got: {output}"
        );
        assert!(
            output.contains("not applied"),
            "should show 'not applied' status, got: {output}"
        );
    }

    #[test]
    fn cmd_status_module_json_output_found() {
        let module_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: json-status-mod
spec:
  packages:
    - name: vim
  files:
    - source: vimrc
      target: /tmp/cfgd-test-vimrc
  depends:
    - core
"#;
        let h = CliTestHarness::builder()
            .json()
            .module("json-status-mod", module_yaml)
            .build();

        let result = super::cmd_status_module(&h.cli(), h.printer(), "json-status-mod");
        assert!(
            result.is_ok(),
            "JSON module status should succeed: {:?}",
            result.err()
        );

        let json = h.json_output();
        assert_json_has_fields(&json, &["name", "packages", "files", "depends", "status"]);
        assert_eq!(json["name"].as_str().unwrap(), "json-status-mod");
        assert_eq!(json["packages"].as_u64().unwrap(), 1);
        assert_eq!(json["files"].as_u64().unwrap(), 1);
        assert_eq!(json["status"].as_str().unwrap(), "not applied");
        let depends = json["depends"].as_array().unwrap();
        assert_eq!(depends.len(), 1);
        assert_eq!(depends[0].as_str().unwrap(), "core");
    }

    #[test]
    fn cmd_status_module_json_output_not_found() {
        let h = CliTestHarness::builder().json().build();

        let result = super::cmd_status_module(&h.cli(), h.printer(), "nonexistent-mod");
        assert!(result.is_ok(), "missing module JSON status should succeed");

        let json = h.json_output();
        assert_eq!(json["name"].as_str().unwrap(), "nonexistent-mod");
        assert_eq!(json["status"].as_str().unwrap(), "not found");
        assert_eq!(json["packages"].as_u64().unwrap(), 0);
    }

    // -----------------------------------------------------------------------
    // cmd_config_show — exercises all branches (origins, sources, daemon, etc.)
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_config_show_with_rich_config_full() {
        let h = CliTestHarness::builder().rich_config().build();

        let result = super::cmd_config_show(&h.cli(), h.printer());
        assert!(
            result.is_ok(),
            "config show should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Configuration"),
            "should show Configuration header, got: {output}"
        );
        assert!(
            output.contains("Profile") && output.contains("default"),
            "should show profile name, got: {output}"
        );
        // Should show sources section
        assert!(
            output.contains("Sources") && output.contains("team-config"),
            "should show source names, got: {output}"
        );
        // Should show daemon section
        assert!(
            output.contains("Daemon") && output.contains("yes"),
            "should show daemon enabled, got: {output}"
        );
        assert!(
            output.contains("Reconcile interval") && output.contains("5m"),
            "should show reconcile interval, got: {output}"
        );
        // Should show secrets
        assert!(
            output.contains("Secrets") && output.contains("age"),
            "should show secrets backend, got: {output}"
        );
    }

    #[test]
    fn cmd_config_show_missing_file_errors() {
        let dir = tempfile::tempdir().unwrap();
        let cli = test_cli_with_state(dir.path(), None);
        let printer = test_printer();
        let result = super::cmd_config_show(&cli, &printer);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("No cfgd.yaml found"),
            "should report missing config"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_config_get — exercises walk_yaml_path through config
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_config_get_string_value() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_config_get(&h.cli(), h.printer(), "profile");
        assert!(
            result.is_ok(),
            "config get profile should succeed: {:?}",
            result.err()
        );
        let output = h.output();
        assert!(
            output.contains("default"),
            "should output 'default' profile, got: {output}"
        );
    }

    #[test]
    fn cmd_config_get_missing_key_errors_no_config() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_config_get(&h.cli(), h.printer(), "nonexistent.key");
        assert!(result.is_err(), "missing key should error");
        assert!(
            result.unwrap_err().to_string().contains("not found"),
            "should report key not found"
        );
    }

    #[test]
    fn cmd_config_get_json_output() {
        let h = CliTestHarness::builder().json().build();
        let result = super::cmd_config_get(&h.cli(), h.printer(), "profile");
        assert!(result.is_ok(), "JSON config get should succeed");
        let output = h.output();
        // JSON output should contain the value
        assert!(
            output.contains("default"),
            "JSON should contain profile value, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // build_registry_with_config_and_packages — with custom packages spec
    // -----------------------------------------------------------------------

    #[test]
    fn build_registry_with_config_populates_secret_backend() {
        let cfg = config::CfgdConfig {
            api_version: cfgd_core::API_VERSION.into(),
            kind: "Config".into(),
            metadata: config::ConfigMetadata {
                name: "test".into(),
            },
            spec: config::ConfigSpec {
                secrets: Some(config::SecretsConfig {
                    backend: "age".into(),
                    sops: None,
                    integrations: vec![],
                }),
                ..config::ConfigSpec::default()
            },
        };
        let registry = super::build_registry_with_config_and_packages(Some(&cfg), None);
        assert!(
            registry.secret_backend.is_some(),
            "should have a secret backend configured"
        );
        assert!(
            !registry.package_managers.is_empty(),
            "should have package managers"
        );
        assert!(
            !registry.system_configurators.is_empty(),
            "should have system configurators"
        );
    }

    #[test]
    fn build_registry_with_no_config_uses_defaults() {
        let registry = super::build_registry_with_config_and_packages(None, None);
        assert!(
            !registry.package_managers.is_empty(),
            "should have default package managers even without config"
        );
        assert!(
            registry.secret_backend.is_some(),
            "should have default secret backend"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_diff empty profile — exercises file, package, and system diff display
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_diff_full_profile_shows_all_sections() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_diff(&h.cli(), h.printer(), None);
        assert!(
            result.is_ok(),
            "diff with default profile should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Diff"),
            "should show Diff header, got: {output}"
        );
        assert!(
            output.contains("Files"),
            "should show Files section, got: {output}"
        );
        assert!(
            output.contains("Packages"),
            "should show Packages section, got: {output}"
        );
        assert!(
            output.contains("System"),
            "should show System section, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_diff with module filter — module-only diff path
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_diff_module_not_found_shows_info() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_diff(&h.cli(), h.printer(), Some("nonexistent-mod"));
        assert!(
            result.is_ok(),
            "diff with missing module should succeed gracefully"
        );
        let output = h.output();
        assert!(
            output.contains("not found"),
            "should indicate module not found, got: {output}"
        );
    }

    #[test]
    fn cmd_diff_module_with_files_shows_file_and_package_sections() {
        let module_yaml = r#"apiVersion: cfgd.io/v1alpha1
kind: Module
metadata:
  name: diff-mod
spec:
  packages:
    - name: curl
  files:
    - source: my-config
      target: /tmp/cfgd-diff-test-target
"#;
        let h = CliTestHarness::builder()
            .module("diff-mod", module_yaml)
            .build();

        // Create source file in module
        let module_files = h
            .config_path()
            .join("modules")
            .join("diff-mod")
            .join("files");
        std::fs::write(module_files.join("my-config"), "new config content\n").unwrap();

        let result = super::cmd_diff(&h.cli(), h.printer(), Some("diff-mod"));
        assert!(
            result.is_ok(),
            "module diff should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Module") && output.contains("diff-mod"),
            "should show module name, got: {output}"
        );
        assert!(
            output.contains("Files"),
            "should show Files section, got: {output}"
        );
        assert!(
            output.contains("Packages"),
            "should show Packages section, got: {output}"
        );
        // Target doesn't exist, so should show "missing"
        assert!(
            output.contains("missing"),
            "should show missing target file, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_status full profile with sources — exercises source display
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_status_with_sources_shows_source_section() {
        let h = CliTestHarness::builder().rich_config().build();
        let result = super::cmd_status(&h.cli(), h.printer(), None);
        assert!(
            result.is_ok(),
            "status with sources should succeed: {:?}",
            result.err()
        );

        let output = h.output();
        assert!(
            output.contains("Status"),
            "should show Status header, got: {output}"
        );
        assert!(
            output.contains("Config Sources"),
            "should show Config Sources section, got: {output}"
        );
        assert!(
            output.contains("team-config"),
            "should show source name, got: {output}"
        );
        assert!(
            output.contains("not yet fetched"),
            "unfetched source should show 'not yet fetched', got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // display_plan_table — exercises formatted table display
    // -----------------------------------------------------------------------

    #[test]
    fn display_plan_table_shows_phase_names() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let (printer, buf) = Printer::for_test();
        let plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["git".into(), "curl".into()],
                        origin: "profile".into(),
                    })],
                },
                Phase {
                    name: PhaseName::Env,
                    actions: vec![],
                },
            ],
            warnings: vec![],
        };

        super::display_plan_table(&plan, &printer, None);

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Packages") || output.contains("packages"),
            "should show Packages phase, got: {output}"
        );
    }

    #[test]
    fn display_plan_table_with_phase_filter_packages() {
        use cfgd_core::reconciler::{Action, Phase, Plan};

        let (printer, buf) = Printer::for_test();
        let plan = Plan {
            phases: vec![
                Phase {
                    name: PhaseName::Packages,
                    actions: vec![Action::Package(PackageAction::Install {
                        manager: "brew".into(),
                        packages: vec!["git".into()],
                        origin: "profile".into(),
                    })],
                },
                Phase {
                    name: PhaseName::Files,
                    actions: vec![Action::File(FileAction::Create {
                        source: PathBuf::from("/src"),
                        target: PathBuf::from("/dst"),
                        origin: "profile".into(),
                        strategy: config::FileStrategy::Copy,
                        source_hash: None,
                    })],
                },
            ],
            warnings: vec![],
        };

        // Filter to only files phase
        super::display_plan_table(&plan, &printer, Some(&PhaseName::Files));

        let output = buf.lock().unwrap().clone();
        // Files phase should be shown
        assert!(
            output.contains("Files") || output.contains("files") || output.contains("/dst"),
            "should show Files phase content, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // action_path — remaining variants not covered above
    // -----------------------------------------------------------------------

    #[test]
    fn action_path_file_update() {
        let action = reconciler::Action::File(FileAction::Update {
            source: PathBuf::from("/src/bashrc"),
            target: PathBuf::from("/home/user/.bashrc"),
            diff: "diff contents".into(),
            origin: "profile".into(),
            strategy: config::FileStrategy::Copy,
            source_hash: None,
        });
        let path = super::action_path(&PhaseName::Files, &action);
        assert_eq!(path, "files:/home/user/.bashrc");
    }

    #[test]
    fn action_path_file_delete() {
        let action = reconciler::Action::File(FileAction::Delete {
            target: PathBuf::from("/home/user/.obsolete"),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Files, &action);
        assert_eq!(path, "files:/home/user/.obsolete");
    }

    #[test]
    fn action_path_file_permissions() {
        let action = reconciler::Action::File(FileAction::SetPermissions {
            target: PathBuf::from("/home/user/.ssh/config"),
            mode: 0o600,
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Files, &action);
        assert_eq!(path, "files:/home/user/.ssh/config");
    }

    #[test]
    fn action_path_file_skip() {
        let action = reconciler::Action::File(FileAction::Skip {
            target: PathBuf::from("/home/user/.gitconfig"),
            reason: "up to date".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Files, &action);
        assert_eq!(path, "files:/home/user/.gitconfig");
    }

    #[test]
    fn action_path_package_uninstall() {
        let action = reconciler::Action::Package(PackageAction::Uninstall {
            manager: "npm".into(),
            packages: vec!["left-pad".into()],
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Packages, &action);
        assert_eq!(path, "packages.npm");
    }

    #[test]
    fn action_path_package_bootstrap() {
        let action = reconciler::Action::Package(PackageAction::Bootstrap {
            manager: "brew".into(),
            method: "curl install".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Packages, &action);
        assert_eq!(path, "packages.brew");
    }

    #[test]
    fn action_path_package_skip() {
        let action = reconciler::Action::Package(PackageAction::Skip {
            manager: "cargo".into(),
            reason: "already installed".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Packages, &action);
        assert_eq!(path, "packages.cargo");
    }

    #[test]
    fn action_path_script_run() {
        let entry = config::ScriptEntry::Simple("scripts/setup.sh".into());
        let action = reconciler::Action::Script(reconciler::ScriptAction::Run {
            entry,
            phase: reconciler::ScriptPhase::PreApply,
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::PreScripts, &action);
        assert_eq!(path, "pre-scripts:scripts/setup.sh");
    }

    #[test]
    fn action_path_script_run_full_entry() {
        let entry = config::ScriptEntry::Full {
            run: "echo hello".into(),
            timeout: None,
            idle_timeout: None,
            continue_on_error: None,
        };
        let action = reconciler::Action::Script(reconciler::ScriptAction::Run {
            entry,
            phase: reconciler::ScriptPhase::PostApply,
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::PostScripts, &action);
        assert_eq!(path, "post-scripts:echo hello");
    }

    #[test]
    fn action_path_system_set_value() {
        let action = reconciler::Action::System(reconciler::SystemAction::SetValue {
            configurator: "sysctl".into(),
            key: "vm.swappiness".into(),
            desired: "10".into(),
            current: "60".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::System, &action);
        assert_eq!(path, "system.sysctl.vm.swappiness");
    }

    #[test]
    fn action_path_system_skip() {
        let action = reconciler::Action::System(reconciler::SystemAction::Skip {
            configurator: "macosDefaults".into(),
            reason: "not on macOS".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::System, &action);
        assert_eq!(path, "system.macosDefaults");
    }

    #[test]
    fn action_path_env_inject_source_line() {
        let action = reconciler::Action::Env(reconciler::EnvAction::InjectSourceLine {
            rc_path: PathBuf::from("/home/user/.zshrc"),
            line: "source ~/.cfgd.env".into(),
        });
        let path = super::action_path(&PhaseName::Env, &action);
        assert_eq!(path, "env:/home/user/.zshrc");
    }

    #[test]
    fn action_path_secret_decrypt() {
        let action = reconciler::Action::Secret(SecretAction::Decrypt {
            source: PathBuf::from("/repo/secrets/api.enc"),
            target: PathBuf::from("/home/user/.config/api-key"),
            backend: "sops".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Secrets, &action);
        assert_eq!(path, "secrets:/home/user/.config/api-key");
    }

    #[test]
    fn action_path_secret_resolve() {
        let action = reconciler::Action::Secret(SecretAction::Resolve {
            provider: "1password".into(),
            reference: "op://vault/item/field".into(),
            target: PathBuf::from("/home/user/.token"),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Secrets, &action);
        assert_eq!(path, "secrets.1password.op://vault/item/field");
    }

    #[test]
    fn action_path_secret_resolve_env() {
        let action = reconciler::Action::Secret(SecretAction::ResolveEnv {
            provider: "vault".into(),
            reference: "secret/data/app".into(),
            envs: vec!["API_KEY".into(), "DB_PASS".into()],
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Secrets, &action);
        assert_eq!(path, "secrets.vault.secret/data/app:[API_KEY,DB_PASS]");
    }

    #[test]
    fn action_path_secret_skip() {
        let action = reconciler::Action::Secret(SecretAction::Skip {
            source: "old-secret".into(),
            reason: "not needed".into(),
            origin: "profile".into(),
        });
        let path = super::action_path(&PhaseName::Secrets, &action);
        assert_eq!(path, "secrets.old-secret");
    }

    // -----------------------------------------------------------------------
    // generate_release_workflow_yaml — deeper content verification
    // -----------------------------------------------------------------------

    #[test]
    fn generate_release_workflow_multiple_modules() {
        let yaml = super::generate_release_workflow_yaml(
            &["shell-tools".into(), "git-config".into()],
            &[],
        );
        // Both module paths should appear
        assert!(yaml.contains("modules/shell-tools/**"));
        assert!(yaml.contains("modules/git-config/**"));
        // Both should have matrix entries in detect-changes outputs
        assert!(yaml.contains("module_shell_tools"));
        assert!(yaml.contains("module_git_config"));
        // Should have tag-modules job but not tag-profiles
        assert!(yaml.contains("tag-modules:"));
        assert!(!yaml.contains("tag-profiles:"));
    }

    #[test]
    fn generate_release_workflow_hyphenated_names_become_underscored() {
        let yaml = super::generate_release_workflow_yaml(
            &["my-cool-tools".into()],
            &["work-laptop".into()],
        );
        // Hyphens in names become underscores in output variable names
        assert!(yaml.contains("module_my_cool_tools"));
        assert!(yaml.contains("profile_work_laptop"));
    }

    #[test]
    fn generate_release_workflow_empty_has_placeholder_job() {
        let yaml = super::generate_release_workflow_yaml(&[], &[]);
        // Should have commented-out paths section
        assert!(yaml.contains("# paths:"));
        // Should have placeholder job
        assert!(yaml.contains("placeholder:"));
        assert!(yaml.contains("No modules or profiles to tag yet"));
        // Should NOT have detect-changes or tag jobs
        assert!(!yaml.contains("detect-changes:"));
        assert!(!yaml.contains("tag-modules:"));
        assert!(!yaml.contains("tag-profiles:"));
    }

    #[test]
    fn generate_release_workflow_profiles_only() {
        let yaml =
            super::generate_release_workflow_yaml(&[], &["personal".into(), "server".into()]);
        assert!(yaml.contains("profiles/personal.yaml"));
        assert!(yaml.contains("profiles/personal.yml"));
        assert!(yaml.contains("profiles/server.yaml"));
        assert!(yaml.contains("profiles/server.yml"));
        assert!(yaml.contains("tag-profiles:"));
        assert!(!yaml.contains("tag-modules:"));
        assert!(yaml.contains("detect-changes:"));
    }

    // -----------------------------------------------------------------------
    // set_nested_yaml_value — top-level key
    // -----------------------------------------------------------------------

    #[test]
    fn set_nested_yaml_value_top_level_key() {
        let mut root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        super::set_nested_yaml_value(&mut root, "name", &serde_yaml::Value::String("test".into()))
            .unwrap();

        let val = root.get("name").and_then(|v| v.as_str());
        assert_eq!(val, Some("test"));
    }

    #[test]
    fn set_nested_yaml_value_three_level_path() {
        let mut root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        super::set_nested_yaml_value(
            &mut root,
            "a.b.c",
            &serde_yaml::Value::String("value".into()),
        )
        .unwrap();

        let val = root
            .get("a")
            .and_then(|v| v.get("b"))
            .and_then(|v| v.get("c"))
            .and_then(|v| v.as_str());
        assert_eq!(val, Some("value"));
        // Intermediate nodes should be mappings
        assert!(root.get("a").unwrap().is_mapping());
        assert!(root.get("a").unwrap().get("b").unwrap().is_mapping());
    }

    #[test]
    fn set_nested_yaml_value_preserves_siblings() {
        let mut root: serde_yaml::Value = serde_yaml::from_str("a:\n  existing: kept\n").unwrap();
        super::set_nested_yaml_value(
            &mut root,
            "a.new_key",
            &serde_yaml::Value::String("added".into()),
        )
        .unwrap();

        // Existing sibling should be preserved
        let existing = root
            .get("a")
            .and_then(|v| v.get("existing"))
            .and_then(|v| v.as_str());
        assert_eq!(existing, Some("kept"));
        // New key should be present
        let new_key = root
            .get("a")
            .and_then(|v| v.get("new_key"))
            .and_then(|v| v.as_str());
        assert_eq!(new_key, Some("added"));
    }

    #[test]
    fn set_nested_yaml_value_numeric_value() {
        let mut root = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
        super::set_nested_yaml_value(
            &mut root,
            "spec.replicas",
            &serde_yaml::Value::Number(serde_yaml::Number::from(3)),
        )
        .unwrap();

        let val = root
            .get("spec")
            .and_then(|v| v.get("replicas"))
            .and_then(|v| v.as_u64());
        assert_eq!(val, Some(3));
    }

    // -----------------------------------------------------------------------
    // pattern_matches — additional cases
    // -----------------------------------------------------------------------

    #[test]
    fn pattern_matches_no_match() {
        assert!(!super::pattern_matches("files", "packages.brew.ripgrep"));
    }

    #[test]
    fn pattern_matches_longer_pattern_than_path() {
        assert!(!super::pattern_matches(
            "packages.brew.ripgrep.extra",
            "packages.brew.ripgrep"
        ));
    }

    #[test]
    fn pattern_matches_secrets_colon() {
        assert!(super::pattern_matches(
            "secrets",
            "secrets:/home/user/.token"
        ));
    }

    #[test]
    fn pattern_matches_env_colon() {
        assert!(super::pattern_matches("env", "env:/home/user/.zshrc"));
    }

    #[test]
    fn pattern_matches_exact_colon_path() {
        assert!(super::pattern_matches(
            "files:/home/user/.bashrc",
            "files:/home/user/.bashrc"
        ));
    }

    #[test]
    fn pattern_matches_empty_pattern() {
        // Empty pattern should not match non-empty paths
        assert!(!super::pattern_matches("", "packages.brew"));
    }

    // -----------------------------------------------------------------------
    // copy_files_to_dir — additional forbidden prefix tests
    // -----------------------------------------------------------------------

    #[test]
    fn copy_files_to_dir_rejects_usr_directory() {
        let dir = tempfile::tempdir().unwrap();
        let repo_dir = dir.path().join("repo");
        if std::path::Path::new("/usr/bin/env").exists() {
            let result = super::copy_files_to_dir(&["/usr/bin/env".into()], &repo_dir);
            assert!(result.is_err());
            let msg = result.unwrap_err().to_string();
            assert!(
                msg.contains("system directory"),
                "expected 'system directory' error, got: {msg}"
            );
        }
    }

    #[test]
    fn copy_files_to_dir_rejects_bin_directory() {
        let dir = tempfile::tempdir().unwrap();
        let repo_dir = dir.path().join("repo");
        if std::path::Path::new("/bin/sh").exists() {
            let result = super::copy_files_to_dir(&["/bin/sh".into()], &repo_dir);
            assert!(result.is_err());
            let msg = result.unwrap_err().to_string();
            assert!(
                msg.contains("system directory"),
                "expected 'system directory' error, got: {msg}"
            );
        }
    }

    #[test]
    fn copy_files_to_dir_rejects_var_directory() {
        let dir = tempfile::tempdir().unwrap();
        let repo_dir = dir.path().join("repo");
        if std::path::Path::new("/var/log/syslog").exists() {
            let result = super::copy_files_to_dir(&["/var/log/syslog".into()], &repo_dir);
            assert!(result.is_err());
            let msg = result.unwrap_err().to_string();
            assert!(
                msg.contains("system directory"),
                "expected 'system directory' error, got: {msg}"
            );
        }
    }

    #[test]
    fn copy_files_to_dir_allows_home_directory() {
        let dir = tempfile::tempdir().unwrap();
        // Create a file in a temp directory (not a system directory)
        let source = dir.path().join("safe-file.txt");
        std::fs::write(&source, "safe content").unwrap();
        let repo_dir = dir.path().join("repo");

        let results = super::copy_files_to_dir(&[source.display().to_string()], &repo_dir).unwrap();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].0, "safe-file.txt");
        assert!(repo_dir.join("safe-file.txt").exists());
        assert_eq!(
            std::fs::read_to_string(repo_dir.join("safe-file.txt")).unwrap(),
            "safe content"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_source_list — table columns with state info
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_list_table_shows_status_and_priority() {
        let h = CliTestHarness::builder().rich_config().build();
        // Populate state with source info so the table columns have values
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_config_source(
                "team-config",
                "https://github.com/team/config",
                "main",
                Some("abc123"),
                Some("1.2.0"),
                None,
            )
            .unwrap();

        super::cmd_source_list(&h.cli(), h.printer()).unwrap();

        let output = h.output();
        // Table should include the source name, URL, priority, version, and status
        assert!(
            output.contains("team-config"),
            "table should show source name, got: {output}"
        );
        assert!(
            output.contains("100"),
            "table should show priority 100, got: {output}"
        );
    }

    #[test]
    fn cmd_source_list_structured_json_includes_state_info() {
        let h = CliTestHarness::builder().rich_config().json().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_config_source(
                "team-config",
                "https://github.com/team/config",
                "main",
                Some("abc123def"),
                Some("2.0.0"),
                None,
            )
            .unwrap();

        super::cmd_source_list(&h.cli(), h.printer()).unwrap();

        let output = h.output();
        let parsed: serde_json::Value = serde_json::from_str(output.trim())
            .unwrap_or_else(|e| panic!("invalid JSON: {e}, got: {output}"));
        let arr = parsed.as_array().expect("should be an array");
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["name"], "team-config");
        assert_eq!(arr[0]["version"], "2.0.0");
        assert!(
            arr[0]["lastFetched"].is_string(),
            "lastFetched should be populated after upsert"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_source_show — verify key fields displayed with state + resources
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_show_displays_all_key_fields() {
        let h = CliTestHarness::builder().rich_config().build();

        super::cmd_source_show(&h.cli(), h.printer(), "team-config").unwrap();

        let output = h.output();
        assert!(
            output.contains("URL"),
            "should display URL label, got: {output}"
        );
        assert!(
            output.contains("https://github.com/team/config"),
            "should display URL value, got: {output}"
        );
        assert!(
            output.contains("Branch"),
            "should display Branch label, got: {output}"
        );
        assert!(
            output.contains("main"),
            "should display branch value, got: {output}"
        );
        assert!(
            output.contains("Priority"),
            "should display Priority label, got: {output}"
        );
        assert!(
            output.contains("100"),
            "should display priority value, got: {output}"
        );
        assert!(
            output.contains("Sync Interval"),
            "should display Sync Interval label, got: {output}"
        );
        assert!(
            output.contains("Auto Apply"),
            "should display Auto Apply label, got: {output}"
        );
    }

    #[test]
    fn cmd_source_show_with_state_shows_status_section() {
        let h = CliTestHarness::builder().rich_config().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_config_source(
                "team-config",
                "https://github.com/team/config",
                "main",
                Some("deadbeef1234"),
                Some("3.1.0"),
                None,
            )
            .unwrap();

        super::cmd_source_show(&h.cli(), h.printer(), "team-config").unwrap();

        let output = h.output();
        assert!(
            output.contains("State"),
            "should display State section, got: {output}"
        );
        assert!(
            output.contains("Status"),
            "should display Status within State section, got: {output}"
        );
        assert!(
            output.contains("Last Fetched"),
            "should display Last Fetched, got: {output}"
        );
        // Last Commit should be truncated to 12 chars
        assert!(
            output.contains("deadbeef1234"),
            "should display truncated commit hash, got: {output}"
        );
        assert!(
            output.contains("3.1.0"),
            "should display version, got: {output}"
        );
    }

    #[test]
    fn cmd_source_show_with_managed_resources_shows_table() {
        let h = CliTestHarness::builder().rich_config().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_managed_resource("package", "brew/curl", "team-config", None, None)
            .unwrap();
        state
            .upsert_managed_resource("file", "~/.bashrc", "team-config", None, None)
            .unwrap();

        super::cmd_source_show(&h.cli(), h.printer(), "team-config").unwrap();

        let output = h.output();
        assert!(
            output.contains("Managed Resources"),
            "should display Managed Resources section, got: {output}"
        );
        assert!(
            output.contains("brew/curl"),
            "should list brew/curl resource, got: {output}"
        );
        assert!(
            output.contains("~/.bashrc"),
            "should list ~/.bashrc resource, got: {output}"
        );
    }

    #[test]
    fn cmd_source_show_json_includes_managed_resources() {
        let h = CliTestHarness::builder().rich_config().json().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_managed_resource("env", "EDITOR", "team-config", None, None)
            .unwrap();

        super::cmd_source_show(&h.cli(), h.printer(), "team-config").unwrap();

        let parsed = h.json_output();
        assert_eq!(parsed["name"], "team-config");
        let resources = parsed["managedResources"]
            .as_array()
            .expect("should be array");
        assert_eq!(resources.len(), 1);
        assert_eq!(resources[0]["resourceType"], "env");
        assert_eq!(resources[0]["resourceId"], "EDITOR");
    }

    // -----------------------------------------------------------------------
    // cmd_source_remove — keep_all reassigns resources to local
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_remove_keep_all_reassigns_resources_to_local() {
        let h = CliTestHarness::builder().rich_config().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        // Pre-populate managed resources owned by team-config
        state
            .upsert_managed_resource("package", "brew/curl", "team-config", Some("hash1"), None)
            .unwrap();
        state
            .upsert_managed_resource("env", "EDITOR", "team-config", Some("hash2"), None)
            .unwrap();

        let result = super::cmd_source_remove(&h.cli(), h.printer(), "team-config", true, false);
        assert!(result.is_ok(), "remove with keep_all: {:?}", result.err());

        // Source should be gone from config
        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        assert!(
            cfg.spec.sources.is_empty(),
            "source should be removed from config"
        );

        // Resources should now be owned by "local"
        let resources = state.managed_resources_by_source("local").unwrap();
        assert_eq!(
            resources.len(),
            2,
            "both resources should be reassigned to local"
        );
        let resource_ids: Vec<&str> = resources.iter().map(|r| r.resource_id.as_str()).collect();
        assert!(resource_ids.contains(&"brew/curl"));
        assert!(resource_ids.contains(&"EDITOR"));

        // team-config should have no resources left
        let team_resources = state.managed_resources_by_source("team-config").unwrap();
        assert!(team_resources.is_empty());
    }

    #[test]
    fn cmd_source_remove_remove_all_does_not_reassign() {
        let h = CliTestHarness::builder().rich_config().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();
        state
            .upsert_managed_resource("package", "brew/curl", "team-config", None, None)
            .unwrap();

        let result = super::cmd_source_remove(&h.cli(), h.printer(), "team-config", false, true);
        assert!(result.is_ok(), "remove with remove_all: {:?}", result.err());

        // Source should be gone from config
        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        assert!(cfg.spec.sources.is_empty());

        // Resources should NOT be reassigned to local (they stay with team-config
        // but the source state record is deleted, so they're effectively orphaned)
        let local_resources = state.managed_resources_by_source("local").unwrap();
        assert!(
            local_resources.is_empty(),
            "remove_all should not reassign resources to local"
        );

        h.assert_output_contains("removed");
    }

    #[test]
    fn cmd_source_remove_prints_success_message() {
        let h = CliTestHarness::builder().rich_config().build();

        super::cmd_source_remove(&h.cli(), h.printer(), "team-config", false, true).unwrap();

        h.assert_output_contains("Source 'team-config' removed");
    }

    // -----------------------------------------------------------------------
    // cmd_compliance_diff — actual differences between snapshots
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_compliance_diff_identical_snapshots_reports_no_differences() {
        let h = CliTestHarness::builder().build();

        // Create two identical snapshots
        super::cmd_compliance_snapshot(&h.cli(), h.printer()).unwrap();
        super::cmd_compliance_snapshot(&h.cli(), h.printer()).unwrap();

        let state = super::open_state_store(Some(h.state_path())).unwrap();
        let entries = state.compliance_history(None, 10).unwrap();
        assert_eq!(entries.len(), 2);

        // Clear output before diff
        h.buf.lock().unwrap().clear();

        super::cmd_compliance_diff(&h.cli(), h.printer(), entries[1].id, entries[0].id).unwrap();

        h.assert_output_contains("No differences");
    }

    #[test]
    fn cmd_compliance_diff_with_changes_shows_added_and_removed() {
        let h = CliTestHarness::builder().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();

        // Create first snapshot with one check
        let snap1 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-01T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![cfgd_core::compliance::ComplianceCheck {
                category: "packages".into(),
                target: Some("brew/curl".into()),
                status: cfgd_core::compliance::ComplianceStatus::Compliant,
                ..Default::default()
            }],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 1,
                warning: 0,
                violation: 0,
            },
        };
        state.store_compliance_snapshot(&snap1, "hash1").unwrap();

        // Create second snapshot with a different check (curl removed, git added)
        let snap2 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-02T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![cfgd_core::compliance::ComplianceCheck {
                category: "packages".into(),
                target: Some("brew/git".into()),
                status: cfgd_core::compliance::ComplianceStatus::Warning,
                ..Default::default()
            }],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 0,
                warning: 1,
                violation: 0,
            },
        };
        state.store_compliance_snapshot(&snap2, "hash2").unwrap();

        let entries = state.compliance_history(None, 10).unwrap();
        assert_eq!(entries.len(), 2);
        // entries are DESC order, so entries[0] is snap2 (id=2), entries[1] is snap1 (id=1)
        let id1 = entries[1].id;
        let id2 = entries[0].id;

        super::cmd_compliance_diff(&h.cli(), h.printer(), id1, id2).unwrap();

        let output = h.output();
        assert!(
            output.contains("Added"),
            "should show Added section for brew/git, got: {output}"
        );
        assert!(
            output.contains("Removed"),
            "should show Removed section for brew/curl, got: {output}"
        );
        assert!(
            output.contains("brew/git"),
            "should mention added check brew/git, got: {output}"
        );
        assert!(
            output.contains("brew/curl"),
            "should mention removed check brew/curl, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_diff_with_status_change_shows_changed() {
        let h = CliTestHarness::builder().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();

        let snap1 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-01T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![cfgd_core::compliance::ComplianceCheck {
                category: "packages".into(),
                target: Some("brew/curl".into()),
                status: cfgd_core::compliance::ComplianceStatus::Compliant,
                detail: None,
                ..Default::default()
            }],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 1,
                warning: 0,
                violation: 0,
            },
        };
        state.store_compliance_snapshot(&snap1, "hash1").unwrap();

        // Same check but status changed from Compliant to Violation
        let snap2 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-02T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![cfgd_core::compliance::ComplianceCheck {
                category: "packages".into(),
                target: Some("brew/curl".into()),
                status: cfgd_core::compliance::ComplianceStatus::Violation,
                detail: Some("package not installed".into()),
                ..Default::default()
            }],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 0,
                warning: 0,
                violation: 1,
            },
        };
        state.store_compliance_snapshot(&snap2, "hash2").unwrap();

        let entries = state.compliance_history(None, 10).unwrap();
        let id1 = entries[1].id;
        let id2 = entries[0].id;

        super::cmd_compliance_diff(&h.cli(), h.printer(), id1, id2).unwrap();

        let output = h.output();
        assert!(
            output.contains("Changed"),
            "should show Changed section, got: {output}"
        );
        assert!(
            output.contains("Compliant") && output.contains("Violation"),
            "should show status transition, got: {output}"
        );
        assert!(
            output.contains("package not installed"),
            "should show detail for changed check, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_diff_structured_json_with_changes() {
        let h = CliTestHarness::builder().json().build();
        let state = super::open_state_store(Some(h.state_path())).unwrap();

        let snap1 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-01T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![cfgd_core::compliance::ComplianceCheck {
                category: "env".into(),
                target: Some("EDITOR".into()),
                status: cfgd_core::compliance::ComplianceStatus::Compliant,
                ..Default::default()
            }],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 1,
                warning: 0,
                violation: 0,
            },
        };
        state.store_compliance_snapshot(&snap1, "hash1").unwrap();

        let snap2 = cfgd_core::compliance::ComplianceSnapshot {
            timestamp: "2026-01-02T00:00:00Z".into(),
            machine: cfgd_core::compliance::MachineInfo {
                hostname: "test".into(),
                os: "linux".into(),
                arch: "x86_64".into(),
            },
            profile: "default".into(),
            sources: vec![],
            checks: vec![
                cfgd_core::compliance::ComplianceCheck {
                    category: "env".into(),
                    target: Some("EDITOR".into()),
                    status: cfgd_core::compliance::ComplianceStatus::Warning,
                    ..Default::default()
                },
                cfgd_core::compliance::ComplianceCheck {
                    category: "packages".into(),
                    target: Some("brew/jq".into()),
                    status: cfgd_core::compliance::ComplianceStatus::Compliant,
                    ..Default::default()
                },
            ],
            summary: cfgd_core::compliance::ComplianceSummary {
                compliant: 1,
                warning: 1,
                violation: 0,
            },
        };
        state.store_compliance_snapshot(&snap2, "hash2").unwrap();

        let entries = state.compliance_history(None, 10).unwrap();
        let id1 = entries[1].id;
        let id2 = entries[0].id;

        super::cmd_compliance_diff(&h.cli(), h.printer(), id1, id2).unwrap();

        let parsed = h.json_output();
        assert_eq!(parsed["id1"], id1);
        assert_eq!(parsed["id2"], id2);
        let added = parsed["added"].as_array().expect("added should be array");
        assert_eq!(added.len(), 1, "one check was added (brew/jq)");
        assert_eq!(added[0]["target"], "brew/jq");
        let changed = parsed["changed"]
            .as_array()
            .expect("changed should be array");
        assert_eq!(changed.len(), 1, "one check changed status (EDITOR)");
        assert_eq!(changed[0]["oldStatus"], "Compliant");
        assert_eq!(changed[0]["newStatus"], "Warning");
        let removed = parsed["removed"]
            .as_array()
            .expect("removed should be array");
        assert!(removed.is_empty(), "nothing was removed");
    }

    // -----------------------------------------------------------------------
    // cmd_compliance_history — with snapshots populated
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_compliance_history_with_entries_shows_table() {
        let h = CliTestHarness::builder().build();

        // Create a snapshot to populate history
        super::cmd_compliance_snapshot(&h.cli(), h.printer()).unwrap();

        h.buf.lock().unwrap().clear();

        super::cmd_compliance_history(&h.cli(), h.printer(), None).unwrap();

        let output = h.output();
        assert!(
            output.contains("Compliance History"),
            "should show Compliance History header, got: {output}"
        );
        // Table should have columns
        assert!(
            output.contains("Compliant"),
            "should have Compliant column, got: {output}"
        );
        assert!(
            output.contains("Warning"),
            "should have Warning column, got: {output}"
        );
        assert!(
            output.contains("Violation"),
            "should have Violation column, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_doctor — invalid config, JSON fields, modules section
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_doctor_with_invalid_config_shows_error_but_succeeds() {
        let h = CliTestHarness::builder()
            .config("this is not valid yaml: [[[")
            .build();

        let result = super::cmd_doctor(&h.cli(), h.printer());
        assert!(
            result.is_ok(),
            "doctor should succeed even with invalid config"
        );

        let output = h.output();
        assert!(
            output.contains("Doctor"),
            "should show Doctor header, got: {output}"
        );
        // The config check should report invalid
        assert!(
            output.contains("not found") || output.contains("Config file"),
            "should mention config file status, got: {output}"
        );
    }

    #[test]
    fn cmd_doctor_json_has_all_top_level_fields() {
        let h = CliTestHarness::builder().json().build();

        super::cmd_doctor(&h.cli(), h.printer()).unwrap();

        let parsed = h.json_output();
        assert_json_has_fields(
            &parsed,
            &[
                "config",
                "git",
                "secrets",
                "packageManagers",
                "modules",
                "systemConfigurators",
            ],
        );
        assert_json_field_type(&parsed, "config", "object");
        assert_json_field_type(&parsed, "git", "boolean");
        assert_json_field_type(&parsed, "secrets", "object");
        assert_json_field_type(&parsed, "packageManagers", "array");
        assert_json_field_type(&parsed, "modules", "array");
        assert_json_field_type(&parsed, "systemConfigurators", "array");
    }

    #[test]
    fn cmd_doctor_json_config_section_has_expected_fields() {
        let h = CliTestHarness::builder().json().build();

        super::cmd_doctor(&h.cli(), h.printer()).unwrap();

        let parsed = h.json_output();
        let config = &parsed["config"];
        assert_json_has_fields(config, &["valid", "path"]);
        assert_eq!(config["valid"], true);
        assert!(
            config["name"].is_string(),
            "name should be present for valid config"
        );
    }

    #[test]
    fn cmd_doctor_with_module_in_profile() {
        let profile_with_module = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - test-mod
  packages:
    cargo:
      - bat
"#;
        let h = CliTestHarness::builder()
            .profile("default", profile_with_module)
            .module("test-mod", SIMPLE_MODULE_YAML)
            .build();

        super::cmd_doctor(&h.cli(), h.printer()).unwrap();

        let output = h.output();
        assert!(
            output.contains("Modules"),
            "should show Modules section when modules declared, got: {output}"
        );
        assert!(
            output.contains("test-mod"),
            "should list the test-mod module, got: {output}"
        );
    }

    #[test]
    fn cmd_doctor_with_missing_module_reports_not_found() {
        let profile_with_missing_module = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - nonexistent-mod
  packages:
    cargo:
      - bat
"#;
        let h = CliTestHarness::builder()
            .profile("default", profile_with_missing_module)
            .build();

        super::cmd_doctor(&h.cli(), h.printer()).unwrap();

        let output = h.output();
        assert!(
            output.contains("nonexistent-mod"),
            "should mention the missing module, got: {output}"
        );
        assert!(
            output.contains("not found"),
            "should report module not found, got: {output}"
        );
    }

    #[test]
    fn cmd_doctor_json_with_missing_module_shows_error() {
        let profile_with_missing_module = r#"apiVersion: cfgd.io/v1alpha1
kind: Profile
metadata:
  name: default
spec:
  modules:
    - ghost-mod
  packages:
    cargo:
      - bat
"#;
        let h = CliTestHarness::builder()
            .profile("default", profile_with_missing_module)
            .json()
            .build();

        super::cmd_doctor(&h.cli(), h.printer()).unwrap();

        let parsed = h.json_output();
        let modules = parsed["modules"]
            .as_array()
            .expect("modules should be array");
        assert_eq!(modules.len(), 1);
        assert_eq!(modules[0]["name"], "ghost-mod");
        assert_eq!(modules[0]["valid"], false);
        assert!(
            modules[0]["error"].is_string(),
            "error should be set for missing module"
        );
    }

    // -----------------------------------------------------------------------
    // cmd_decide — exercise decision resolution logic with state verification
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_decide_no_args_no_pending_shows_info() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();

        // With no resource, no source, and all=false, should show pending list
        super::cmd_decide(
            &printer,
            "accept",
            None,
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("No pending decisions"),
            "should report no pending decisions, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_no_args_with_pending_shows_list() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision("alpha", "pkg/git", "required", "install", "Install git")
            .unwrap();
        state
            .upsert_pending_decision("beta", "env/EDITOR", "recommended", "set", "Set EDITOR")
            .unwrap();

        // No resource/source/all — should display pending decisions
        super::cmd_decide(
            &printer,
            "accept",
            None,
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("Pending Decisions"),
            "should show Pending Decisions header, got: {output}"
        );
        assert!(
            output.contains("alpha"),
            "should list alpha source, got: {output}"
        );
        assert!(
            output.contains("beta"),
            "should list beta source, got: {output}"
        );
        assert!(
            output.contains("pkg/git"),
            "should list pkg/git resource, got: {output}"
        );
        assert!(
            output.contains("env/EDITOR"),
            "should list env/EDITOR resource, got: {output}"
        );
        // Usage hint
        assert!(
            output.contains("cfgd decide accept"),
            "should show usage hint, got: {output}"
        );

        // Decisions should still be pending (not resolved by just viewing)
        let pending = state.pending_decisions().unwrap();
        assert_eq!(pending.len(), 2, "viewing should not resolve decisions");
    }

    #[test]
    fn cmd_decide_reject_specific_resource_verifies_resolution() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision(
                "team",
                "packages.brew.jq",
                "recommended",
                "install",
                "Install jq",
            )
            .unwrap();

        super::cmd_decide(
            &printer,
            "reject",
            Some("packages.brew.jq"),
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("REJECTED"),
            "should confirm rejection, got: {output}"
        );
        assert!(
            output.contains("packages.brew.jq"),
            "should mention resource name, got: {output}"
        );
        assert!(
            output.contains("not be applied"),
            "rejected resource should mention 'not be applied', got: {output}"
        );

        let pending = state.pending_decisions().unwrap();
        assert!(pending.is_empty(), "decision should be resolved");
    }

    #[test]
    fn cmd_decide_accept_specific_resource_verifies_messaging() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision("team", "file/bashrc", "required", "create", "Create bashrc")
            .unwrap();

        super::cmd_decide(
            &printer,
            "accept",
            Some("file/bashrc"),
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("ACCEPTED"),
            "should confirm acceptance, got: {output}"
        );
        assert!(
            output.contains("file/bashrc"),
            "should mention resource name, got: {output}"
        );
        assert!(
            output.contains("be applied"),
            "accepted resource should mention 'be applied', got: {output}"
        );
    }

    #[test]
    fn cmd_decide_accept_nonexistent_resource_warns() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();

        super::cmd_decide(
            &printer,
            "accept",
            Some("no.such.resource"),
            None,
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("No pending decision found"),
            "should warn about nonexistent resource, got: {output}"
        );
        assert!(
            output.contains("no.such.resource"),
            "should mention the resource name, got: {output}"
        );
    }

    #[test]
    fn cmd_decide_accept_all_reports_count() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        for i in 0..3 {
            state
                .upsert_pending_decision(
                    "src",
                    &format!("pkg/{i}"),
                    "recommended",
                    "install",
                    &format!("Install pkg {i}"),
                )
                .unwrap();
        }

        super::cmd_decide(&printer, "accept", None, None, true, Some(state_dir.path())).unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("ACCEPTED"),
            "should confirm acceptance, got: {output}"
        );
        assert!(
            output.contains("3 items"),
            "should report count of 3 items, got: {output}"
        );
        assert!(
            output.contains("next reconcile"),
            "should mention next reconcile, got: {output}"
        );

        let pending = state.pending_decisions().unwrap();
        assert!(pending.is_empty(), "all decisions should be resolved");
    }

    #[test]
    fn cmd_decide_reject_by_source_preserves_other_sources() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision("alpha", "pkg/a", "recommended", "install", "A")
            .unwrap();
        state
            .upsert_pending_decision("alpha", "pkg/b", "recommended", "install", "B")
            .unwrap();
        state
            .upsert_pending_decision("beta", "env/X", "required", "set", "X")
            .unwrap();

        super::cmd_decide(
            &printer,
            "reject",
            None,
            Some("alpha"),
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("REJECTED"),
            "should confirm rejection, got: {output}"
        );
        assert!(
            output.contains("2 items"),
            "should report 2 items rejected from alpha, got: {output}"
        );
        assert!(
            output.contains("alpha"),
            "should mention source name, got: {output}"
        );

        // Only beta's decision should remain
        let pending = state.pending_decisions().unwrap();
        assert_eq!(pending.len(), 1, "only beta's decision should remain");
        assert_eq!(pending[0].source, "beta");
        assert_eq!(pending[0].resource, "env/X");
    }

    #[test]
    fn cmd_decide_reject_by_source_with_no_matching_decisions() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision("alpha", "pkg/a", "recommended", "install", "A")
            .unwrap();

        super::cmd_decide(
            &printer,
            "reject",
            None,
            Some("nonexistent-source"),
            false,
            Some(state_dir.path()),
        )
        .unwrap();

        let output = buf.lock().unwrap().clone();
        assert!(
            output.contains("No pending decisions for source"),
            "should report no decisions for this source, got: {output}"
        );

        // Alpha's decision should be untouched
        let pending = state.pending_decisions().unwrap();
        assert_eq!(pending.len(), 1);
    }

    #[test]
    fn cmd_decide_accept_single_item_singular_message() {
        let state_dir = tempfile::tempdir().unwrap();
        let (printer, buf) = Printer::for_test();
        let state = super::open_state_store(Some(state_dir.path())).unwrap();

        state
            .upsert_pending_decision("src", "pkg/only", "recommended", "install", "Only pkg")
            .unwrap();

        super::cmd_decide(&printer, "accept", None, None, true, Some(state_dir.path())).unwrap();

        let output = buf.lock().unwrap().clone();
        // When exactly 1 item, the message should use singular "item" not "items"
        assert!(
            output.contains("1 item"),
            "should report singular '1 item', got: {output}"
        );
        assert!(
            !output.contains("1 items"),
            "should NOT use plural '1 items', got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_source_update error display path
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_update_load_failure_displays_error() {
        // Config has a source pointing to a non-existent git URL.
        // load_source will fail, exercising the Err(e) branch that
        // calls printer.error and state.update_config_source_status.
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: my-source
      origin:
        url: file:///nonexistent/repo.git
        branch: main
        type: Git
      subscription:
        priority: 300
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        // The update should succeed overall (errors per source are printed, not propagated)
        super::cmd_source_update(&h.cli(), h.printer(), None).unwrap();
        h.assert_header("Update Sources");
        h.assert_output_contains("Failed to update source 'my-source'");
    }

    #[test]
    fn cmd_source_update_named_load_failure_displays_error() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: alpha
      origin:
        url: file:///nonexistent/alpha.git
        branch: main
        type: Git
      subscription:
        priority: 100
    - name: beta
      origin:
        url: file:///nonexistent/beta.git
        branch: main
        type: Git
      subscription:
        priority: 200
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        // Update only 'alpha'; 'beta' should not appear in output
        super::cmd_source_update(&h.cli(), h.printer(), Some("alpha")).unwrap();
        h.assert_output_contains("Failed to update source 'alpha'");
        let output = h.output();
        assert!(
            !output.contains("beta"),
            "should not attempt to update 'beta' when 'alpha' was specified, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_source_replace — replace removes old and adds new
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_replace_existing_source() {
        // Set up a config with a source, then replace it.
        // The replacement will fail at the "add" step (can't clone the new URL),
        // but the remove step will succeed, exercising the replace flow.
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: old-source
      origin:
        url: https://github.com/old/config
        branch: main
        type: Git
      subscription:
        priority: 400
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        // Replace will display the header and remove old source, then fail on clone
        let result = super::cmd_source_replace(
            &h.cli(),
            h.printer(),
            "old-source",
            "file:///nonexistent/new-config.git",
        );
        // The header should be printed regardless of success/failure
        h.assert_output_contains("Replace Source: old-source");
        h.assert_output_contains("Remove Source: old-source");
        // The add step will fail (can't clone), so the overall result should be Err
        assert!(
            result.is_err(),
            "replace should fail when new source URL is unreachable"
        );
        // Verify old source was removed from config
        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        assert!(
            !cfg.spec.sources.iter().any(|s| s.name == "old-source"),
            "old source should have been removed from config"
        );
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_source_priority — view displays key_value fields
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_source_priority_view_displays_fields() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team-src
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 750
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        super::cmd_source_priority(&h.cli(), h.printer(), "team-src", None).unwrap();
        // View mode should display source name and priority value
        h.assert_output_contains("team-src");
        h.assert_output_contains("750");
        h.assert_output_contains("Local config priority is 1000");
    }

    #[test]
    fn cmd_source_priority_update_displays_change() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team-src
      origin:
        url: https://github.com/team/config
        branch: main
        type: Git
      subscription:
        priority: 750
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        super::cmd_source_priority(&h.cli(), h.printer(), "team-src", Some(100)).unwrap();
        // Should display the old->new priority change
        h.assert_output_contains("priority updated: 750 -> 100");
        // Verify the file was actually updated
        let cfg = config::load_config(&h.config_path().join("cfgd.yaml")).unwrap();
        let source = cfg
            .spec
            .sources
            .iter()
            .find(|s| s.name == "team-src")
            .unwrap();
        assert_eq!(source.subscription.priority, 100);
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_checkin — server unreachable exercises config loading,
    // hash computation, and server client construction paths
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_checkin_server_unreachable() {
        let h = CliTestHarness::builder().build();
        let result = super::cmd_checkin(
            &h.cli(),
            h.printer(),
            "http://127.0.0.1:19999",
            Some("test-api-key"),
            Some("test-device-42"),
        );
        // The call should fail because the server is unreachable
        assert!(
            result.is_err(),
            "checkin should fail with unreachable server"
        );
        let err_msg = result.unwrap_err().to_string();
        assert!(
            err_msg.contains("checkin failed")
                || err_msg.contains("Connection refused")
                || err_msg.contains("connection"),
            "error should mention connection failure, got: {err_msg}"
        );
    }

    #[test]
    fn cmd_checkin_with_compliance_config_server_unreachable() {
        // Config with compliance enabled — exercises the compliance snapshot
        // collection branch before the checkin HTTP call fails.
        let config_with_compliance = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  compliance:
    enabled: true
    scope:
      packages: true
      files: true
"#;
        let h = CliTestHarness::builder()
            .config(config_with_compliance)
            .build();
        let result =
            super::cmd_checkin(&h.cli(), h.printer(), "http://127.0.0.1:19999", None, None);
        assert!(
            result.is_err(),
            "checkin should fail with unreachable server"
        );
        // The compliance snapshot collection path was exercised before the failure
        let output = h.output();
        assert!(
            output.contains("Compliance:") || output.contains("Checking in"),
            "should show compliance or checkin info before failing, got: {output}"
        );
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_compliance_export — snapshot stored then exported to file
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_compliance_export_writes_file_and_displays_path() {
        let h = CliTestHarness::builder().build();
        super::cmd_compliance_export(&h.cli(), h.printer()).unwrap();
        let output = h.output();
        // export writes a file and prints the path in a success message
        assert!(
            output.contains("Compliance snapshot written to"),
            "should confirm file was written, got: {output}"
        );
        // The output should also include the summary table
        assert!(
            output.contains("Compliance Snapshot"),
            "should display the compliance summary header, got: {output}"
        );
    }

    #[test]
    fn cmd_compliance_export_json_returns_snapshot_object() {
        let h = CliTestHarness::builder().json().build();
        super::cmd_compliance_export(&h.cli(), h.printer()).unwrap();
        let parsed = h.json_output();
        // Structured output should contain the snapshot wrapper
        assert_json_has_fields(&parsed, &["snapshot"]);
        let snapshot = &parsed["snapshot"];
        assert_json_has_fields(snapshot, &["timestamp", "profile", "summary"]);
    }

    // -----------------------------------------------------------------------
    // Coverage: cmd_sync — displays pull result and source sync header
    // -----------------------------------------------------------------------

    #[test]
    fn cmd_sync_non_git_shows_pull_warning_and_sync_header() {
        // A tempdir is not a git repo, so git_pull_sync will fail with a
        // warning. The test verifies both the header and the pull-failure warning path.
        let h = CliTestHarness::builder().build();
        super::cmd_sync(&h.cli(), h.printer()).unwrap();
        h.assert_header("Sync");
        let output = h.output();
        // git_pull_sync on a non-git dir returns Err, displayed as a warning
        assert!(
            output.contains("Syncing local repo with remote"),
            "should display sync progress message, got: {output}"
        );
        assert!(
            output.contains("Pull failed") || output.contains("up to date"),
            "should show pull result (failure or up-to-date), got: {output}"
        );
    }

    #[test]
    fn cmd_sync_with_sources_shows_source_section() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: team-config
      origin:
        url: file:///nonexistent/repo.git
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        super::cmd_sync(&h.cli(), h.printer()).unwrap();
        h.assert_header("Sync");
        // When sources are configured, the Sources subheader should appear
        h.assert_output_contains("Sources");
        h.assert_output_contains("Syncing source 'team-config'");
        // The source sync will fail because the URL is non-existent
        h.assert_output_contains("Failed to sync 'team-config'");
    }

    // -----------------------------------------------------------------------
    // Coverage: Command dispatch match arms via execute()
    // -----------------------------------------------------------------------

    #[test]
    fn execute_dispatch_checkin() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Checkin {
            server_url: "http://127.0.0.1:19999".to_string(),
            api_key: None,
            device_id: Some("test-device".to_string()),
        });
        let result = super::execute(&cli, h.printer());
        // Checkin fails because server is unreachable, but dispatch arm was exercised
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(
            err_msg.contains("checkin") || err_msg.contains("Connection"),
            "dispatch should route to cmd_checkin, got error: {err_msg}"
        );
    }

    #[test]
    fn execute_dispatch_source_update() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: src1
      origin:
        url: file:///nonexistent/repo.git
        branch: main
        type: Git
      subscription:
        priority: 100
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        let cli = h.cli_with_command(Command::Source {
            command: SourceCommand::Update { name: None },
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_header("Update Sources");
        // Source load fails, error is displayed but command succeeds
        h.assert_output_contains("Failed to update source 'src1'");
    }

    #[test]
    fn execute_dispatch_source_priority() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: my-source
      origin:
        url: https://github.com/org/config
        branch: main
        type: Git
      subscription:
        priority: 500
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        let cli = h.cli_with_command(Command::Source {
            command: SourceCommand::Priority {
                name: "my-source".to_string(),
                value: None,
            },
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_output_contains("my-source");
        h.assert_output_contains("500");
    }

    #[test]
    fn execute_dispatch_source_replace() {
        let config_with_source = r#"apiVersion: cfgd.io/v1alpha1
kind: Config
metadata:
  name: t
spec:
  profile: default
  sources:
    - name: replaceable
      origin:
        url: https://github.com/old/config
        branch: main
        type: Git
      subscription:
        priority: 600
"#;
        let h = CliTestHarness::builder().config(config_with_source).build();
        let cli = h.cli_with_command(Command::Source {
            command: SourceCommand::Replace {
                old_name: "replaceable".to_string(),
                new_url: "file:///nonexistent/new.git".to_string(),
            },
        });
        // Dispatches through execute -> cmd_source_replace
        let result = super::execute(&cli, h.printer());
        // Replace will fail on the add step, but dispatch arm is exercised
        assert!(result.is_err());
        h.assert_output_contains("Replace Source: replaceable");
    }

    #[test]
    fn execute_dispatch_compliance_export() {
        let h = CliTestHarness::builder().build();
        let cli = h.cli_with_command(Command::Compliance {
            command: Some(ComplianceCommand::Export),
        });
        super::execute(&cli, h.printer()).unwrap();
        h.assert_output_contains("Compliance snapshot written to");
    }
}
