cstats-cli 0.1.1

Command line interface for cstats
//! cstats command line interface

use std::path::PathBuf;

use clap::{Args, Parser, Subcommand};
use cstats_core::prelude::*;
use tracing::Level;

mod commands;

use commands::*;

/// cstats - Command line statistics collection and analysis tool
#[derive(Debug, Parser)]
#[command(name = "cstats")]
#[command(version, about, long_about = None)]
struct Cli {
    /// Configuration file path
    #[arg(short, long, global = true)]
    config: Option<PathBuf>,

    /// Verbose output level (-v, -vv, -vvv)
    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
    verbose: u8,

    /// Quiet mode (suppress output)
    #[arg(short, long, global = true)]
    quiet: bool,

    /// Output format
    #[arg(long, global = true, value_enum, default_value = "text")]
    format: OutputFormat,

    #[command(subcommand)]
    command: Commands,
}

/// Available output formats
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum OutputFormat {
    /// Human-readable text output
    Text,
    /// JSON output
    Json,
    /// YAML output  
    Yaml,
}

/// Available commands
#[derive(Debug, Subcommand)]
enum Commands {
    /// Initialize cstats configuration
    Init(InitArgs),

    /// Fetch and display Claude Code usage statistics from Anthropic API
    Stats(StatsArgs),

    /// Collect statistics
    Collect(CollectArgs),

    /// Analyze collected statistics
    Analyze(AnalyzeArgs),

    /// Manage cache
    Cache(CacheArgs),

    /// Generate shell hooks
    Hook(HookArgs),

    /// Show configuration
    Config(ConfigArgs),
}

/// Arguments for the init command
#[derive(Debug, Args)]
struct InitArgs {
    /// Configuration file path
    #[arg(short, long)]
    config: Option<PathBuf>,

    /// Force overwrite existing configuration
    #[arg(short, long)]
    force: bool,

    /// Anthropic API key (if not provided, will prompt)
    #[arg(long)]
    api_key: Option<String>,
}

/// Arguments for the stats command
#[derive(Debug, Args)]
struct StatsArgs {
    /// Time period to fetch stats for
    #[arg(short, long, value_enum, default_value = "daily")]
    period: StatsPeriod,

    /// Show detailed breakdown by model
    #[arg(short, long)]
    detailed: bool,

    /// Include rate limit information
    #[arg(short, long)]
    rate_limit: bool,

    /// Include billing information
    #[arg(short, long)]
    billing: bool,

    /// Force refresh (bypass cache)
    #[arg(long)]
    no_cache: bool,
}

/// Time period for stats
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum StatsPeriod {
    /// Last 24 hours
    Daily,
    /// Last 7 days
    Weekly,
    /// Last 30 days
    Monthly,
    /// Summary of all periods
    Summary,
}

/// Arguments for the collect command
#[derive(Debug, Args)]
struct CollectArgs {
    /// Source identifier
    #[arg(short, long)]
    source: String,

    /// Command to execute and measure
    #[arg(short, long)]
    command: Option<String>,

    /// Metrics to collect
    #[arg(short, long, value_delimiter = ',')]
    metrics: Vec<String>,

    /// Custom metadata key=value pairs
    #[arg(long, value_delimiter = ',')]
    metadata: Vec<String>,
}

/// Arguments for the analyze command
#[derive(Debug, Args)]
struct AnalyzeArgs {
    /// Source to analyze
    #[arg(short, long)]
    source: Option<String>,

    /// Start time for analysis (ISO 8601 format)
    #[arg(long)]
    start_time: Option<String>,

    /// End time for analysis (ISO 8601 format)
    #[arg(long)]
    end_time: Option<String>,

    /// Metrics to include in analysis
    #[arg(short, long, value_delimiter = ',')]
    metrics: Vec<String>,

    /// Group results by source
    #[arg(long)]
    group_by_source: bool,
}

/// Arguments for the cache command
#[derive(Debug, Args)]
struct CacheArgs {
    #[command(subcommand)]
    action: CacheAction,
}

/// Cache management actions
#[derive(Debug, Subcommand)]
enum CacheAction {
    /// Show cache statistics
    Stats,
    /// Clear the cache
    Clear,
    /// List cache entries
    List,
}

/// Arguments for the hook command
#[derive(Debug, Args)]
struct HookArgs {
    /// Shell type to generate hook for
    #[arg(value_enum)]
    shell: ShellType,

    /// Output file path (default: stdout)
    #[arg(short, long)]
    output: Option<PathBuf>,
}

/// Supported shell types
#[derive(Debug, Clone, clap::ValueEnum)]
enum ShellType {
    /// Bash shell
    Bash,
    /// Zsh shell
    Zsh,
    /// Fish shell
    Fish,
    /// PowerShell
    Powershell,
}

/// Arguments for the config command
#[derive(Debug, Args)]
struct ConfigArgs {
    #[command(subcommand)]
    action: ConfigAction,
}

/// Configuration actions
#[derive(Debug, Subcommand)]
enum ConfigAction {
    /// Show current configuration
    Show,
    /// Validate configuration
    Validate,
    /// Generate default configuration
    Default,
    /// Set Anthropic API key
    SetApiKey {
        /// API key to set (if not provided, will prompt)
        #[arg(long)]
        api_key: Option<String>,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Initialize logging
    init_logging(cli.verbose, cli.quiet)?;

    // Load configuration
    let config = load_config(cli.config.as_deref()).await?;

    // Execute command
    match cli.command {
        Commands::Init(args) => init_command(args).await,
        Commands::Stats(args) => stats_command(args, cli.format, config).await,
        Commands::Collect(args) => collect_command(args, &config).await,
        Commands::Analyze(args) => analyze_command(args, &config, cli.format).await,
        Commands::Cache(args) => cache_command(args, &config).await,
        Commands::Hook(args) => hook_command(args).await,
        Commands::Config(args) => {
            config_command(args, &config, cli.format, cli.config.as_deref()).await
        }
    }
}

/// Initialize logging based on verbosity level
fn init_logging(verbose: u8, quiet: bool) -> Result<()> {
    if quiet {
        return Ok(());
    }

    let level = match verbose {
        0 => Level::WARN,
        1 => Level::INFO,
        2 => Level::DEBUG,
        _ => Level::TRACE,
    };

    tracing_subscriber::fmt()
        .with_max_level(level)
        .with_target(false)
        .init();

    Ok(())
}

/// Load configuration from file or use defaults
async fn load_config(config_path: Option<&std::path::Path>) -> Result<Config> {
    if let Some(path) = config_path {
        Config::load_from_path(path).await
    } else {
        Config::load().await
    }
}