//! Configuration file support for FXT
//!
//! This module provides configuration file management for FXT client settings.
//! Configuration is loaded from `~/.foxhunt/config.toml` and can be overridden
//! by CLI arguments (following standard CLI precedence).
//!
//! ## Precedence
//! CLI arguments > Config file > Defaults
//!
//! ## Example Config File
//! ```toml
//! # ~/.foxhunt/config.toml
//! api_url = "https://api.fxhnt.ai"
//! log_level = "info"
//! token_storage = "keyring"
//! ```

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// IBKR-specific connection settings
#[derive(Debug, Serialize, Deserialize)]
pub struct IbkrConfig {
    /// IB Gateway host
    #[serde(default = "default_ibkr_host")]
    pub host: String,
    /// IB Gateway socat port (4004 = paper, 4003 = live)
    #[serde(default = "default_ibkr_port")]
    pub port: u16,
    /// TWS client ID (unique per connection)
    #[serde(default = "default_ibkr_client_id")]
    pub client_id: i32,
}

fn default_ibkr_host() -> String {
    "127.0.0.1".to_owned()
}
fn default_ibkr_port() -> u16 {
    4004
}
// Default IBKR client ID (99 = non-production default)
fn default_ibkr_client_id() -> i32 {
    99
}

impl Default for IbkrConfig {
    fn default() -> Self {
        Self {
            host: default_ibkr_host(),
            port: default_ibkr_port(),
            client_id: default_ibkr_client_id(),
        }
    }
}

/// Broker connectivity settings
#[derive(Debug, Serialize, Deserialize)]
pub struct BrokerConfig {
    /// Broker Gateway gRPC URL
    #[serde(default = "default_broker_gateway_url")]
    pub gateway_url: String,
    /// IBKR-specific settings
    #[serde(default)]
    pub ibkr: IbkrConfig,
}

fn default_broker_gateway_url() -> String {
    "http://localhost:50056".to_owned()
}

impl Default for BrokerConfig {
    fn default() -> Self {
        Self {
            gateway_url: default_broker_gateway_url(),
            ibkr: IbkrConfig::default(),
        }
    }
}

/// FXT configuration structure
///
/// Contains all configurable settings for the FXT client application.
/// Settings can be loaded from `~/.foxhunt/config.toml` and overridden
/// by CLI arguments or environment variables.
#[derive(Debug, Serialize, Deserialize)]
pub struct FxtConfig {
    /// Unified API URL (default: <https://api.fxhnt.ai>)
    #[serde(default = "default_api_url")]
    pub api_url: String,

    /// Log level (default: info)
    #[serde(default = "default_log_level")]
    pub log_level: String,

    /// Token storage backend (default: keyring)
    #[serde(default = "default_token_storage")]
    pub token_storage: String,

    /// JWT secret for token signing (must match API Gateway's JWT_SECRET)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub jwt_secret: Option<String>,

    /// Broker connectivity settings
    #[serde(default)]
    pub broker: BrokerConfig,
}

fn default_api_url() -> String {
    "https://api.fxhnt.ai".to_owned()
}

fn default_log_level() -> String {
    "info".to_owned()
}

fn default_token_storage() -> String {
    "keyring".to_owned()
}

impl Default for FxtConfig {
    fn default() -> Self {
        Self {
            api_url: default_api_url(),
            log_level: default_log_level(),
            token_storage: default_token_storage(),
            jwt_secret: None,
            broker: BrokerConfig::default(),
        }
    }
}

impl FxtConfig {
    /// Load config from ~/.foxhunt/config.toml
    ///
    /// Returns default configuration if file doesn't exist.
    /// Returns error if file exists but is malformed.
    ///
    /// # Examples
    /// ```no_run
    /// use fxt::config::FxtConfig;
    ///
    /// let config = FxtConfig::load().unwrap();
    /// println!("API URL: {}", config.api_url);
    /// ```
    pub fn load() -> Result<Self> {
        let config_path = Self::config_path()?;

        if !config_path.exists() {
            // Return defaults if no config file
            return Ok(Self::default());
        }

        let content =
            std::fs::read_to_string(&config_path).context("Failed to read config file")?;

        let config: Self = toml::from_str(&content).context("Failed to parse config file")?;

        Ok(config)
    }

    /// Save config to ~/.foxhunt/config.toml
    ///
    /// Creates parent directory if it doesn't exist.
    ///
    /// # Examples
    /// ```no_run
    /// use fxt::config::FxtConfig;
    ///
    /// let config = FxtConfig {
    ///     api_url: "http://localhost:50051".to_string(),
    ///     log_level: "debug".to_string(),
    ///     token_storage: "keyring".to_string(),
    ///     jwt_secret: None,
    ///     broker: BrokerConfig::default(),
    /// };
    /// config.save().unwrap();
    /// ```
    pub fn save(&self) -> Result<()> {
        let config_path = Self::config_path()?;

        // Create parent directory if needed
        if let Some(parent) = config_path.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let content = toml::to_string_pretty(self).context("Failed to serialize config")?;

        std::fs::write(&config_path, content).context("Failed to write config file")?;

        Ok(())
    }

    /// Get config file path: ~/.foxhunt/config.toml
    ///
    /// # Errors
    /// Returns error if home directory cannot be determined
    fn config_path() -> Result<PathBuf> {
        let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Cannot find home directory"))?;

        Ok(home.join(".foxhunt").join("config.toml"))
    }
}

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

    #[test]
    fn test_default_config() {
        // Test that Default trait provides correct values
        let config = FxtConfig::default();
        assert_eq!(config.api_url, "https://api.fxhnt.ai");
        assert_eq!(config.log_level, "info");
        assert_eq!(config.token_storage, "keyring");
    }

    #[test]
    fn test_config_serialization() {
        let config = FxtConfig {
            api_url: "http://example.com:50051".to_owned(),
            log_level: "debug".to_owned(),
            token_storage: "file".to_owned(),
            jwt_secret: None,
            broker: BrokerConfig::default(),
        };

        let toml_str = toml::to_string(&config).unwrap();
        let parsed: FxtConfig = toml::from_str(&toml_str).unwrap();

        assert_eq!(config.api_url, parsed.api_url);
        assert_eq!(config.log_level, parsed.log_level);
        assert_eq!(config.token_storage, parsed.token_storage);
    }

    #[test]
    fn test_load_config_succeeds() {
        // load() should always succeed: returns file contents if present, defaults otherwise.
        // Default-value assertions live in test_serde_defaults and test_default_config.
        let result = FxtConfig::load();
        assert!(result.is_ok());
    }

    #[test]
    fn test_serde_defaults() {
        // Test that empty TOML string deserializes to defaults
        let empty_toml = "";
        let config: FxtConfig = toml::from_str(empty_toml).unwrap();
        assert_eq!(config.api_url, "https://api.fxhnt.ai");
        assert_eq!(config.log_level, "info");
        assert_eq!(config.token_storage, "keyring");
    }

    #[test]
    fn test_config_with_broker_section() {
        let toml_str = r#"
            api_url = "http://localhost:50051"

            [broker]
            gateway_url = "http://broker-gw:50060"

            [broker.ibkr]
            host = "10.0.0.5"
            port = 4002
            client_id = 42
        "#;
        let config: FxtConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(config.broker.gateway_url, "http://broker-gw:50060");
        assert_eq!(config.broker.ibkr.host, "10.0.0.5");
        assert_eq!(config.broker.ibkr.port, 4002);
        assert_eq!(config.broker.ibkr.client_id, 42);
    }

    #[test]
    fn test_config_broker_defaults() {
        let config: FxtConfig = toml::from_str("").unwrap();
        assert_eq!(config.broker.gateway_url, "http://localhost:50056");
        assert_eq!(config.broker.ibkr.host, "127.0.0.1");
        assert_eq!(config.broker.ibkr.port, 4004);
        assert_eq!(config.broker.ibkr.client_id, 99);
    }
}
