//! `fxt agent` -- trading agent control.

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

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

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

#[derive(Subcommand, Debug)]
enum AgentAction {
    /// Start the trading agent (enable all strategies)
    Start {
        /// Strategy ID to enable (omit to enable all)
        #[arg(long)]
        strategy: Option<String>,
    },
    /// Stop the trading agent (disable all strategies)
    Stop {
        /// Strategy ID to disable (omit to disable all)
        #[arg(long)]
        strategy: Option<String>,
    },
    /// Show agent status (strategy, positions, signals)
    Status,
    /// Show strategy configuration
    Config,
    /// Show agent performance metrics
    Performance,
}

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

#[derive(Serialize)]
struct AgentStatusResult {
    state: String,
    active_strategies: u32,
    selected_assets: u32,
    portfolio_utilization: f64,
    current_universe_id: String,
    performance: Option<PerformanceInfo>,
}

#[derive(Serialize)]
struct PerformanceInfo {
    total_pnl: f64,
    sharpe_ratio: f64,
    max_drawdown: f64,
    win_rate: f64,
    total_trades: u32,
    avg_trade_pnl: f64,
}

impl HumanReadable for AgentStatusResult {
    fn print_human(&self) {
        let state_colored = match self.state.as_str() {
            "ACTIVE" => self.state.green().bold(),
            "INITIALIZING" | "PAUSED" => self.state.yellow().bold(),
            _ => self.state.red().bold(),
        };
        println!("{}", "Trading Agent".bold().underline());
        println!("  State:              {state_colored}");
        println!("  Active Strategies:  {}", self.active_strategies);
        println!("  Selected Assets:    {}", self.selected_assets);
        println!(
            "  Portfolio Util:     {:.1}%",
            self.portfolio_utilization * 100.0
        );
        if !self.current_universe_id.is_empty() {
            println!("  Universe:           {}", self.current_universe_id.dimmed());
        }

        if let Some(ref perf) = self.performance {
            println!();
            println!("{}", "Performance".bold().underline());
            let pnl_str = if perf.total_pnl >= 0.0 {
                format!("${:.2}", perf.total_pnl).green().to_string()
            } else {
                format!("${:.2}", perf.total_pnl).red().to_string()
            };
            println!("  Total P&L:     {pnl_str}");
            println!("  Sharpe Ratio:  {:.3}", perf.sharpe_ratio);
            println!("  Max Drawdown:  {:.2}%", perf.max_drawdown * 100.0);
            println!("  Win Rate:      {:.1}%", perf.win_rate * 100.0);
            println!("  Total Trades:  {}", perf.total_trades);
            println!("  Avg Trade P&L: ${:.2}", perf.avg_trade_pnl);
        }
    }
}

#[derive(Serialize)]
struct StrategyUpdateResult {
    success: bool,
    message: String,
    strategy_id: String,
    new_status: String,
}

impl HumanReadable for StrategyUpdateResult {
    fn print_human(&self) {
        if self.success {
            println!(
                "{} Strategy {} -> {}",
                "OK".green().bold(),
                self.strategy_id.cyan(),
                self.new_status.bold()
            );
        } else {
            println!(
                "{} {}: {}",
                "FAIL".red().bold(),
                self.strategy_id,
                self.message
            );
        }
    }
}

#[derive(Serialize)]
struct StrategyInfo {
    strategy_id: String,
    name: String,
    strategy_type: String,
    status: String,
    target_symbols: Vec<String>,
    max_capital_pct: f64,
    total_pnl: f64,
    sharpe_ratio: f64,
    win_rate: f64,
    total_trades: u32,
}

#[derive(Serialize)]
struct StrategiesResult {
    strategies: Vec<StrategyInfo>,
}

impl HumanReadable for StrategiesResult {
    fn print_human(&self) {
        if self.strategies.is_empty() {
            println!("{}", "No strategies registered.".dimmed());
            return;
        }
        println!("{}", "Strategies".bold().underline());
        for s in &self.strategies {
            let status_colored = match s.status.as_str() {
                "ENABLED" => s.status.green(),
                "PAUSED" => s.status.yellow(),
                _ => s.status.red(),
            };
            println!(
                "  {} [{}] {:<20} {}",
                s.strategy_id.dimmed(),
                status_colored,
                s.name.bold(),
                s.strategy_type.dimmed()
            );
            if !s.target_symbols.is_empty() {
                println!("    Symbols: {}", s.target_symbols.join(", "));
            }
            println!(
                "    Cap: {:.0}%  P&L: ${:.2}  Sharpe: {:.3}  WR: {:.1}%  Trades: {}",
                s.max_capital_pct * 100.0,
                s.total_pnl,
                s.sharpe_ratio,
                s.win_rate * 100.0,
                s.total_trades,
            );
        }
    }
}

#[derive(Serialize)]
struct AgentPerformanceResult {
    total_pnl: f64,
    sharpe_ratio: f64,
    max_drawdown: f64,
    win_rate: f64,
    total_trades: u32,
    avg_trade_pnl: f64,
    portfolio_turnover: f64,
    strategy_breakdown: Vec<StrategyPerfEntry>,
}

#[derive(Serialize)]
struct StrategyPerfEntry {
    strategy_id: String,
    total_pnl: f64,
    sharpe_ratio: f64,
    win_rate: f64,
    total_trades: u32,
}

impl HumanReadable for AgentPerformanceResult {
    fn print_human(&self) {
        println!("{}", "Agent Performance".bold().underline());
        let pnl_str = if self.total_pnl >= 0.0 {
            format!("${:.2}", self.total_pnl).green().to_string()
        } else {
            format!("${:.2}", self.total_pnl).red().to_string()
        };
        println!("  Total P&L:       {pnl_str}");
        println!("  Sharpe Ratio:    {:.3}", self.sharpe_ratio);
        println!("  Max Drawdown:    {:.2}%", self.max_drawdown * 100.0);
        println!("  Win Rate:        {:.1}%", self.win_rate * 100.0);
        println!("  Total Trades:    {}", self.total_trades);
        println!("  Avg Trade P&L:   ${:.2}", self.avg_trade_pnl);
        println!(
            "  Turnover (ann.): {:.1}%",
            self.portfolio_turnover * 100.0
        );

        if !self.strategy_breakdown.is_empty() {
            println!();
            println!("  {}", "Per-Strategy Breakdown".bold());
            println!(
                "  {:<20} {:>12} {:>10} {:>10} {:>8}",
                "Strategy", "P&L", "Sharpe", "Win Rate", "Trades"
            );
            println!("  {}", "-".repeat(64));
            for s in &self.strategy_breakdown {
                println!(
                    "  {:<20} {:>12.2} {:>10.3} {:>9.1}% {:>8}",
                    s.strategy_id,
                    s.total_pnl,
                    s.sharpe_ratio,
                    s.win_rate * 100.0,
                    s.total_trades,
                );
            }
        }
    }
}

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

fn agent_state_name(v: i32) -> &'static str {
    match trading_agent::AgentState::try_from(v) {
        Ok(trading_agent::AgentState::Initializing) => "INITIALIZING",
        Ok(trading_agent::AgentState::Active) => "ACTIVE",
        Ok(trading_agent::AgentState::Paused) => "PAUSED",
        Ok(trading_agent::AgentState::Error) => "ERROR",
        Ok(trading_agent::AgentState::Shutdown) => "SHUTDOWN",
        _ => "UNKNOWN",
    }
}

fn strategy_status_name(v: i32) -> &'static str {
    match trading_agent::StrategyStatus::try_from(v) {
        Ok(trading_agent::StrategyStatus::Enabled) => "ENABLED",
        Ok(trading_agent::StrategyStatus::Disabled) => "DISABLED",
        Ok(trading_agent::StrategyStatus::Paused) => "PAUSED",
        Ok(trading_agent::StrategyStatus::Error) => "ERROR",
        _ => "UNKNOWN",
    }
}

fn strategy_type_name(v: i32) -> &'static str {
    match trading_agent::StrategyType::try_from(v) {
        Ok(trading_agent::StrategyType::MlEnsemble) => "ML_ENSEMBLE",
        Ok(trading_agent::StrategyType::MeanReversion) => "MEAN_REVERSION",
        Ok(trading_agent::StrategyType::Momentum) => "MOMENTUM",
        Ok(trading_agent::StrategyType::Arbitrage) => "ARBITRAGE",
        Ok(trading_agent::StrategyType::MarketMaking) => "MARKET_MAKING",
        _ => "UNKNOWN",
    }
}

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

impl AgentCommand {
    pub async fn execute(&self, client: &FoxhuntClient, format: OutputFormat) -> Result<()> {
        match &self.action {
            AgentAction::Start { strategy } => {
                self.update_strategy(client, format, strategy, true)
                    .await?;
            }

            AgentAction::Stop { strategy } => {
                self.update_strategy(client, format, strategy, false)
                    .await?;
            }

            AgentAction::Status => {
                let resp = client
                    .trading_agent()
                    .get_agent_status(trading_agent::GetAgentStatusRequest {
                        include_performance: true,
                        include_positions: false,
                    })
                    .await?
                    .into_inner();

                let status = resp.status.unwrap_or_default();
                let performance = resp.performance.map(|p| PerformanceInfo {
                    total_pnl: p.total_pnl,
                    sharpe_ratio: p.sharpe_ratio,
                    max_drawdown: p.max_drawdown,
                    win_rate: p.win_rate,
                    total_trades: p.total_trades,
                    avg_trade_pnl: p.avg_trade_pnl,
                });

                let result = AgentStatusResult {
                    state: agent_state_name(status.state).to_owned(),
                    active_strategies: status.active_strategies,
                    selected_assets: status.selected_assets,
                    portfolio_utilization: status.portfolio_utilization,
                    current_universe_id: status.current_universe_id,
                    performance,
                };
                output::render(&result, format)?;
            }

            AgentAction::Config => {
                let resp = client
                    .trading_agent()
                    .list_strategies(trading_agent::ListStrategiesRequest {
                        status_filter: None,
                    })
                    .await?
                    .into_inner();

                let strategies = resp
                    .strategies
                    .into_iter()
                    .map(|s| {
                        let config = s.config.unwrap_or_default();
                        let perf = s.performance.unwrap_or_default();
                        StrategyInfo {
                            strategy_id: s.strategy_id,
                            name: s.strategy_name,
                            strategy_type: strategy_type_name(s.strategy_type).to_owned(),
                            status: strategy_status_name(s.status).to_owned(),
                            target_symbols: config.target_symbols,
                            max_capital_pct: config.max_capital_pct,
                            total_pnl: perf.total_pnl,
                            sharpe_ratio: perf.sharpe_ratio,
                            win_rate: perf.win_rate,
                            total_trades: perf.total_trades,
                        }
                    })
                    .collect();

                output::render(&StrategiesResult { strategies }, format)?;
            }

            AgentAction::Performance => {
                let resp = client
                    .trading_agent()
                    .get_agent_performance(trading_agent::GetAgentPerformanceRequest {
                        start_time: None,
                        end_time: None,
                        include_strategy_breakdown: true,
                    })
                    .await?
                    .into_inner();

                let metrics = resp.metrics.unwrap_or_default();
                let strategy_breakdown = resp
                    .strategy_performance
                    .into_iter()
                    .map(|s| StrategyPerfEntry {
                        strategy_id: s.strategy_id,
                        total_pnl: s.total_pnl,
                        sharpe_ratio: s.sharpe_ratio,
                        win_rate: s.win_rate,
                        total_trades: s.total_trades,
                    })
                    .collect();

                let result = AgentPerformanceResult {
                    total_pnl: metrics.total_pnl,
                    sharpe_ratio: metrics.sharpe_ratio,
                    max_drawdown: metrics.max_drawdown,
                    win_rate: metrics.win_rate,
                    total_trades: metrics.total_trades,
                    avg_trade_pnl: metrics.avg_trade_pnl,
                    portfolio_turnover: metrics.portfolio_turnover,
                    strategy_breakdown,
                };
                output::render(&result, format)?;
            }
        }

        Ok(())
    }

    /// Enable or disable a strategy (or all strategies if no ID given).
    async fn update_strategy(
        &self,
        client: &FoxhuntClient,
        format: OutputFormat,
        strategy_id: &Option<String>,
        enable: bool,
    ) -> Result<()> {
        let new_status = if enable {
            trading_agent::StrategyStatus::Enabled
        } else {
            trading_agent::StrategyStatus::Disabled
        };

        // If a specific strategy ID is provided, update that one.
        // Otherwise, list all strategies and update each.
        let ids: Vec<String> = if let Some(id) = strategy_id {
            vec![id.clone()]
        } else {
            let resp = client
                .trading_agent()
                .list_strategies(trading_agent::ListStrategiesRequest {
                    status_filter: None,
                })
                .await?
                .into_inner();
            resp.strategies
                .into_iter()
                .map(|s| s.strategy_id)
                .collect()
        };

        if ids.is_empty() {
            println!("{}", "No strategies registered.".dimmed());
            return Ok(());
        }

        for id in &ids {
            let resp = client
                .trading_agent()
                .update_strategy_status(trading_agent::UpdateStrategyStatusRequest {
                    strategy_id: id.clone(),
                    new_status: new_status.into(),
                    reason: None,
                })
                .await?
                .into_inner();

            let updated = resp.updated_strategy.unwrap_or_default();
            let result = StrategyUpdateResult {
                success: resp.success,
                message: resp.message,
                strategy_id: id.clone(),
                new_status: strategy_status_name(updated.status).to_owned(),
            };
            output::render(&result, format)?;
        }

        Ok(())
    }
}
