//! CLI argument parsing — all Clap derive structs for the `roboticus` binary.

use clap::{Parser, Subcommand};

use roboticus_core::config::RoboticusConfig;
use roboticus_server::cli;

// ── Top-level CLI ──────────────────────────────────────────────

#[derive(Parser)]
#[command(
    name = "roboticus",
    version,
    about = "Roboticus Autonomous Agent Runtime"
)]
pub(crate) struct Cli {
    /// Gateway URL for management commands
    #[arg(
        long,
        global = true,
        default_value = "http://localhost:18789",
        env = "ROBOTICUS_URL"
    )]
    pub url: String,

    /// API key for authenticating with the server
    #[arg(long, global = true, env = "ROBOTICUS_API_KEY")]
    pub api_key: Option<String>,

    /// Profile name for state isolation
    #[arg(long, global = true, env = "ROBOTICUS_PROFILE")]
    pub profile: Option<String>,

    /// Path to configuration file
    #[arg(short, long, global = true, env = "ROBOTICUS_CONFIG")]
    pub config: Option<String>,

    /// Color output: auto, always, never
    #[arg(long, global = true, default_value = "auto")]
    pub color: String,

    /// Color theme: crt-green (default), crt-orange, terminal
    #[arg(
        long,
        global = true,
        default_value = "crt-green",
        env = "ROBOTICUS_THEME"
    )]
    pub theme: String,

    /// Disable CRT typewriter draw effect
    #[arg(long, global = true)]
    pub no_draw: bool,

    /// Retro mode: CRT green tint, ASCII symbols, typewriter draw
    #[arg(long, global = true, env = "ROBOTICUS_NERDMODE")]
    pub nerdmode: bool,

    /// Suppress informational output (errors only)
    #[arg(short, long, global = true)]
    pub quiet: bool,

    /// Output structured JSON instead of formatted text
    #[arg(long, global = true)]
    pub json: bool,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

// ── Command hierarchy ────────────────────────────────────────

#[derive(Subcommand)]
pub(crate) enum Commands {
    // ── A-C ────────────────────────────────────────────────
    /// Manage agents
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Agents(AgentsCmd),
    /// Install, list, or uninstall apps
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Apps(AppsCmd),
    /// Manage OAuth authentication for providers
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Auth(AuthCmd),
    /// Inspect channel adapters
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Channels(ChannelsCmd),
    /// Validate configuration
    #[command(next_help_heading = "A-C")]
    Check {
        /// Emit machine-readable JSON
        #[arg(long)]
        json: bool,
    },
    /// Inspect circuit breaker state
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Circuit(CircuitCmd),
    /// Generate shell completions
    #[command(next_help_heading = "A-C")]
    Completion {
        /// Shell: bash, zsh, fish
        shell: String,
    },
    /// Read and write configuration
    #[command(next_help_heading = "A-C")]
    #[command(subcommand)]
    Config(ConfigCmd),

    // ── D-L ────────────────────────────────────────────────
    /// Manage daemon service
    #[command(next_help_heading = "D-L")]
    #[command(subcommand)]
    Daemon(DaemonCmd),
    /// Scan workspace for stale references, config drift, and orphaned artifacts
    #[command(next_help_heading = "D-L")]
    Defrag {
        /// Auto-fix fixable findings
        #[arg(long)]
        fix: bool,
        /// Skip confirmation prompts (use with --fix)
        #[arg(long)]
        yes: bool,
    },
    /// Ingest documents into the knowledge system
    #[command(next_help_heading = "D-L")]
    Ingest {
        /// File or directory path to ingest
        path: String,
        /// Emit machine-readable JSON output
        #[arg(long)]
        json: bool,
    },
    /// Manage channel integrations
    #[command(next_help_heading = "D-L")]
    #[command(subcommand)]
    Integrations(IntegrationsCmd),
    /// Initialize a new workspace
    #[command(next_help_heading = "D-L")]
    Init {
        /// Directory to initialize
        #[arg(default_value = ".")]
        path: String,
    },
    /// Manage encrypted credential store
    #[command(next_help_heading = "D-L")]
    #[command(subcommand)]
    Keystore(KeystoreCmd),
    /// View and tail logs
    #[command(next_help_heading = "D-L")]
    Logs {
        /// Number of lines to show
        #[arg(short = 'n', long, default_value = "50")]
        lines: usize,
        /// Follow log output (stream)
        #[arg(short, long)]
        follow: bool,
        /// Minimum log level: trace, debug, info, warn, error
        #[arg(short, long, default_value = "info")]
        level: String,
    },

    // ── M-R ────────────────────────────────────────────────
    /// Run diagnostics and self-repair
    #[command(alias = "doctor", next_help_heading = "M-R")]
    Mechanic {
        /// Attempt to auto-repair issues
        #[arg(long, short = 'r', alias = "rep")]
        repair: bool,
        /// Emit machine-readable JSON findings
        #[arg(long)]
        json: bool,
        /// Allowlisted paused cron job names to re-enable in --repair mode
        #[arg(long = "allow-job", value_delimiter = ',')]
        allow_job: Vec<String>,
    },
    /// Browse and search memory banks
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Memory(MemoryCmd),
    /// Manage MCP server connections
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Mcp(McpCmd),
    /// View metrics and cost telemetry
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Metrics(MetricsCmd),
    /// Migrate between Legacy and Roboticus
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Migrate(MigrateCmd),
    /// Discover and manage models
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Models(ModelsCmd),
    /// Manage plugins
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Plugins(PluginsCmd),
    /// Manage agent profiles
    #[command(next_help_heading = "M-R")]
    #[command(subcommand)]
    Profile(ProfileCmd),
    /// Reset state to factory defaults
    #[command(next_help_heading = "M-R")]
    Reset {
        /// Skip confirmation prompt
        #[arg(long)]
        yes: bool,
    },

    // ── S-Z ────────────────────────────────────────────────
    /// View and manage scheduled tasks
    #[command(alias = "cron", next_help_heading = "S-Z")]
    #[command(subcommand)]
    Schedule(ScheduleCmd),
    /// Security audit and hardening
    #[command(next_help_heading = "S-Z")]
    #[command(subcommand)]
    Security(SecurityCmd),
    /// Boot the Roboticus runtime
    #[command(alias = "start", alias = "run", next_help_heading = "S-Z")]
    Serve {
        /// Override bind port
        #[arg(short, long)]
        port: Option<u16>,
        /// Override bind address
        #[arg(short, long)]
        bind: Option<String>,
    },
    /// Manage sessions
    #[command(next_help_heading = "S-Z")]
    #[command(subcommand)]
    Sessions(SessionsCmd),
    /// Interactive setup wizard
    #[command(alias = "onboard", next_help_heading = "S-Z")]
    Setup,
    /// Manage skills & plugins
    #[command(next_help_heading = "S-Z")]
    #[command(subcommand)]
    Skills(SkillsCmd),
    /// Display system status
    #[command(next_help_heading = "S-Z")]
    Status,
    /// Interactive terminal user interface
    #[command(next_help_heading = "S-Z")]
    Tui {
        /// Server URL to connect to
        #[arg(long, default_value = "http://localhost:18789")]
        url: String,
        /// Session ID to resume (creates new if omitted)
        #[arg(long)]
        session: Option<String>,
    },
    /// Uninstall Roboticus daemon and data
    #[command(next_help_heading = "S-Z")]
    Uninstall {
        /// Also remove ~/.roboticus/ data directory
        #[arg(long)]
        purge: bool,
    },
    /// Check for and install updates
    #[command(alias = "upgrade", next_help_heading = "S-Z")]
    #[command(subcommand)]
    Update(UpdateCmd),
    /// Report version and build information
    #[command(next_help_heading = "S-Z")]
    Version,
    /// Inspect wallet and treasury
    #[command(next_help_heading = "S-Z")]
    #[command(subcommand)]
    Wallet(WalletCmd),
    /// Open the web dashboard
    #[command(next_help_heading = "S-Z")]
    Web,
}

// ── Subcommand enums ────────────────────────────────────────

#[derive(Subcommand)]
pub(crate) enum AuthCmd {
    /// Log in to a provider via OAuth
    Login {
        /// Provider name (e.g., anthropic)
        #[arg(long)]
        provider: String,
        /// OAuth client ID (overrides config)
        #[arg(long)]
        client_id: Option<String>,
    },
    /// Show OAuth token status
    Status,
    /// Remove stored OAuth tokens for a provider
    Logout {
        /// Provider name (e.g., anthropic)
        #[arg(long)]
        provider: String,
    },
}

#[derive(Subcommand)]
pub(crate) enum SessionsCmd {
    /// List all sessions
    List,
    /// Show session details and messages
    Show { id: String },
    /// Create a new session
    Create { agent_id: String },
    /// Export session to file
    Export {
        /// Session ID
        id: String,
        /// Output format: json, html, markdown
        #[arg(short, long, default_value = "json")]
        format: String,
        /// Output file (default: stdout)
        #[arg(short, long)]
        output: Option<String>,
    },
    /// Backfill nicknames for all sessions missing one
    BackfillNicknames,
}

#[derive(Subcommand)]
pub(crate) enum MemoryCmd {
    /// List entries in a memory tier
    List {
        /// Memory tier: working, episodic, semantic
        tier: String,
        /// Session ID (required for working memory)
        #[arg(short, long)]
        session: Option<String>,
        /// Limit results
        #[arg(short, long)]
        limit: Option<i64>,
    },
    /// Search across memory tiers
    Search {
        /// Search query
        query: String,
        /// Limit results
        #[arg(short, long)]
        limit: Option<i64>,
    },
}

#[derive(Subcommand)]
pub(crate) enum McpCmd {
    /// List active MCP client connections
    List,
    /// Print TOML snippet to add a new MCP server
    Add {
        /// Server name (alphanumeric, hyphens, underscores)
        name: String,
        /// STDIO command to spawn
        #[arg(long, group = "transport")]
        stdio: Option<String>,
        /// SSE endpoint URL
        #[arg(long, group = "transport")]
        sse: Option<String>,
        /// Additional arguments for STDIO command (space-separated)
        #[arg(long, value_delimiter = ' ')]
        args: Vec<String>,
    },
    /// Print guidance to remove an MCP server from config
    Remove {
        /// Server name to remove
        name: String,
    },
    /// Test connectivity to a configured MCP server (requires daemon)
    Test {
        /// Server name to test
        name: String,
    },
}

#[derive(Subcommand)]
pub(crate) enum ScheduleCmd {
    /// List scheduled tasks
    List,
    /// Execute a scheduled task once immediately
    Run {
        /// Cron job name or ID
        job: String,
    },
    /// Re-enable paused cron jobs after unknown-action containment
    Recover {
        /// Re-enable all paused jobs
        #[arg(long)]
        all: bool,
        /// Re-enable only specific job names (repeatable)
        #[arg(long = "name")]
        names: Vec<String>,
        /// Preview what would be changed without writing
        #[arg(long)]
        dry_run: bool,
    },
}

#[derive(Subcommand)]
pub(crate) enum MetricsCmd {
    /// Show inference cost breakdown
    Costs,
    /// Show transaction history
    Transactions {
        /// Time window in hours
        #[arg(short = 'H', long)]
        hours: Option<i64>,
    },
    /// Show semantic cache statistics
    Cache,
}

#[derive(Subcommand)]
pub(crate) enum WalletCmd {
    /// Show wallet overview
    Show,
    /// Display wallet address
    Address,
    /// Check wallet balance
    Balance,
}

#[derive(Subcommand)]
pub(crate) enum CircuitCmd {
    /// Show circuit breaker status
    Status,
    /// Reset tripped circuit breakers
    Reset {
        /// Reset only one provider breaker (e.g., openai)
        provider: Option<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum IntegrationsCmd {
    /// List all channel integrations and their health
    List,
    /// Test connectivity for a specific channel
    Test {
        /// Channel platform name (e.g. discord, telegram)
        platform: String,
    },
    /// Show overall integration health summary
    Health,
    /// Print TOML config snippet for adding a channel integration
    Connect {
        /// Channel platform name (telegram, discord, whatsapp, signal, email, matrix)
        platform: String,
    },
    /// Print guidance for removing a channel integration
    Disconnect {
        /// Channel platform name (telegram, discord, whatsapp, signal, email, matrix)
        platform: String,
    },
}

#[derive(Subcommand)]
pub(crate) enum ChannelsCmd {
    /// List channel adapters and their status
    List,
    /// List dead-letter channel deliveries
    DeadLetter {
        /// Maximum number of dead-letter rows to show
        #[arg(long, default_value_t = 50)]
        limit: usize,
    },
    /// Replay a dead-letter delivery by id
    Replay { id: String },
}

#[derive(Subcommand)]
pub(crate) enum ModelsCmd {
    /// List configured models
    List,
    /// Scan providers for available models
    Scan {
        /// Provider to scan (e.g., ollama, openai)
        provider: Option<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum AgentsCmd {
    /// List all agents
    List,
    /// Start an agent
    Start { id: String },
    /// Stop an agent
    Stop { id: String },
}

#[derive(Subcommand)]
pub(crate) enum DaemonCmd {
    /// Install daemon service (LaunchAgent/systemd/Windows Service)
    Install {
        /// Path to config file
        #[arg(short, long, default_value = "roboticus.toml")]
        config: String,
        /// Start the daemon immediately after install without prompting
        #[arg(long)]
        start: bool,
    },
    /// Start the daemon
    Start,
    /// Stop the daemon
    Stop,
    /// Restart the daemon
    Restart,
    /// Show daemon status
    Status,
    /// Uninstall daemon service
    Uninstall,
}

#[derive(Subcommand)]
pub(crate) enum ConfigCmd {
    /// Show running configuration (from gateway)
    Show,
    /// Get a config value by TOML path
    Get { path: String },
    /// Set a config value
    Set {
        /// TOML path (e.g., models.primary)
        path: String,
        /// New value
        value: String,
        /// Config file to modify
        #[arg(short, long, default_value = "roboticus.toml")]
        file: String,
        /// Skip immediate runtime apply via API
        #[arg(long, default_value_t = false)]
        no_apply: bool,
    },
    /// Remove a config key
    Unset {
        /// TOML path to remove
        path: String,
        /// Config file to modify
        #[arg(short, long, default_value = "roboticus.toml")]
        file: String,
        /// Skip immediate runtime apply via API
        #[arg(long, default_value_t = false)]
        no_apply: bool,
    },
    /// Lint/validate a config file without applying it
    Lint {
        /// Config file to validate
        #[arg(short, long, default_value = "roboticus.toml")]
        file: String,
    },
    /// Create a timestamped backup of a config file
    Backup {
        /// Config file to back up
        #[arg(short, long, default_value = "roboticus.toml")]
        file: String,
    },
}

#[derive(Subcommand)]
pub(crate) enum SkillsCmd {
    /// List all registered skills
    List,
    /// Show skill details
    Show { id: String },
    /// Reload skills from disk
    Reload,
    /// List/search remote skills catalog entries
    CatalogList {
        /// Optional case-insensitive search query
        #[arg(long)]
        query: Option<String>,
    },
    /// Install skills from the catalog
    CatalogInstall {
        /// Skill names to install (accepts names or filenames)
        skills: Vec<String>,
        /// Activate (reload) after install
        #[arg(long)]
        activate: bool,
    },
    /// Activate installed skills (reload)
    CatalogActivate {
        /// Optional skill names for operator context
        skills: Vec<String>,
    },
    /// Import skills from an Legacy workspace or archive
    Import {
        /// Path to Legacy workspace/skills directory or .tar.gz archive
        source: String,
        /// Skip safety checks (dangerous)
        #[arg(long)]
        no_safety_check: bool,
        /// Auto-accept warnings (still blocks on critical findings)
        #[arg(long)]
        accept_warnings: bool,
    },
    /// Export skills to a portable archive
    Export {
        /// Output path for the archive (.tar.gz)
        #[arg(short, long, default_value = "roboticus-skills-export.tar.gz")]
        output: String,
        /// Specific skill IDs (default: all)
        ids: Vec<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum MigrateCmd {
    /// Import data from an Legacy workspace into Roboticus
    Import {
        /// Path to Legacy workspace root
        source: String,
        /// Specific areas to import (default: all)
        #[arg(short, long, value_delimiter = ',')]
        areas: Vec<String>,
        /// Skip confirmation prompts
        #[arg(long)]
        yes: bool,
        /// Skip safety checks on skill scripts
        #[arg(long)]
        no_safety_check: bool,
    },
    /// Export Roboticus data to Legacy format
    Export {
        /// Output directory for the Legacy workspace
        target: String,
        /// Specific areas to export (default: all)
        #[arg(short, long, value_delimiter = ',')]
        areas: Vec<String>,
    },
    /// Migrate an Ironclad installation to Roboticus
    Ironclad {
        /// Source directory (default: ~/.ironclad)
        #[arg(short, long)]
        source: Option<String>,
        /// Skip confirmation prompts
        #[arg(long)]
        yes: bool,
    },
}

#[derive(Subcommand)]
pub(crate) enum PluginsCmd {
    /// List installed plugins
    List,
    /// Show plugin details
    Info { name: String },
    /// Install a plugin from a local dir, .ic.zip archive, or catalog name
    Install {
        /// Plugin source: directory path, .ic.zip path, or catalog plugin name
        source: String,
    },
    /// Uninstall a plugin
    Uninstall { name: String },
    /// Enable a disabled plugin
    Enable { name: String },
    /// Disable a plugin
    Disable { name: String },
    /// Search the plugin catalog
    Search {
        /// Search query (matches name, description, author)
        query: String,
    },
    /// Pack a plugin directory into a distributable .ic.zip archive
    Pack {
        /// Path to the plugin directory containing plugin.toml
        dir: String,
        /// Output directory for the archive (default: current directory)
        #[arg(short, long)]
        output: Option<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum AppsCmd {
    /// Install an app from a local path or registry
    Install {
        /// Local directory path or registry URL
        source: String,
    },
    /// List installed apps
    List,
    /// Uninstall an app
    Uninstall {
        /// App name to remove
        name: String,
        /// Also delete all app data (database, workspace files, memories)
        #[arg(long)]
        delete_data: bool,
    },
}

#[derive(Subcommand)]
pub(crate) enum ProfileCmd {
    /// List installed profiles
    List,
    /// Create a new profile
    Create {
        /// Profile ID (alphanumeric, hyphens, underscores)
        name: String,
        /// Human-readable display name
        #[arg(long)]
        display_name: Option<String>,
    },
    /// Switch active profile
    Switch {
        /// Profile ID to activate
        name: String,
    },
    /// Delete a profile
    Delete {
        /// Profile ID to remove
        name: String,
        /// Keep the profile data directory on disk
        #[arg(long)]
        keep_data: bool,
    },
}

#[derive(Subcommand)]
pub(crate) enum KeystoreCmd {
    /// Store a secret in the keystore
    Set {
        /// Secret name
        key: String,
        /// Secret value (omit for interactive prompt)
        value: Option<String>,
        /// Custom passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
    /// Retrieve a secret
    Get {
        /// Secret name
        key: String,
        /// Custom passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
    /// List all stored secret names
    List {
        /// Custom passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
    /// Remove a secret
    Remove {
        /// Secret name
        key: String,
        /// Custom passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
    /// Import secrets from a JSON file
    Import {
        /// Path to JSON file with {"key": "value", ...} format
        path: String,
        /// Custom passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
    /// Change the keystore passphrase
    Rekey {
        /// Current passphrase (default: machine-derived key)
        #[arg(long)]
        password: Option<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum SecurityCmd {
    /// Run security audit on configuration and permissions
    Audit {
        /// Config file to audit
        #[arg(short, long, default_value = "roboticus.toml")]
        config: String,
    },
}

#[derive(Subcommand)]
pub(crate) enum UpdateCmd {
    /// Show available updates without installing anything
    Check {
        /// Update channel: stable, beta, dev
        #[arg(long, default_value = "stable")]
        channel: String,
        /// Override registry URL for content packs
        #[arg(long, env = "ROBOTICUS_REGISTRY_URL")]
        registry_url: Option<String>,
    },
    /// Update everything (binary + content packs)
    All {
        /// Update channel: stable, beta, dev
        #[arg(long, default_value = "stable")]
        channel: String,
        /// Auto-accept unmodified files (still prompts for conflicts)
        #[arg(long)]
        yes: bool,
        /// Don't restart daemon after update
        #[arg(long)]
        no_restart: bool,
        /// Override registry URL for content packs
        #[arg(long, env = "ROBOTICUS_REGISTRY_URL")]
        registry_url: Option<String>,
        /// Force reinstall even if current version matches
        #[arg(long)]
        force: bool,
    },
    /// Update the Roboticus binary (download or build)
    Binary {
        /// Update channel: stable, beta, dev
        #[arg(long, default_value = "stable")]
        channel: String,
        /// Auto-accept if newer version is available
        #[arg(long)]
        yes: bool,
        /// Update method: download (default) or build
        #[arg(long, default_value = "download", value_parser = ["download", "build"])]
        method: String,
    },
    /// Update bundled provider configurations
    Providers {
        /// Auto-accept unmodified files (still prompts for conflicts)
        #[arg(long)]
        yes: bool,
        /// Override registry URL
        #[arg(long, env = "ROBOTICUS_REGISTRY_URL")]
        registry_url: Option<String>,
    },
    /// Update blessed skill pack
    Skills {
        /// Auto-accept unmodified files (still prompts for conflicts)
        #[arg(long)]
        yes: bool,
        /// Override registry URL
        #[arg(long, env = "ROBOTICUS_REGISTRY_URL")]
        registry_url: Option<String>,
    },
}

// ── Helper functions ────────────────────────────────────────

pub(crate) fn prompt_yes_no(question: &str) -> bool {
    use std::io::Write;
    eprint!("  {question} [y/N] ");
    // best-effort: flush failure is non-critical for interactive prompt
    std::io::stderr().flush().ok();
    let mut input = String::new();
    if std::io::stdin().read_line(&mut input).is_err() {
        return false;
    }
    matches!(input.trim(), "y" | "Y" | "yes" | "Yes" | "YES")
}

pub(crate) fn make_hygiene_fn() -> cli::HygieneFn {
    Box::new(|config_path: &str| {
        let state_db = RoboticusConfig::from_file(std::path::Path::new(config_path))
            .map(|cfg| cfg.database.path)
            .unwrap_or_else(|_| {
                roboticus_core::home_dir()
                    .join(".roboticus")
                    .join("state.db")
            });
        let report = roboticus_server::state_hygiene::run_state_hygiene(&state_db)?;
        if report.changed {
            Ok(Some((
                report.changed_rows,
                report.subagent_rows_normalized,
                report.cron_payload_rows_repaired,
                report.cron_jobs_disabled_invalid_expr,
            )))
        } else {
            Ok(None)
        }
    })
}

pub(crate) fn make_daemon_callbacks() -> cli::DaemonCallbacks {
    cli::DaemonCallbacks {
        is_installed: Box::new(roboticus_server::daemon::is_installed),
        restart: Box::new(|| {
            roboticus_server::daemon::restart_daemon()
                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
        }),
    }
}
