//! `fxt broker` -- broker connectivity.

use anyhow::Result;
use clap::{Parser, Subcommand};
use colored::Colorize;
use serde::Serialize;
use tokio_stream::StreamExt;

use crate::grpc::FoxhuntClient;
use crate::output::{self, HumanReadable, OutputFormat};
use crate::proto::broker_gateway;

#[derive(Parser, Debug)]
pub struct BrokerCommand {
    #[command(subcommand)]
    action: BrokerAction,
}

#[derive(Subcommand, Debug)]
enum BrokerAction {
    /// Show broker connection status
    Status,
    /// Connect to broker gateway
    Connect {
        /// Broker host override
        #[arg(long)]
        host: Option<String>,
        /// Broker port override
        #[arg(long)]
        port: Option<u16>,
    },
    /// List recent executions
    Executions {
        /// Follow execution stream in real-time
        #[arg(long, short)]
        follow: bool,
        /// Maximum number of executions to show (non-follow mode)
        #[arg(long, default_value = "20")]
        limit: u32,
    },
}

// ── Result types ────────────────────────────────────────────────────

#[derive(Serialize)]
struct SessionStatusResult {
    session_id: String,
    state: String,
    sender_seq_num: i64,
    target_seq_num: i64,
    heartbeat_rtt_ms: f64,
    details: std::collections::HashMap<String, String>,
}

impl HumanReadable for SessionStatusResult {
    fn print_human(&self) {
        let state_colored = match self.state.as_str() {
            "ACTIVE" => self.state.green().bold(),
            "CONNECTED" | "LOGGING_IN" => self.state.yellow().bold(),
            _ => self.state.red().bold(),
        };
        println!("{}", "Broker Session".bold().underline());
        println!("  Session ID:  {}", self.session_id.cyan());
        println!("  State:       {state_colored}");
        println!("  Sender Seq:  {}", self.sender_seq_num);
        println!("  Target Seq:  {}", self.target_seq_num);
        if self.heartbeat_rtt_ms > 0.0 {
            println!("  Heartbeat:   {:.1} ms RTT", self.heartbeat_rtt_ms);
        }
        if !self.details.is_empty() {
            println!("  Details:");
            for (k, v) in &self.details {
                println!("    {k}: {v}");
            }
        }
    }
}

#[derive(Serialize)]
struct HealthResult {
    healthy: bool,
    message: String,
    details: std::collections::HashMap<String, String>,
}

impl HumanReadable for HealthResult {
    fn print_human(&self) {
        if self.healthy {
            println!("{} {}", "HEALTHY".green().bold(), self.message);
        } else {
            println!("{} {}", "UNHEALTHY".red().bold(), self.message);
        }
    }
}

// ── Helpers ─────────────────────────────────────────────────────────

fn session_state_name(v: i32) -> &'static str {
    match broker_gateway::SessionState::try_from(v) {
        Ok(broker_gateway::SessionState::Disconnected) => "DISCONNECTED",
        Ok(broker_gateway::SessionState::Connected) => "CONNECTED",
        Ok(broker_gateway::SessionState::LoggingIn) => "LOGGING_IN",
        Ok(broker_gateway::SessionState::Active) => "ACTIVE",
        Ok(broker_gateway::SessionState::LoggingOut) => "LOGGING_OUT",
        _ => "UNKNOWN",
    }
}

fn exec_side_name(v: i32) -> &'static str {
    match broker_gateway::OrderSide::try_from(v) {
        Ok(broker_gateway::OrderSide::Buy) => "BUY",
        Ok(broker_gateway::OrderSide::Sell) => "SELL",
        _ => "?",
    }
}

fn exec_type_name(v: i32) -> &'static str {
    match broker_gateway::ExecutionType::try_from(v) {
        Ok(broker_gateway::ExecutionType::New) => "NEW",
        Ok(broker_gateway::ExecutionType::Trade) => "TRADE",
        Ok(broker_gateway::ExecutionType::Canceled) => "CANCELED",
        Ok(broker_gateway::ExecutionType::Rejected) => "REJECTED",
        _ => "?",
    }
}

// ── Execute ─────────────────────────────────────────────────────────

impl BrokerCommand {
    pub async fn execute(&self, client: &FoxhuntClient, format: OutputFormat) -> Result<()> {
        match &self.action {
            BrokerAction::Status => {
                let resp = client
                    .broker_gateway()
                    .get_session_status(broker_gateway::GetSessionStatusRequest {
                        session_id: None,
                    })
                    .await?
                    .into_inner();

                let result = SessionStatusResult {
                    session_id: resp.session_id,
                    state: session_state_name(resp.state).to_owned(),
                    sender_seq_num: resp.sender_seq_num,
                    target_seq_num: resp.target_seq_num,
                    heartbeat_rtt_ms: resp.heartbeat_rtt_ms,
                    details: resp.details,
                };
                output::render(&result, format)?;
            }

            BrokerAction::Connect { .. } => {
                // First do a health check to verify connectivity.
                let resp = client
                    .broker_gateway()
                    .health_check(broker_gateway::HealthCheckRequest {})
                    .await?
                    .into_inner();

                let result = HealthResult {
                    healthy: resp.healthy,
                    message: if resp.healthy {
                        "Broker connection is managed by the broker-gateway service. \
                         Gateway is healthy."
                            .to_owned()
                    } else {
                        format!(
                            "Broker gateway reports unhealthy: {}",
                            resp.message
                        )
                    },
                    details: resp.details,
                };
                output::render(&result, format)?;
            }

            BrokerAction::Executions { follow, limit } => {
                if *follow {
                    // Stream executions in real-time.
                    let mut stream = client
                        .broker_gateway()
                        .stream_executions(broker_gateway::StreamExecutionsRequest {
                            account_id: None,
                            symbol: None,
                        })
                        .await?
                        .into_inner();

                    println!(
                        "{} Streaming executions (Ctrl-C to stop)...",
                        "LIVE".green().bold()
                    );
                    println!(
                        "  {:<14} {:<10} {:<6} {:>10} {:>12} {:<8}",
                        "ExecID", "Symbol", "Side", "Qty", "Price", "Type"
                    );
                    println!("  {}", "-".repeat(64));

                    while let Some(msg) = stream.next().await {
                        let exec = msg?;
                        println!(
                            "  {:<14} {:<10} {:<6} {:>10.2} {:>12.4} {:<8}",
                            truncate_id(&exec.execution_id, 14),
                            exec.symbol,
                            exec_side_name(exec.side),
                            exec.last_qty,
                            exec.last_price,
                            exec_type_name(exec.exec_type),
                        );
                    }
                } else {
                    // Non-streaming: inform user about the limitation.
                    println!(
                        "{} The broker gateway streams executions in real-time.",
                        "INFO".blue().bold()
                    );
                    println!(
                        "  Use {} to stream live executions.",
                        "fxt broker executions --follow".cyan()
                    );
                    println!(
                        "  Use {} to view execution history.",
                        "fxt trade orders <ORDER_ID>".cyan()
                    );
                    let _ = limit; // acknowledged but not used without a history RPC
                }
            }
        }

        Ok(())
    }
}

/// Truncate a string to at most `max` characters for display.
fn truncate_id(s: &str, max: usize) -> String {
    if s.len() <= max {
        s.to_owned()
    } else {
        let visible = max.saturating_sub(2);
        let prefix: String = s.chars().take(visible).collect();
        format!("{prefix}..")
    }
}
