#![deny(clippy::unwrap_used, clippy::expect_used)]
#![allow(clippy::cognitive_complexity)]
#![allow(clippy::redundant_pattern_matching)]

//! FXT -- Foxhunt Operations Platform CLI
//!
//! Thin gRPC client that talks to the API Gateway for all operations:
//! trading, training, tuning, risk, services, cluster, data, and more.

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

use fxt::auth::token_manager::{AuthTokenManager, FileTokenStorage};
use fxt::auth::LoginClient;
use fxt::commands::{
    agent, auth, backtest, broker, cluster, config, data, mcp, model, risk, service, trade,
    train, tune, watch,
};
use fxt::grpc::FoxhuntClient;
use fxt::output::OutputFormat;

/// Foxhunt Operations Platform
#[derive(Parser)]
#[command(name = "fxt", about = "Foxhunt Operations Platform", version)]
struct Cli {
    /// API Gateway endpoint URL
    #[arg(
        long = "api-url",
        env = "FXT_API_URL",
        default_value = "https://api.fxhnt.ai"
    )]
    api_url: String,

    /// Output as JSON (for CI/LLM integration)
    #[arg(long, short)]
    json: bool,

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

#[derive(Subcommand)]
enum Commands {
    /// Authentication
    Auth(auth::AuthCommand),
    /// Order management
    Trade(trade::TradeCommand),
    /// ML training lifecycle
    Train(train::TrainCommand),
    /// Hyperparameter optimization
    Tune(tune::TuneCommand),
    /// Model management
    Model(model::ModelCommand),
    /// Trading agent control
    Agent(agent::AgentCommand),
    /// Backtesting
    Backtest(backtest::BacktestCommand),
    /// Broker connectivity
    Broker(broker::BrokerCommand),
    /// Data pipeline management
    Data(data::DataCommand),
    /// Service operations
    Service(service::ServiceCommand),
    /// Cluster operations
    Cluster(cluster::ClusterCommand),
    /// Risk management
    Risk(risk::RiskCommand),
    /// System configuration
    Config(config::ConfigCommand),
    /// TUI cockpit dashboard
    Watch(watch::WatchCommand),
    /// MCP server mode
    Mcp(mcp::McpCommand),
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();
    let format = if cli.json {
        OutputFormat::Json
    } else {
        OutputFormat::Human
    };

    // Commands that do not need a gRPC connection.
    match &cli.command {
        Commands::Watch(cmd) => return cmd.execute().await,
        Commands::Mcp(cmd) => return cmd.execute().await,
        Commands::Auth(_)
        | Commands::Trade(_)
        | Commands::Train(_)
        | Commands::Tune(_)
        | Commands::Model(_)
        | Commands::Agent(_)
        | Commands::Backtest(_)
        | Commands::Broker(_)
        | Commands::Data(_)
        | Commands::Service(_)
        | Commands::Cluster(_)
        | Commands::Risk(_)
        | Commands::Config(_) => {}
    }

    // Connect to API Gateway (lazy -- no TCP until first RPC).
    let client = FoxhuntClient::connect(&cli.api_url).await?;

    // Auto-refresh: if the access token is expired but a refresh token exists,
    // silently regenerate tokens before dispatching the command.
    // Skip for auth commands (login/logout handle their own token lifecycle).
    if !matches!(cli.command, Commands::Auth(_)) {
        let storage = FileTokenStorage::new()?;
        let manager = AuthTokenManager::new(storage);
        if manager.needs_refresh().await {
            let login_client = LoginClient::new(client.channel());
            if login_client.silent_login(&manager).await? {
                eprintln!("Token refreshed.");
            }
        }
    }

    match &cli.command {
        Commands::Auth(cmd) => cmd.execute(&client, format).await,
        Commands::Trade(cmd) => cmd.execute(&client, format).await,
        Commands::Train(cmd) => cmd.execute(&client, format).await,
        Commands::Tune(cmd) => cmd.execute(&client, format).await,
        Commands::Model(cmd) => cmd.execute(&client, format).await,
        Commands::Agent(cmd) => cmd.execute(&client, format).await,
        Commands::Backtest(cmd) => cmd.execute(&client, format).await,
        Commands::Broker(cmd) => cmd.execute(&client, format).await,
        Commands::Data(cmd) => cmd.execute(&client, format).await,
        Commands::Service(cmd) => cmd.execute(&client, format).await,
        Commands::Cluster(cmd) => cmd.execute(&client, format).await,
        Commands::Risk(cmd) => cmd.execute(&client, format).await,
        Commands::Config(cmd) => cmd.execute(&client, format).await,
        // Already handled above -- unreachable.
        Commands::Watch(_) | Commands::Mcp(_) => Ok(()),
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn test_cli_parse_auth_status() {
        let cli = Cli::parse_from(["fxt", "auth", "status"]);
        assert!(!cli.json);
        assert_eq!(cli.api_url, "https://api.fxhnt.ai");
    }

    #[test]
    fn test_cli_parse_json_flag() {
        let cli = Cli::parse_from(["fxt", "--json", "auth", "status"]);
        assert!(cli.json);
    }

    #[test]
    fn test_cli_parse_api_url_override() {
        let cli = Cli::parse_from(["fxt", "--api-url", "http://localhost:9090", "auth", "login"]);
        assert_eq!(cli.api_url, "http://localhost:9090");
    }

    #[test]
    fn test_cli_parse_trade_submit() {
        let cli = Cli::parse_from([
            "fxt", "trade", "submit", "--symbol", "ES.FUT", "--side", "buy", "--qty", "1",
        ]);
        assert!(matches!(cli.command, Commands::Trade(_)));
    }

    #[test]
    fn test_cli_parse_service_list() {
        let cli = Cli::parse_from(["fxt", "service", "list"]);
        assert!(matches!(cli.command, Commands::Service(_)));
    }

    #[test]
    fn test_cli_parse_watch() {
        let cli = Cli::parse_from(["fxt", "watch"]);
        assert!(matches!(cli.command, Commands::Watch(_)));
    }

    #[test]
    fn test_cli_parse_all_subcommands() {
        // Verify every top-level subcommand parses without error.
        let commands = [
            vec!["fxt", "auth", "status"],
            vec!["fxt", "trade", "positions"],
            vec!["fxt", "train", "list"],
            vec!["fxt", "tune", "status", "some-id"],
            vec!["fxt", "model", "list"],
            vec!["fxt", "agent", "status"],
            vec![
                "fxt", "backtest", "run", "--strategy", "s", "--symbol", "ES", "--from",
                "2025-01-01", "--to", "2025-12-31",
            ],
            vec!["fxt", "broker", "status"],
            vec!["fxt", "data", "status"],
            vec!["fxt", "service", "health"],
            vec!["fxt", "cluster", "status"],
            vec!["fxt", "risk", "status"],
            vec!["fxt", "config", "env"],
            vec!["fxt", "watch"],
            vec!["fxt", "mcp"],
        ];

        for args in &commands {
            let result = Cli::try_parse_from(args.iter());
            assert!(
                result.is_ok(),
                "Failed to parse: {:?} -- {}",
                args,
                result.err().map_or_else(String::new, |e| e.to_string())
            );
        }
    }
}
