use clap::Parser;
use indodax_cli::errors::IndodaxError;
use indodax_cli::mcp;
use indodax_cli::output::{CommandOutput, OutputFormat};
use indodax_cli::{
    client::IndodaxClient,
    commands::utility::{execute as utility_execute, UtilityCommand},
    config::IndodaxConfig,
    dispatch, map_anyhow_error, Cli, Command,
};
use std::io::BufRead;
use std::process;

#[tokio::main]
async fn main() {
    // Custom panic hook for cleaner error output
    std::panic::set_hook(Box::new(|info| {
        let message = if let Some(s) = info.payload().downcast_ref::<&str>() {
            s.to_string()
        } else if let Some(s) = info.payload().downcast_ref::<String>() {
            s.clone()
        } else {
            "unexpected internal error".to_string()
        };
        let location = info
            .location()
            .map(|l| format!(" at {}:{}", l.file(), l.line()))
            .unwrap_or_default();
        eprintln!("Internal error: {}{}", message, location);
        std::process::exit(1);
    }));

    // Initialize tracing (logs to stderr, never stdout)
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "indodax_cli=info".into()),
        )
        .with_target(true)
        .init();

    let cli = Cli::parse();

    // Capture output format before cli is consumed by dispatch
    let output_format = cli.output;

    let mut config = match IndodaxConfig::load() {
        Ok(c) => c,
        Err(e) => {
            report_error(&IndodaxError::Other(e.to_string()), output_format);
            process::exit(1);
        }
    };

    // Handle --api-secret-stdin: read secret from stdin (more secure than CLI args)
    let api_secret = if cli.api_secret_stdin {
        let mut secret = String::new();
        if let Err(e) = std::io::stdin().lock().read_line(&mut secret) {
            report_error(
                &IndodaxError::Other(format!("Failed to read API secret from stdin: {}", e)),
                output_format,
            );
            process::exit(1);
        }
        let trimmed = secret.trim().to_string();
        if trimmed.is_empty() {
            eprintln!("Warning: --api-secret-stdin used but no input received (empty line).");
            cli.api_secret.clone()
        } else {
            Some(trimmed)
        }
    } else {
        cli.api_secret.clone()
    };

    let creds = match config.resolve_credentials(cli.api_key.clone(), api_secret) {
        Ok(c) => c,
        Err(e) => {
            report_error(&IndodaxError::Other(e.to_string()), output_format);
            process::exit(1);
        }
    };

    let signer = creds
        .as_ref()
        .map(|c| indodax_cli::auth::Signer::new(c.api_key.as_str(), c.api_secret.as_str()));

    let client = match IndodaxClient::new(signer) {
        Ok(c) => c.with_ws_token(config.ws_token.as_ref().map(|t| t.as_str().to_string())),
        Err(e) => {
            report_error(&e, output_format);
            process::exit(1);
        }
    };

    // Handle MCP server separately — it runs indefinitely on stdio
    if let Command::Mcp {
        groups,
        allow_dangerous,
    } = &cli.command
    {
        match mcp::run(groups, *allow_dangerous, client, config).await {
            Ok(()) => process::exit(0),
            Err(e) => {
                report_error(&e, output_format);
                process::exit(1);
            }
        }
    }

    let result: Result<CommandOutput, IndodaxError> = match &cli.command {
        Command::Setup => utility_execute(&client, &creds, &UtilityCommand::Setup)
            .await
            .map_err(map_anyhow_error),
        Command::Shell => utility_execute(&client, &creds, &UtilityCommand::Shell)
            .await
            .map_err(map_anyhow_error),
        _ => dispatch(cli, &client, &mut config).await,
    };

    match result {
        Ok(output) => {
            if !output.suppress_final_output || output_format == OutputFormat::Table {
                println!("{}", output.render());
            }
        }
        Err(e) => {
            report_error(&e, output_format);
            process::exit(1);
        }
    }
}

/// Report an error in the appropriate format.
///
/// In JSON mode, prints a parseable error envelope to stdout.
/// In table mode, prints human-readable error to stderr.
fn report_error(err: &IndodaxError, format: OutputFormat) {
    if format == OutputFormat::Json {
        let envelope = serde_json::json!({
            "success": false,
            "data": null,
            "error": true,
            "message": err.to_string(),
            "error_type": err.category(),
            "retryable": err.is_retryable(),
        });
        match serde_json::to_string_pretty(&envelope) {
            Ok(s) => println!("{}", s),
            Err(_) => eprintln!("Error: {}", err),
        }
    } else {
        eprintln!("Error: {}", err);
    }
}
