//! # Broker Connector Service
//!
//! Production broker connectivity service that delegates to configured broker clients.
//! Use feature flag `interactive-brokers` to enable IB. ICMarkets cTrader is always available.

#![warn(missing_docs)]

// Public modules
pub mod config;
pub mod error;
pub mod fix;
pub mod icmarkets;
pub mod interactive_brokers;
pub mod monitoring;
pub mod routing;
pub mod security;

use self::config::BrokerConnectorConfig;
use tracing::{error, info, warn};

#[cfg(feature = "interactive-brokers")]
use self::interactive_brokers::InteractiveBrokersClient;

use self::icmarkets::ICMarketsClient;

use crate::trading::data_interface::BrokerInterface;
use crate::trading_operations::TradingOrder;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

/// Production broker connector that delegates to real broker clients.
///
/// ICMarkets cTrader connectivity is always available.
/// Use the `interactive-brokers` feature flag to enable IB TWS connectivity.
#[derive(Debug)]
pub struct BrokerConnector {
    /// Broker connection configuration
    config: BrokerConnectorConfig,

    /// Interactive Brokers client (requires `interactive-brokers` feature)
    #[cfg(feature = "interactive-brokers")]
    ib_client: Option<InteractiveBrokersClient>,

    /// ICMarkets client (always available)
    icm_client: Option<ICMarketsClient>,
}

impl BrokerConnector {
    /// Create a new broker connector from configuration.
    ///
    /// Instantiates broker clients based on enabled features and configuration.
    /// Clients are created in a disconnected state; call `initialize()` to connect.
    pub fn new(config: BrokerConnectorConfig) -> Self {
        #[cfg(feature = "interactive-brokers")]
        let ib_client = config.brokers.interactive_brokers.enabled.then(|| {
            info!("Interactive Brokers client created (disconnected)");
            InteractiveBrokersClient::new(config.brokers.interactive_brokers.clone())
        });

        let icm_client = config.brokers.icmarkets.enabled.then(|| {
            info!("ICMarkets client created (disconnected)");
            ICMarketsClient::new(config.brokers.icmarkets.clone())
        });

        Self {
            config,
            #[cfg(feature = "interactive-brokers")]
            ib_client,
            icm_client,
        }
    }

    /// Get a reference to the broker configuration
    pub fn config(&self) -> &BrokerConnectorConfig {
        &self.config
    }

    /// Initialize broker connections.
    ///
    /// Connects to all enabled and configured brokers. Failures are logged
    /// but do not prevent other brokers from connecting, unless
    /// `fail_on_broker_error` is set in the configuration.
    pub async fn initialize(&mut self) -> Result<()> {
        info!("Initializing broker connections...");
        let mut errors: Vec<String> = Vec::new();

        #[cfg(feature = "interactive-brokers")]
        if let Some(ref mut client) = self.ib_client {
            match client.connect().await {
                Ok(()) => info!("Interactive Brokers connected successfully"),
                Err(e) => {
                    let msg = format!("Interactive Brokers connection failed: {e}");
                    error!("{}", msg);
                    errors.push(msg);
                }
            }
        }

        if let Some(ref mut client) = self.icm_client {
            match client.connect().await {
                Ok(()) => info!("ICMarkets connected successfully"),
                Err(e) => {
                    let msg = format!("ICMarkets connection failed: {e}");
                    error!("{}", msg);
                    errors.push(msg);
                }
            }
        }

        if self.config.fail_on_broker_error && !errors.is_empty() {
            return Err(format!(
                "Broker initialization failed: {}",
                errors.join("; ")
            )
            .into());
        }

        info!("Broker initialization complete");
        Ok(())
    }

    /// Submit an order to the active broker.
    ///
    /// Routes the order to the default broker specified in configuration.
    /// Returns the broker-assigned order ID on success.
    pub async fn submit_order(&self, order: &TradingOrder) -> Result<String> {
        let default_broker = &self.config.routing.default_broker;

        match default_broker.as_str() {
            "InteractiveBrokers" | "IBKR" | "IB" => {
                #[cfg(feature = "interactive-brokers")]
                {
                    if let Some(ref client) = self.ib_client {
                        if client.is_connected() {
                            return client
                                .submit_order(order)
                                .await
                                .map_err(|e| e.to_string().into());
                        }
                        return Err("Interactive Brokers client is not connected".into());
                    }
                    Err("Interactive Brokers client not configured (enabled=false)".into())
                }
                #[cfg(not(feature = "interactive-brokers"))]
                {
                    Err("Interactive Brokers support requires the 'interactive-brokers' feature flag".into())
                }
            }
            "ICMarkets" | "ICM" => {
                if let Some(ref client) = self.icm_client {
                    if client.is_connected() {
                        return client
                            .submit_order(order)
                            .await
                            .map_err(|e| e.to_string().into());
                    }
                    return Err("ICMarkets client is not connected".into());
                }
                Err("ICMarkets client not configured (enabled=false)".into())
            }
            other => {
                Err(format!(
                    "Unknown default broker '{}'. Supported: InteractiveBrokers, ICMarkets",
                    other
                )
                .into())
            }
        }
    }

    /// Cancel an order at the active broker.
    pub async fn cancel_order(&self, order_id: &str) -> Result<()> {
        let mut cancelled = false;

        // Try cancelling at all connected brokers since we may not know which one holds the order
        #[cfg(feature = "interactive-brokers")]
        if let Some(ref client) = self.ib_client {
            if client.is_connected() {
                match client.cancel_order(order_id).await {
                    Ok(()) => {
                        info!(order_id, "Order cancelled at Interactive Brokers");
                        cancelled = true;
                    }
                    Err(e) => {
                        warn!(order_id, error = %e, "Cancel at Interactive Brokers failed");
                    }
                }
            }
        }

        if let Some(ref client) = self.icm_client {
            if client.is_connected() {
                match client.cancel_order(order_id).await {
                    Ok(()) => {
                        info!(order_id, "Order cancelled at ICMarkets");
                        cancelled = true;
                    }
                    Err(e) => {
                        warn!(order_id, error = %e, "Cancel at ICMarkets failed");
                    }
                }
            }
        }

        if !cancelled {
            return Err(format!(
                "Failed to cancel order '{}' at any connected broker",
                order_id
            )
            .into());
        }

        Ok(())
    }

    /// Get the list of actually connected broker names.
    pub async fn get_connected_brokers(&self) -> Vec<String> {
        let mut connected: Vec<String> = Vec::new();

        #[cfg(feature = "interactive-brokers")]
        if let Some(ref client) = self.ib_client {
            if client.is_connected() {
                connected.push("InteractiveBrokers".to_owned());
            }
        }

        if let Some(ref client) = self.icm_client {
            if client.is_connected() {
                connected.push("ICMarkets".to_owned());
            }
        }

        connected
    }

    /// Shutdown and disconnect from all brokers.
    pub async fn shutdown(&mut self) -> Result<()> {
        info!("Shutting down broker connections...");

        #[cfg(feature = "interactive-brokers")]
        if let Some(ref mut client) = self.ib_client {
            if client.is_connected() {
                if let Err(e) = client.disconnect().await {
                    error!(error = %e, "Error disconnecting Interactive Brokers");
                }
            }
        }

        if let Some(ref mut client) = self.icm_client {
            if client.is_connected() {
                if let Err(e) = client.disconnect().await {
                    error!(error = %e, "Error disconnecting ICMarkets");
                }
            }
        }

        info!("All broker connections shut down");
        Ok(())
    }

    /// Get a reference to the Interactive Brokers client (if enabled and configured).
    #[cfg(feature = "interactive-brokers")]
    pub fn ib_client(&self) -> Option<&InteractiveBrokersClient> {
        self.ib_client.as_ref()
    }

    /// Get a reference to the ICMarkets client (if configured).
    pub fn icm_client(&self) -> Option<&ICMarketsClient> {
        self.icm_client.as_ref()
    }
}

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

    #[tokio::test]
    async fn test_broker_connector_creation() {
        let config = BrokerConnectorConfig::default();
        let connector = BrokerConnector::new(config);

        // No brokers enabled in default config, so none should be connected
        let connected_brokers = connector.get_connected_brokers().await;
        assert!(
            connected_brokers.is_empty(),
            "Default config has no enabled brokers"
        );
    }

    #[tokio::test]
    async fn test_broker_connector_initialize_no_features() {
        let config = BrokerConnectorConfig::default();
        let mut connector = BrokerConnector::new(config);

        // Should succeed even with no features enabled (just logs a warning)
        let result = connector.initialize().await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_submit_order_unknown_broker() {
        use chrono::Utc;
        use common::{OrderSide, OrderStatus, OrderType, TimeInForce};
        use rust_decimal::Decimal;
        use std::collections::HashMap;

        let mut config = BrokerConnectorConfig::default();
        config.routing.default_broker = "UnknownBroker".to_owned();
        let connector = BrokerConnector::new(config);

        let order = TradingOrder {
            id: "test-unknown-broker".to_owned().into(),
            symbol: "TEST".to_owned(),
            side: OrderSide::Buy,
            order_type: OrderType::Market,
            quantity: Decimal::from(1),
            price: Decimal::from(100),
            time_in_force: TimeInForce::Day,
            account_id: None,
            metadata: HashMap::new(),
            created_at: Utc::now(),
            submitted_at: None,
            executed_at: None,
            status: OrderStatus::Created,
            fill_quantity: Decimal::ZERO,
            average_fill_price: None,
        };
        let result = connector.submit_order(&order).await;
        assert!(result.is_err());
        let err_msg = result.err().map(|e| e.to_string()).unwrap_or_default();
        assert!(
            err_msg.contains("Unknown default broker"),
            "Expected unknown broker error, got: {}",
            err_msg
        );
    }
}
