mod auth;
mod client;
mod commands;
mod config;
mod error;
mod history;
mod optimizer;
mod output;
mod retry;
mod risk;
mod style;
mod tui;

use std::io::IsTerminal;
use std::sync::OnceLock;

use clap::{CommandFactory, Parser, Subcommand};
use owo_colors::OwoColorize;

/// Global no-color flag, set once during startup.
pub(crate) static NO_COLOR: OnceLock<bool> = OnceLock::new();

use commands::account::AccountCommand;
use commands::basis::BasisCommand;
use commands::borrow::BorrowCommand;
use commands::capital::CapitalCommand;
use commands::fills::FillsCommand;
use commands::markets::MarketsCommand;
use commands::order::OrderCommand;
use commands::portfolio::PortfolioArgs;
use commands::positions::PositionsCommand;
use commands::rfq::RfqCommand;
use commands::risk::RiskCommand;
use commands::scenario::ScenarioCommand;
use commands::strategy::StrategyCommand;
use commands::watch::WatchCommand;
use output::OutputFormat;

/// Exit codes for structured error reporting (agent-friendly)
#[allow(dead_code)]
mod exit_code {
    pub const SUCCESS: i32 = 0;
    pub const GENERAL: i32 = 1;
    pub const AUTH: i32 = 2;
    pub const INPUT: i32 = 3;
    pub const NETWORK: i32 = 4;
}

#[derive(Parser)]
#[command(
    name = "bpx",
    about = "Official CLI for Backpack Exchange",
    version,
    propagate_version = true,
    styles = clap_styles()
)]
struct Cli {
    #[command(subcommand)]
    command: Option<Command>,

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

    /// Config profile to use
    #[arg(short, long, global = true, default_value = "default")]
    profile: String,

    /// Disable colored output
    #[arg(long, global = true)]
    no_color: bool,

    /// API secret key (overrides config)
    #[arg(long, global = true, env = "BPX_API_SECRET", hide_env_values = true)]
    api_secret: Option<String>,

    /// API base URL (overrides config)
    #[arg(long, global = true, env = "BPX_BASE_URL")]
    base_url: Option<String>,

    /// Number of retries on transient errors (timeout, 429, 502, 503)
    #[arg(long, global = true, default_value = "0")]
    retry: u32,

    /// Initial delay between retries (e.g. "1s", "500ms")
    #[arg(long, global = true, default_value = "1s")]
    retry_delay: String,

    /// Maximum delay between retries (e.g. "30s")
    #[arg(long, global = true, default_value = "30s")]
    retry_max_delay: String,

    /// Validate orders without executing (order commands only)
    #[arg(long, global = true)]
    dry_run: bool,
}

#[derive(Subcommand)]
enum Command {
    /// Interactive configuration wizard
    Setup {
        /// API secret (non-interactive mode for agents/scripts)
        #[arg(long)]
        secret: Option<String>,
        /// Profile name (non-interactive mode)
        #[arg(long, default_value = "default")]
        profile_name: Option<String>,
        /// API base URL (non-interactive mode)
        #[arg(long)]
        setup_base_url: Option<String>,
        /// WebSocket URL (non-interactive mode)
        #[arg(long)]
        ws_url: Option<String>,
    },

    /// Check API connectivity and authentication
    Status,

    /// Market data commands
    #[command(subcommand, alias = "m")]
    Markets(MarketsCommand),

    /// Order management
    #[command(subcommand, alias = "o")]
    Order(Box<OrderCommand>),

    /// Account information
    #[command(subcommand, alias = "a")]
    Account(AccountCommand),

    /// Capital management (deposits, withdrawals)
    #[command(subcommand)]
    Capital(CapitalCommand),

    /// Futures positions
    #[command(subcommand, alias = "pos")]
    Positions(PositionsCommand),

    /// Trade fill history
    #[command(subcommand)]
    Fills(FillsCommand),

    /// Borrow/lend positions
    #[command(subcommand)]
    Borrow(BorrowCommand),

    /// Request for quote
    #[command(subcommand)]
    Rfq(RfqCommand),

    /// Stream account data (positions, orders, balances, funding)
    #[command(subcommand, alias = "w")]
    Watch(WatchCommand),

    /// Aggregate account summary (health, positions, collateral)
    #[command(alias = "pf")]
    Portfolio(PortfolioArgs),

    /// What-if scenario analysis (trade impact, price moves)
    #[command(subcommand)]
    Scenario(ScenarioCommand),

    /// Basis trade automation (spot + lend + short perp carry)
    #[command(subcommand, alias = "b")]
    Basis(BasisCommand),

    /// Strategy templates (save and replay trade setups)
    #[command(subcommand)]
    Strategy(StrategyCommand),

    /// Portfolio risk analysis and stress tests
    #[command(subcommand)]
    Risk(RiskCommand),

    /// Collateral optimization suggestions
    Optimize {
        /// Apply suggested optimizations (with confirmation)
        #[arg(long)]
        apply: bool,
    },

    /// Generate shell completions
    Completions {
        /// Shell to generate completions for
        shell: clap_complete::Shell,
    },

    /// Launch the live trading dashboard
    Dashboard {
        /// Market symbol to display
        #[arg(long, default_value = "SOL_USDC")]
        symbol: String,
    },

    /// Manage config profiles (for subaccounts)
    #[command(subcommand, alias = "profiles")]
    Profile(ProfileCommand),
}

#[derive(Subcommand)]
enum ProfileCommand {
    /// List all configured profiles
    List,
    /// Show the current active profile
    Current,
}

fn clap_styles() -> clap::builder::Styles {
    clap::builder::Styles::styled()
        .header(
            clap::builder::styling::AnsiColor::BrightCyan
                .on_default()
                .bold(),
        )
        .usage(
            clap::builder::styling::AnsiColor::BrightCyan
                .on_default()
                .bold(),
        )
        .literal(clap::builder::styling::AnsiColor::White.on_default().bold())
        .placeholder(clap::builder::styling::AnsiColor::BrightBlack.on_default())
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .with_target(false)
        .without_time()
        .init();

    let cli = Cli::parse();

    // Store no-color flag globally. owo-colors checks NO_COLOR env var,
    // and our Styles::new() checks this flag too.
    NO_COLOR
        .set(cli.no_color || std::env::var_os("NO_COLOR").is_some())
        .ok();

    let format = cli.output;

    let retry_config = match (
        retry::parse_duration(&cli.retry_delay),
        retry::parse_duration(&cli.retry_max_delay),
    ) {
        (Ok(delay), Ok(max_delay)) => retry::RetryConfig {
            max_retries: cli.retry,
            initial_delay: delay,
            max_delay,
        },
        (Err(e), _) | (_, Err(e)) => {
            output_error("Invalid retry config", &e.to_string(), format);
            std::process::exit(exit_code::INPUT);
        }
    };

    let config = match config::load_config(&cli.profile) {
        Ok(c) => c,
        Err(e) => {
            output_error("Config error", &e.to_string(), format);
            std::process::exit(exit_code::GENERAL);
        }
    };

    let dry_run = cli.dry_run;

    let result = match cli.command {
        None => {
            if std::io::stdout().is_terminal() {
                // First-run: if no config file exists, show welcome banner
                if !config::config_exists() {
                    print_welcome();
                    Ok(())
                } else {
                    commands::dashboard::run(
                        None,
                        &config,
                        cli.api_secret.as_deref(),
                        cli.base_url.as_deref(),
                    )
                    .await
                }
            } else {
                // Re-parse with --help to print usage
                let _ = Cli::try_parse_from(["bpx", "--help"]);
                Ok(())
            }
        }

        Some(Command::Setup {
            secret,
            profile_name,
            setup_base_url,
            ws_url,
        }) => {
            commands::setup::run(
                secret.as_deref(),
                profile_name.as_deref(),
                setup_base_url.as_deref(),
                ws_url.as_deref(),
            )
            .await
        }

        Some(Command::Status) => {
            commands::status::run(&config, cli.base_url.as_deref(), format).await
        }

        Some(Command::Markets(cmd)) => {
            commands::markets::run(&cmd, &config, cli.base_url.as_deref(), format).await
        }

        Some(Command::Order(cmd)) => {
            commands::order::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                &retry_config,
                dry_run,
            )
            .await
        }

        Some(Command::Account(cmd)) => {
            commands::account::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Capital(cmd)) => {
            commands::capital::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Positions(cmd)) => {
            commands::positions::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Fills(cmd)) => {
            commands::fills::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Borrow(cmd)) => {
            commands::borrow::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Rfq(cmd)) => {
            commands::rfq::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Watch(cmd)) => {
            commands::watch::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                &retry_config,
            )
            .await
        }

        Some(Command::Portfolio(args)) => {
            commands::portfolio::run(
                &args,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                &retry_config,
            )
            .await
        }

        Some(Command::Scenario(cmd)) => {
            commands::scenario::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                &retry_config,
            )
            .await
        }

        Some(Command::Basis(cmd)) => {
            commands::basis::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                &retry_config,
            )
            .await
        }

        Some(Command::Strategy(cmd)) => {
            commands::strategy::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Risk(cmd)) => {
            commands::risk::run(
                &cmd,
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
            )
            .await
        }

        Some(Command::Optimize { apply }) => {
            optimizer::run(
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
                format,
                apply,
            )
            .await
        }

        Some(Command::Completions { shell }) => {
            let mut cmd = Cli::command();
            clap_complete::generate(shell, &mut cmd, "bpx", &mut std::io::stdout());
            Ok(())
        }

        Some(Command::Dashboard { symbol }) => {
            commands::dashboard::run(
                Some(&symbol),
                &config,
                cli.api_secret.as_deref(),
                cli.base_url.as_deref(),
            )
            .await
        }

        Some(Command::Profile(cmd)) => run_profile(&cli.profile, cmd, format),
    };

    if let Err(e) = result {
        let msg = e.to_string();
        let code = classify_exit_code(&msg);
        output_error("error", &msg, format);
        std::process::exit(code);
    }
}

fn run_profile(
    active_profile: &str,
    cmd: ProfileCommand,
    format: OutputFormat,
) -> anyhow::Result<()> {
    match cmd {
        ProfileCommand::List => {
            let profiles = config::list_profiles()?;
            match format {
                OutputFormat::Json => {
                    output::print_json(&serde_json::json!({
                        "active": active_profile,
                        "profiles": profiles,
                    }));
                }
                OutputFormat::Table => {
                    if profiles.is_empty() {
                        println!(
                            "No profiles configured. Run {} to get started.",
                            "bpx setup".bold()
                        );
                    } else {
                        let s = style::Styles::new();
                        for name in &profiles {
                            if name == active_profile {
                                println!(
                                    "  {} {} {}",
                                    "*".style(s.green),
                                    name.style(s.bold),
                                    "(active)".style(s.dim)
                                );
                            } else {
                                println!("    {name}");
                            }
                        }
                        println!();
                        println!(
                            "  Switch profiles with: {}",
                            "bpx --profile <name> <command>".style(s.dim)
                        );
                    }
                }
            }
        }
        ProfileCommand::Current => match format {
            OutputFormat::Json => {
                output::print_json(&serde_json::json!({"profile": active_profile}));
            }
            OutputFormat::Table => {
                println!("{active_profile}");
            }
        },
    }
    Ok(())
}

fn print_welcome() {
    let s = style::Styles::new();
    println!();
    println!("{}", "   _".style(s.cyan));
    println!("{}", "  | |__ _ __ __ __".style(s.cyan));
    println!("{}", r"  | '_ \ '_ \\ \ / /".style(s.cyan));
    println!("{}", "  | |_) | |_) |> <".style(s.cyan));
    println!("{}", r"  |_.__/| .__//_/\_\".style(s.cyan));
    println!("{}", "       |_|".style(s.cyan));
    println!();
    println!(
        "  {}  {}",
        "Backpack Exchange CLI".style(s.bold),
        format!("v{}", env!("CARGO_PKG_VERSION")).style(s.dim)
    );
    println!();
    println!("  {}", "Get started:".style(s.dim));
    println!();
    println!(
        "    {}          Connect your API key",
        "bpx setup".style(s.bold)
    );
    println!(
        "    {}   Browse markets (no key needed)",
        "bpx markets list".style(s.bold)
    );
    println!(
        "    {}         See all commands",
        "bpx --help".style(s.bold)
    );
    println!();
}

/// Output an error in the appropriate format (text for table, JSON for json mode).
fn output_error(kind: &str, message: &str, format: OutputFormat) {
    let code = error::classify_error(message);
    match format {
        OutputFormat::Json => {
            println!(
                "{}",
                serde_json::json!({
                    "error": message,
                    "kind": kind,
                    "code": code.as_str(),
                    "category": code.category(),
                    "exit_code": code.exit_code(),
                    "suggestion": code.suggestion(),
                    "timestamp": chrono::Utc::now().to_rfc3339(),
                })
            );
        }
        OutputFormat::Table => {
            eprintln!("{kind}: {message}");
        }
    }
}

/// Classify the error message into an exit code using structured error codes.
fn classify_exit_code(msg: &str) -> i32 {
    error::classify_error(msg).exit_code()
}
