mod client;
mod commands;
mod output;
mod tui;

use anyhow::Result;
use clap::{Parser, Subcommand, ValueEnum};

#[derive(Parser)]
#[command(name = "tael", about = "AI-agent-native observability CLI")]
struct Cli {
    #[command(subcommand)]
    command: Commands,

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

    /// Server address
    #[arg(long, global = true, default_value = "http://127.0.0.1:7701")]
    server: String,
}

#[derive(Clone, ValueEnum)]
enum OutputFormat {
    Json,
    Table,
}

#[derive(Subcommand)]
enum Commands {
    /// Run the tael server: OTLP ingest (gRPC :4317), storage, REST API (:7701)
    Serve {
        /// OTLP gRPC listen address (env: TAEL_OTLP_GRPC_ADDR)
        #[arg(long)]
        otlp_grpc_addr: Option<String>,
        /// REST API listen address (env: TAEL_REST_API_ADDR)
        #[arg(long)]
        rest_api_addr: Option<String>,
        /// Data directory (env: TAEL_DATA_DIR)
        #[arg(long)]
        data_dir: Option<String>,
        /// Storage backend: tael-backend (default) or duckdb (env: TAEL_STORAGE)
        #[arg(long)]
        storage: Option<String>,
    },
    /// Query telemetry data
    Query {
        #[command(subcommand)]
        signal: QuerySignal,
    },
    /// Get a specific resource by ID
    Get {
        #[command(subcommand)]
        resource: GetResource,
    },
    /// Add or view comments on traces
    Comment {
        #[command(subcommand)]
        action: CommentAction,
    },
    /// List known services and their health
    Services,
    /// Interactive TUI trace feed
    Live {
        /// Filter by service name
        #[arg(long)]
        service: Option<String>,
        /// Filter by status (ok, error, unset)
        #[arg(long)]
        status: Option<String>,
        /// Poll interval in seconds
        #[arg(long, default_value = "2")]
        interval: u64,
    },
    /// Aggregated health summary over a time window
    Summarize {
        /// Time window (e.g. 1h, 30m, 7d). Defaults to 1h.
        #[arg(long)]
        last: Option<String>,
        /// Filter to a single service
        #[arg(long)]
        service: Option<String>,
    },
    /// Surface services whose error-rate or p95 regressed vs a baseline window
    Anomalies {
        /// Current window (default 1h)
        #[arg(long)]
        last: Option<String>,
        /// Baseline window to compare against (default: 6× current)
        #[arg(long)]
        baseline: Option<String>,
        /// Filter to a single service
        #[arg(long)]
        service: Option<String>,
    },
    /// Pull spans, logs, and metrics for a trace ID
    Correlate {
        /// Trace ID to correlate across signals
        #[arg(long)]
        trace: String,
    },
    /// Poll the summary endpoint and print deltas between samples
    Watch {
        /// Summary window (default 1m)
        #[arg(long)]
        last: Option<String>,
        /// Filter to a single service
        #[arg(long)]
        service: Option<String>,
        /// Poll interval in seconds
        #[arg(long, default_value = "10")]
        interval: u64,
    },
    /// Server management
    Server {
        #[command(subcommand)]
        action: ServerAction,
    },
    /// Install the tael Claude Code skill
    Skill {
        #[command(subcommand)]
        action: SkillAction,
    },
}

#[derive(Subcommand)]
enum QuerySignal {
    /// Search and filter traces
    Traces {
        /// Filter by service name
        #[arg(long)]
        service: Option<String>,
        /// Filter by operation name (substring match)
        #[arg(long)]
        operation: Option<String>,
        /// Minimum span duration (e.g. 100ms, 1s, 500)
        #[arg(long)]
        min_duration: Option<String>,
        /// Maximum span duration
        #[arg(long)]
        max_duration: Option<String>,
        /// Filter by status (ok, error, unset)
        #[arg(long)]
        status: Option<String>,
        /// Time window (e.g. 1h, 30m, 7d)
        #[arg(long)]
        last: Option<String>,
        /// Max results to return
        #[arg(long, default_value = "100")]
        limit: u32,
        /// Filter by span attribute, repeatable. Format: key=value
        /// (e.g. --attribute http.method=GET --attribute http.status_code=500)
        #[arg(long = "attribute")]
        attribute: Vec<String>,
        /// Full-text search over LLM prompt/completion payloads
        /// (tael-backend storage only; e.g. --text "rate limit")
        #[arg(long)]
        text: Option<String>,
    },
    /// Search and filter metrics
    Metrics {
        /// PromQL subset expression (e.g. `rate(http_requests[5m])`)
        #[arg(long)]
        query: Option<String>,
        /// Filter by service name (ignored when --query is set)
        #[arg(long)]
        service: Option<String>,
        /// Filter by metric name (ignored when --query is set)
        #[arg(long)]
        name: Option<String>,
        /// Filter by metric type (gauge, sum, histogram, summary)
        #[arg(long = "type")]
        metric_type: Option<String>,
        /// Time window (e.g. 1h, 30m, 7d)
        #[arg(long)]
        last: Option<String>,
        /// Max results to return
        #[arg(long, default_value = "500")]
        limit: u32,
    },
    /// Search and filter logs
    Logs {
        /// Filter by service name
        #[arg(long)]
        service: Option<String>,
        /// Filter by severity (trace, debug, info, warn, error, fatal)
        #[arg(long)]
        severity: Option<String>,
        /// Search log body text (substring match)
        #[arg(long)]
        body_contains: Option<String>,
        /// Filter by trace ID
        #[arg(long)]
        trace_id: Option<String>,
        /// Time window (e.g. 1h, 30m, 7d)
        #[arg(long)]
        last: Option<String>,
        /// Max results to return
        #[arg(long, default_value = "100")]
        limit: u32,
    },
    /// Run a read-only SQL query over the telemetry tables
    /// (spans, logs, metrics, trace_comments)
    Sql {
        /// The SQL query (SELECT/WITH only), e.g.
        /// "SELECT service, COUNT(*) FROM spans GROUP BY service"
        query: String,
    },
}

#[derive(Subcommand)]
enum GetResource {
    /// Get a full trace by trace ID
    Trace {
        /// The trace ID to look up
        trace_id: String,
    },
}

#[derive(Subcommand)]
enum CommentAction {
    /// Add a comment to a trace
    Add {
        /// The trace ID to comment on
        trace_id: String,
        /// Comment text
        body: String,
        /// Author name
        #[arg(long, default_value = "cli")]
        author: String,
        /// Optional span ID to attach comment to
        #[arg(long)]
        span_id: Option<String>,
    },
    /// List comments on a trace
    List {
        /// The trace ID to list comments for
        trace_id: String,
    },
}

#[derive(Subcommand)]
enum ServerAction {
    /// Show server status
    Status,
}

#[derive(Subcommand)]
enum SkillAction {
    /// Install SKILL.md into Claude Code's skills directory
    Install {
        /// Install into ./.claude/skills/tael/ instead of ~/.claude/skills/tael/
        #[arg(long)]
        project: bool,
        /// Overwrite an existing SKILL.md at the destination
        #[arg(long)]
        force: bool,
    },
    /// Print the destination path without writing anything
    Where {
        /// Resolve the project-local path instead of the personal path
        #[arg(long)]
        project: bool,
    },
}

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

    // `serve` runs the embedded server; it needs no REST client, so handle it
    // before constructing one.
    if let Commands::Serve {
        otlp_grpc_addr,
        rest_api_addr,
        data_dir,
        storage,
    } = cli.command
    {
        // Start from env defaults, then override with any explicit flags.
        let mut config = tael_server::ServerConfig::from_env();
        if let Some(a) = otlp_grpc_addr {
            config.otlp_grpc_addr = a;
        }
        if let Some(a) = rest_api_addr {
            config.rest_api_addr = a;
        }
        if let Some(d) = data_dir {
            config.data_dir = d;
        }
        if let Some(s) = storage {
            config.storage = tael_server::StorageBackend::parse(&s);
        }
        return tael_server::run(config).await;
    }

    let client = client::TaelClient::new(&cli.server);

    match cli.command {
        // Handled above; the early return means this arm is never reached.
        Commands::Serve { .. } => unreachable!(),
        Commands::Query { signal } => match signal {
            QuerySignal::Traces {
                service,
                operation,
                min_duration,
                max_duration,
                status,
                last,
                limit,
                attribute,
                text,
            } => {
                commands::query::traces(
                    &client,
                    &cli.format,
                    service,
                    operation,
                    min_duration,
                    max_duration,
                    status,
                    last,
                    limit,
                    attribute,
                    text,
                )
                .await?;
            }
            QuerySignal::Metrics {
                query,
                service,
                name,
                metric_type,
                last,
                limit,
            } => {
                commands::query::metrics(
                    &client,
                    &cli.format,
                    query,
                    service,
                    name,
                    metric_type,
                    last,
                    limit,
                )
                .await?;
            }
            QuerySignal::Logs {
                service,
                severity,
                body_contains,
                trace_id,
                last,
                limit,
            } => {
                commands::query::logs(
                    &client,
                    &cli.format,
                    service,
                    severity,
                    body_contains,
                    trace_id,
                    last,
                    limit,
                )
                .await?;
            }
            QuerySignal::Sql { query } => {
                commands::query::sql(&client, &cli.format, &query).await?;
            }
        },
        Commands::Get { resource } => match resource {
            GetResource::Trace { trace_id } => {
                commands::get::trace(&client, &cli.format, &trace_id).await?;
            }
        },
        Commands::Comment { action } => match action {
            CommentAction::Add {
                trace_id,
                body,
                author,
                span_id,
            } => {
                commands::comment::add(
                    &client,
                    &cli.format,
                    &trace_id,
                    &body,
                    Some(&author),
                    span_id.as_deref(),
                )
                .await?;
            }
            CommentAction::List { trace_id } => {
                commands::comment::list(&client, &cli.format, &trace_id).await?;
            }
        },
        Commands::Services => {
            commands::services::list(&client, &cli.format).await?;
        }
        Commands::Live {
            service,
            status,
            interval,
        } => {
            tui::run(&cli.server, service, status, interval).await?;
        }
        Commands::Summarize { last, service } => {
            commands::summarize::run(&client, &cli.format, last, service).await?;
        }
        Commands::Anomalies {
            last,
            baseline,
            service,
        } => {
            commands::anomalies::run(&client, &cli.format, last, baseline, service).await?;
        }
        Commands::Correlate { trace } => {
            commands::correlate::run(&client, &cli.format, &trace).await?;
        }
        Commands::Watch {
            last,
            service,
            interval,
        } => {
            commands::watch::run(&client, &cli.format, last, service, interval).await?;
        }
        Commands::Server { action } => match action {
            ServerAction::Status => {
                commands::server::status(&client, &cli.format).await?;
            }
        },
        Commands::Skill { action } => match action {
            SkillAction::Install { project, force } => {
                commands::skill::install(project, force)?;
            }
            SkillAction::Where { project } => {
                commands::skill::print_path(project)?;
            }
        },
    }

    Ok(())
}
