//! KnishIO CLI — unified orchestration for the KnishIO validator stack.
//!
//! ## Usage
//!
//! ```bash
//! # First-time production setup
//! knishio init --tls
//!
//! # Docker control
//! knishio start --build -d
//! knishio stop
//! knishio destroy --volumes
//! knishio rebuild
//! knishio logs --follow --tail 100
//! knishio status
//!
//! # Cell management
//! knishio cell create TESTCELL --name "Test Cell"
//! knishio cell list
//! knishio cell activate TESTCELL
//! knishio cell pause TESTCELL
//! knishio cell archive TESTCELL
//!
//! # Database management
//! knishio backup
//! knishio backup --output mybackup.sql
//! knishio backup list
//! knishio restore backups/knishio_20260406.sql
//! knishio psql
//! knishio psql -c "SELECT count(*) FROM molecules"
//!
//! # Updates
//! knishio update
//! knishio update --build
//! knishio update --rollback
//!
//! # Benchmarks
//! knishio bench run --types meta,value-transfer --identities 50 --concurrency 5
//! knishio bench generate --types meta -o plan.db
//! knishio bench execute plan.db --concurrency 10
//!
//! # Embedding management
//! knishio embed status
//! knishio embed reset
//! knishio embed reset --model qwen3-embedding-0.6b -y
//! knishio embed search "user profile settings"
//! knishio embed search "token metadata" --limit 20 --threshold 0.8
//! knishio embed ask "what stores sell kitchen stuff?"
//! knishio embed ask "who has the most tokens?" --max-results 30
//!
//! # Health checks
//! knishio health
//! knishio ready
//! knishio full
//! knishio db
//! ```

mod backup;
mod bench;
mod cell;
mod config;
mod docker;
mod embed;
mod health;
mod init;
mod output;
mod paths;
mod update;

use anyhow::Result;
use clap::{Parser, Subcommand};
use std::env;

// ═══════════════════════════════════════════════════════════════
// CLI Definitions
// ═══════════════════════════════════════════════════════════════

#[derive(Parser)]
#[command(
    name = "knishio",
    about = "KnishIO Validator Orchestration CLI",
    version,
    propagate_version = true
)]
struct Cli {
    /// Validator base URL (for health commands)
    #[arg(long, global = true, default_value = "https://localhost:8080")]
    url: String,

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

#[derive(Subcommand)]
enum Commands {
    /// Initialize production deployment (generates secrets, config, certs)
    Init {
        /// Generate self-signed TLS certificates
        #[arg(long)]
        tls: bool,

        /// Set CORS_ORIGINS in generated config
        #[arg(long)]
        cors: Option<String>,
    },

    /// Start the validator stack (docker compose up)
    Start {
        /// Build images before starting
        #[arg(long)]
        build: bool,

        /// Run in detached mode
        #[arg(short, long)]
        detach: bool,
    },

    /// Stop the validator stack
    Stop,

    /// Destroy the validator stack (docker compose down)
    Destroy {
        /// Also remove volumes (all data lost!)
        #[arg(long)]
        volumes: bool,
    },

    /// Rebuild validator image from scratch and restart
    Rebuild,

    /// Pull/build latest version, restart, and verify health
    Update {
        /// Build from source instead of pulling images
        #[arg(long)]
        build: bool,

        /// Roll back to previous version
        #[arg(long, conflicts_with = "build")]
        rollback: bool,
    },

    /// Show container logs
    Logs {
        /// Follow log output
        #[arg(short, long)]
        follow: bool,

        /// Number of lines to show from end
        #[arg(long)]
        tail: Option<usize>,
    },

    /// Show container status
    Status,

    /// Cell management
    Cell {
        #[command(subcommand)]
        command: CellCommands,
    },

    /// Database backup and restore
    Backup {
        #[command(subcommand)]
        command: BackupCommands,
    },

    /// Restore database from a backup file
    Restore {
        /// Path to the backup file
        path: String,

        /// Skip post-restore verification
        #[arg(long)]
        skip_verify: bool,
    },

    /// Open an interactive psql session
    Psql {
        /// Run a single SQL command instead of interactive mode
        #[arg(short, long)]
        command: Option<String>,
    },

    /// Benchmark operations
    Bench {
        #[command(subcommand)]
        command: BenchCommands,
    },

    /// Embedding management (DataBraid VKG)
    Embed {
        #[command(subcommand)]
        command: EmbedCommands,
    },

    /// Quick health check (GET /healthz)
    Health,

    /// Readiness check (GET /readyz)
    Ready,

    /// Full readiness detail (GET /readyz with body)
    Full,

    /// Database consistency check (GET /db-check)
    Db,
}

#[derive(Subcommand)]
enum BackupCommands {
    /// Create a database backup (default)
    Create {
        /// Output file path (default: backups/knishio_TIMESTAMP.sql)
        #[arg(short, long)]
        output: Option<String>,
    },

    /// List available backups
    List,
}

#[derive(Subcommand)]
enum CellCommands {
    /// Create a new cell
    Create {
        /// Cell slug identifier
        slug: String,

        /// Human-readable name (defaults to slug)
        #[arg(long)]
        name: Option<String>,

        /// Initial status
        #[arg(long, default_value = "active")]
        status: String,
    },

    /// List all cells
    List,

    /// Activate a cell
    Activate {
        /// Cell slug
        slug: String,
    },

    /// Pause a cell
    Pause {
        /// Cell slug
        slug: String,
    },

    /// Archive a cell
    Archive {
        /// Cell slug
        slug: String,
    },
}

#[derive(Subcommand)]
enum BenchCommands {
    /// Generate a plan and execute it in one shot
    Run {
        /// Number of identities
        #[arg(long, default_value_t = 50)]
        identities: usize,

        /// Comma-separated molecule types (meta, value-transfer, rule, burn)
        #[arg(long, default_value = "meta", value_delimiter = ',')]
        types: Vec<String>,

        /// Meta mutations per identity
        #[arg(long, default_value_t = 100)]
        metas_per_identity: usize,

        /// Value transfers per identity
        #[arg(long, default_value_t = 10)]
        transfers_per_identity: usize,

        /// Rule molecules per identity
        #[arg(long, default_value_t = 5)]
        rules_per_identity: usize,

        /// Burn molecules per identity
        #[arg(long, default_value_t = 5)]
        burns_per_identity: usize,

        /// Initial token supply
        #[arg(long, default_value_t = 1_000_000.0)]
        token_amount: f64,

        /// Validator endpoint URL
        #[arg(long, default_value = "https://localhost:8080")]
        endpoint: String,

        /// Concurrency level
        #[arg(long, default_value_t = 5)]
        concurrency: usize,

        /// Cell slug
        #[arg(long)]
        cell_slug: Option<String>,

        /// Keep benchmark data in DB after execution (default: auto-cleanup)
        #[arg(long)]
        keep: bool,
    },

    /// Generate a benchmark plan file
    Generate {
        /// Number of identities
        #[arg(long, default_value_t = 50)]
        identities: usize,

        /// Comma-separated molecule types
        #[arg(long, default_value = "meta", value_delimiter = ',')]
        types: Vec<String>,

        /// Meta mutations per identity
        #[arg(long, default_value_t = 100)]
        metas_per_identity: usize,

        /// Value transfers per identity
        #[arg(long, default_value_t = 10)]
        transfers_per_identity: usize,

        /// Rule molecules per identity
        #[arg(long, default_value_t = 5)]
        rules_per_identity: usize,

        /// Burn molecules per identity
        #[arg(long, default_value_t = 5)]
        burns_per_identity: usize,

        /// Initial token supply
        #[arg(long, default_value_t = 1_000_000.0)]
        token_amount: f64,

        /// Output plan file path
        #[arg(short, long)]
        output: String,
    },

    /// Execute an existing benchmark plan
    Execute {
        /// Path to the plan file
        plan: String,

        /// Validator endpoint URL
        #[arg(long, default_value = "https://localhost:8080")]
        endpoint: String,

        /// Concurrency level
        #[arg(long, default_value_t = 5)]
        concurrency: usize,

        /// Cell slug
        #[arg(long)]
        cell_slug: Option<String>,

        /// Keep benchmark data in DB after execution (default: auto-cleanup)
        #[arg(long)]
        keep: bool,
    },

    /// Clean up benchmark data from the database
    Clean {
        /// Specific cell slug to purge
        #[arg(long, conflicts_with = "all")]
        cell_slug: Option<String>,

        /// Purge ALL benchmark cells (BENCH_CLI_*)
        #[arg(long, conflicts_with = "cell_slug")]
        all: bool,
    },
}

#[derive(Subcommand)]
enum EmbedCommands {
    /// Show embedding coverage and model statistics
    Status,

    /// Clear embeddings so the automatic backfill re-embeds them
    Reset {
        /// Clear only embeddings from this specific model name
        #[arg(long, conflicts_with = "all")]
        model: Option<String>,

        /// Clear ALL embeddings (nuclear option)
        #[arg(long, conflicts_with = "model")]
        all: bool,

        /// Skip confirmation prompt
        #[arg(long, short = 'y')]
        yes: bool,
    },

    /// Run semantic search from the terminal
    Search {
        /// Natural language search query
        query: String,

        /// Maximum number of results
        #[arg(long, default_value_t = 10)]
        limit: i32,

        /// Minimum cosine similarity threshold (0.0 to 1.0)
        #[arg(long, default_value_t = 0.7)]
        threshold: f64,

        /// Filter by meta_type
        #[arg(long)]
        meta_type: Option<String>,
    },

    /// Ask a natural language question about DAG data (RAG)
    Ask {
        /// Question to ask
        question: String,

        /// Maximum source records to consider
        #[arg(long, default_value_t = 20)]
        max_results: i32,

        /// Minimum cosine similarity threshold (0.0 to 1.0)
        #[arg(long, default_value_t = 0.5)]
        threshold: f64,

        /// Filter by meta_type
        #[arg(long)]
        meta_type: Option<String>,
    },
}

// ═══════════════════════════════════════════════════════════════
// Main
// ═══════════════════════════════════════════════════════════════

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

    // Load config: file -> env vars -> CLI flags
    let cfg = config::Config::load(&cwd).with_url_override(&cli.url);

    match cli.command {
        // ── Init ────────────────────────────────────────────
        Commands::Init { tls, cors } => {
            init::run(&cwd, tls, cors.as_deref()).await?;
        }

        // ── Docker control ──────────────────────────────────
        Commands::Start { build, detach } => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::start(&compose, build, detach).await?;
        }
        Commands::Stop => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::stop(&compose).await?;
        }
        Commands::Destroy { volumes } => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::destroy(&compose, volumes).await?;
        }
        Commands::Rebuild => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::rebuild(&compose).await?;
        }
        Commands::Update { build, rollback } => {
            let compose = require_compose(&cwd, &cfg)?;
            if rollback {
                update::rollback(&compose, &cfg).await?;
            } else {
                update::update(&compose, &cfg, build).await?;
            }
        }
        Commands::Logs { follow, tail } => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::logs(&compose, follow, tail).await?;
        }
        Commands::Status => {
            let compose = require_compose(&cwd, &cfg)?;
            docker::status(&compose).await?;
        }

        // ── Cell management ─────────────────────────────────
        Commands::Cell { command } => match command {
            CellCommands::Create { slug, name, status } => {
                cell::create(&cfg, &slug, name.as_deref(), &status).await?;
            }
            CellCommands::List => {
                cell::list(&cfg).await?;
            }
            CellCommands::Activate { slug } => {
                cell::set_status(&cfg, &slug, "active").await?;
            }
            CellCommands::Pause { slug } => {
                cell::set_status(&cfg, &slug, "paused").await?;
            }
            CellCommands::Archive { slug } => {
                cell::set_status(&cfg, &slug, "archived").await?;
            }
        },

        // ── Backup / Restore ────────────────────────────────
        Commands::Backup { command } => match command {
            BackupCommands::Create { output } => {
                backup::backup(&cfg, output.as_deref()).await?;
            }
            BackupCommands::List => {
                backup::list().await?;
            }
        },
        Commands::Restore { path, skip_verify } => {
            backup::restore(&cfg, &path, skip_verify).await?;
        }

        // ── Psql ────────────────────────────────────────────
        Commands::Psql { command } => {
            docker::psql(&cfg, command.as_deref()).await?;
        }

        // ── Benchmarks ──────────────────────────────────────
        Commands::Bench { command } => match command {
            BenchCommands::Run {
                identities,
                types,
                metas_per_identity,
                transfers_per_identity,
                rules_per_identity,
                burns_per_identity,
                token_amount,
                endpoint,
                concurrency,
                cell_slug,
                keep,
            } => {
                let gen_args = bench::generate::GenerateArgs {
                    identities,
                    types,
                    metas_per_identity,
                    transfers_per_identity,
                    rules_per_identity,
                    burns_per_identity,
                    token_amount,
                    output: String::new(), // filled by run()
                };
                let exec_args = bench::execute::ExecuteArgs {
                    plan: String::new(), // filled by run()
                    endpoint: Some(endpoint),
                    endpoints: None,
                    strategy: bench::execute::Strategy::RoundRobin,
                    concurrency,
                    cell_slug,
                    csv: None,
                    plot: None,
                    insecure_tls: cfg.validator.insecure_tls,
                };
                bench::run(gen_args, exec_args, &cfg, keep).await?;
            }
            BenchCommands::Generate {
                identities,
                types,
                metas_per_identity,
                transfers_per_identity,
                rules_per_identity,
                burns_per_identity,
                token_amount,
                output: output_path,
            } => {
                let gen_args = bench::generate::GenerateArgs {
                    identities,
                    types,
                    metas_per_identity,
                    transfers_per_identity,
                    rules_per_identity,
                    burns_per_identity,
                    token_amount,
                    output: output_path,
                };
                bench::generate(gen_args)?;
                output::success("Plan generation complete");
            }
            BenchCommands::Execute {
                plan,
                endpoint,
                concurrency,
                cell_slug,
                keep,
            } => {
                let exec_args = bench::execute::ExecuteArgs {
                    plan,
                    endpoint: Some(endpoint),
                    endpoints: None,
                    strategy: bench::execute::Strategy::RoundRobin,
                    concurrency,
                    cell_slug,
                    csv: None,
                    plot: None,
                    insecure_tls: cfg.validator.insecure_tls,
                };
                bench::execute(exec_args, &cfg, keep).await?;
            }
            BenchCommands::Clean { cell_slug, all } => {
                bench::clean(&cfg, cell_slug.as_deref(), all).await?;
            }
        },

        // ── Embedding management ────────────────────────────
        Commands::Embed { command } => match command {
            EmbedCommands::Status => {
                embed::status(&cfg).await?;
            }
            EmbedCommands::Reset { model, all, yes } => {
                embed::reset(&cfg, model.as_deref(), all, yes).await?;
            }
            EmbedCommands::Search {
                query,
                limit,
                threshold,
                meta_type,
            } => {
                embed::search(&cfg, &query, limit, threshold, meta_type.as_deref()).await?;
            }
            EmbedCommands::Ask {
                question,
                max_results,
                threshold,
                meta_type,
            } => {
                embed::ask(&cfg, &question, max_results, threshold, meta_type.as_deref()).await?;
            }
        },

        // ── Health checks ───────────────────────────────────
        Commands::Health => {
            health::healthz(&cfg.validator.url, cfg.validator.insecure_tls).await?;
        }
        Commands::Ready => {
            health::readyz(&cfg.validator.url, false, cfg.validator.insecure_tls).await?;
        }
        Commands::Full => {
            health::readyz(&cfg.validator.url, true, cfg.validator.insecure_tls).await?;
        }
        Commands::Db => {
            health::db_check(&cfg.validator.url, cfg.validator.insecure_tls).await?;
        }
    }

    Ok(())
}

/// Resolve the docker-compose file or exit with a helpful error.
/// Uses the compose_file name from config for discovery.
fn require_compose(cwd: &std::path::Path, cfg: &config::Config) -> Result<std::path::PathBuf> {
    paths::find_compose_file(cwd, &cfg.docker.compose_file).ok_or_else(|| {
        anyhow::anyhow!(
            "Could not find {}.\n\
             Run this command from inside the KnishIO project tree.",
            cfg.docker.compose_file
        )
    })
}
