//! Lucid Train — agentic terminal harness with a Codex-style TUI.
//!
//! Interactive: `lucid-train`
//! Headless:    `lucid-train exec "fix the failing tests"`
//! Models:      `lucid-train models`

use clap::{Parser, Subcommand};
use lucid_train::config::{self, load_config, CliOverrides, Provider, MODEL_PRESETS};
use lucid_train::{exec, lucid_auth, serve, tui};

#[derive(Parser)]
#[command(
    name = "lucid-train",
    version,
    about = ">_ Lucid Train — an agentic terminal harness with a Codex-style CLI.\nOpen-source models via OpenRouter, self-hosted endpoints, and local Ollama.",
    arg_required_else_help = false
)]
struct Cli {
    /// Task to run interactively as the first message.
    task: Vec<String>,

    /// Model alias or id (try `lucid-train models`).
    #[arg(short, long, global = true)]
    model: Option<String>,

    /// Provider: openrouter | openai | ollama | custom.
    #[arg(short, long, global = true)]
    provider: Option<String>,

    /// OpenAI-compatible base URL (self-hosted vLLM / llama.cpp / SGLang...).
    #[arg(long, global = true)]
    base_url: Option<String>,

    /// API key (falls back to LLM_API_KEY / OPENROUTER_API_KEY / OPENAI_API_KEY).
    #[arg(long, global = true)]
    api_key: Option<String>,

    /// Approval policy: plan | auto | full-auto.
    #[arg(short, long, global = true)]
    approvals: Option<String>,

    /// Maximum agent iterations per turn.
    #[arg(long, global = true)]
    max_iterations: Option<u32>,

    /// Working directory for the agent.
    #[arg(short = 'C', long, global = true)]
    cwd: Option<String>,

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

#[derive(Subcommand)]
enum Cmd {
    /// Run a task non-interactively (benchmark/CI mode) and exit.
    Exec {
        /// The task prompt.
        task: Vec<String>,
    },
    /// Speak newline-delimited JSON over stdio (for embedding in other apps).
    Serve,
    /// List model presets (OpenRouter open-source frontier + local Ollama).
    Models,
    /// Sign in to Lucid Cloud with Google (opens your browser).
    Login,
    /// Sign out of Lucid Cloud and clear the saved token.
    Logout,
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();
    let overrides = CliOverrides {
        model: cli.model.clone(),
        provider: cli.provider.clone(),
        base_url: cli.base_url.clone(),
        api_key: cli.api_key.clone(),
        approvals: cli.approvals.clone(),
        max_iterations: cli.max_iterations,
        cwd: cli.cwd.clone(),
    };
    let cfg = load_config(&overrides);

    match cli.command {
        Some(Cmd::Models) => {
            println!("Model presets ({} total):\n", MODEL_PRESETS.len());
            for p in MODEL_PRESETS {
                println!(
                    "  {:<22} {:<36} [{}] {} ctx\n  {:<22} {}\n",
                    p.alias,
                    p.id,
                    p.provider.label(),
                    config::fmt_context_window(p.context_tokens),
                    "",
                    p.note
                );
            }
            println!("Anything on OpenRouter works: lucid-train -m vendor/model-id");
            println!("Self-hosted (vLLM/llama.cpp/SGLang): lucid-train --base-url http://host:8000/v1 -m served-model");
            println!("Local Ollama: lucid-train -p ollama -m qwen2.5-coder:7b");
            return;
        }
        Some(Cmd::Login) => {
            let http = reqwest::Client::new();
            let base = Provider::LucidCloud.default_base_url();
            println!(">_ Signing in to Lucid Cloud — opening your browser…");
            match lucid_auth::login(&http, &base).await {
                Ok(info) => {
                    let _ = config::save_config_values(&[
                        ("provider", Provider::LucidCloud.label()),
                        ("lucid_token", info.token.as_str()),
                        ("lucid_email", info.email.as_str()),
                        ("onboarded", "true"),
                    ]);
                    let plan = if info.status == "none" || info.status.is_empty() {
                        "no active plan — subscribe at lucidtrain.com/pricing".to_string()
                    } else {
                        format!("{} plan ({}) • ${:.2} credit", info.tier, info.status, info.balance_usd)
                    };
                    println!("✓ Signed in as {} — {plan}", info.email);
                }
                Err(e) => {
                    eprintln!("login failed: {e:#}\n  Is the Lucid Cloud backend reachable? (set LUCID_CLOUD_URL, default {base})");
                    std::process::exit(1);
                }
            }
            return;
        }
        Some(Cmd::Logout) => {
            let http = reqwest::Client::new();
            let base = Provider::LucidCloud.default_base_url();
            let root = lucid_auth::cloud_root(&base);
            if !cfg.lucid_token.is_empty() {
                let _ = http
                    .post(format!("{root}/auth/logout"))
                    .bearer_auth(&cfg.lucid_token)
                    .timeout(std::time::Duration::from_secs(10))
                    .send()
                    .await;
            }
            let _ = config::save_config_values(&[("lucid_token", ""), ("lucid_email", "")]);
            println!("Signed out of Lucid Cloud.");
            return;
        }
        Some(Cmd::Exec { task }) => {
            let task = task.join(" ");
            if task.trim().is_empty() {
                eprintln!("error: exec requires a task, e.g. lucid-train exec \"fix the failing tests\"");
                std::process::exit(2);
            }
            if let Err(msg) = preflight(&cfg) {
                eprintln!("error: {msg}");
                std::process::exit(2);
            }
            let code = exec::run_exec(cfg, task).await;
            std::process::exit(code);
        }
        Some(Cmd::Serve) => {
            // No preflight: session setup is credential-free, and a missing
            // key surfaces as an error event on the first user.message.
            let code = serve::run_serve(cfg).await;
            std::process::exit(code);
        }
        None => {
            // The TUI always opens — onboarding (/setup, /apikey, /download)
            // handles missing keys and models interactively.
            let initial = cli.task.join(" ");
            if let Err(e) = run_interactive(cfg, initial).await {
                eprintln!("error: {e:#}");
                std::process::exit(1);
            }
        }
    }
}

fn preflight(cfg: &config::Config) -> Result<(), String> {
    if cfg.provider == Provider::LucidCloud {
        if cfg.lucid_token.is_empty() {
            return Err("not signed in to Lucid Cloud. Run `lucid-train login` (Google sign-in), then subscribe at lucidtrain.com/pricing.".to_string());
        }
        return Ok(());
    }
    if cfg.api_key.is_empty() && cfg.provider != Provider::Ollama {
        return Err(format!(
            "no API key configured for provider {}.\n  set OPENROUTER_API_KEY (recommended), LLM_API_KEY, or pass --api-key.\n  For free local models: lucid-train -p ollama -m local-qwen-coder",
            cfg.provider.label()
        ));
    }
    Ok(())
}

async fn run_interactive(cfg: config::Config, _initial: String) -> anyhow::Result<()> {
    // The TUI takes over the terminal; an initial task (if given) is typed in
    // by the user — keeping startup deterministic.
    tui::run_tui(cfg).await
}
