// Copyright 2025 Neuraville Inc.
// SPDX-License-Identifier: Apache-2.0

//! Configuration for FEAGI Agent SDK

use crate::core::error::{Result, SdkError};
use feagi_io::{
    AgentCapabilities, AgentType, MotorCapability, SensoryCapability, VisionCapability,
    VisualizationCapability,
};

/// Agent configuration builder
#[derive(Debug, Clone)]
pub struct AgentConfig {
    /// Unique agent identifier
    pub agent_id: String,

    /// Agent type (sensory, motor, both, visualization, or infrastructure)
    pub agent_type: AgentType,

    /// Agent capabilities
    pub capabilities: AgentCapabilities,

    /// FEAGI registration endpoint (ZMQ REQ)
    pub registration_endpoint: String,

    /// FEAGI sensory input endpoint (ZMQ PUSH)
    pub sensory_endpoint: String,

    /// FEAGI motor output endpoint (ZMQ SUB)
    pub motor_endpoint: String,

    /// FEAGI visualization stream endpoint (ZMQ SUB)
    pub visualization_endpoint: String,

    /// FEAGI control/API endpoint (ZMQ REQ - REST over ZMQ)
    pub control_endpoint: String,

    /// Heartbeat interval in seconds (0 = disabled)
    pub heartbeat_interval: f64,

    /// Connection timeout in milliseconds
    pub connection_timeout_ms: u64,

    /// Registration retry attempts
    pub registration_retries: u32,

    /// Retry backoff base in milliseconds
    pub retry_backoff_ms: u64,

    /// ZMQ PUSH socket high-water-mark for sensory data
    pub sensory_send_hwm: i32,
    /// ZMQ PUSH socket linger period when disconnecting
    pub sensory_linger_ms: i32,
    /// Whether to enable ZMQ immediate mode on the sensory socket
    pub sensory_immediate: bool,
}

impl AgentConfig {
    /// Create a new agent configuration
    ///
    /// # Arguments
    /// * `agent_id` - Unique identifier for this agent
    /// * `agent_type` - Type of agent (Sensory, Motor, or Both)
    ///
    /// # Example
    /// ```
    /// use feagi_agent::{AgentConfig, AgentType};
    ///
    /// let config = AgentConfig::new("my_camera", AgentType::Sensory);
    /// ```
    pub fn new(agent_id: impl Into<String>, agent_type: AgentType) -> Self {
        Self {
            agent_id: agent_id.into(),
            agent_type,
            capabilities: AgentCapabilities::default(),
            // NO HARDCODED ENDPOINTS - must be set explicitly via builder methods or with_feagi_endpoints()
            registration_endpoint: String::new(),
            sensory_endpoint: String::new(),
            motor_endpoint: String::new(),
            visualization_endpoint: String::new(),
            control_endpoint: String::new(),
            heartbeat_interval: 5.0,
            connection_timeout_ms: 5000,
            registration_retries: 3,
            retry_backoff_ms: 1000,
            // REAL-TIME: HWM=1 ensures agent doesn't buffer old sensory data
            // If FEAGI is slow, old frames are dropped (desired behavior for real-time)
            sensory_send_hwm: 1,
            sensory_linger_ms: 0,
            // REAL-TIME: immediate=true disables Nagle's algorithm for lowest latency
            sensory_immediate: true,
        }
    }

    /// Set FEAGI host and ports to derive all endpoints
    ///
    /// Note: This method requires explicit port numbers. NO DEFAULTS are provided.
    /// Ports must match those configured in FEAGI's feagi_configuration.toml
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("camera", AgentType::Sensory)
    ///     .with_feagi_endpoints("192.168.1.100", 30001, 5558, 5564, 5562, 5563);
    /// ```
    #[deprecated(
        since = "0.1.0",
        note = "Use with_feagi_endpoints() instead to explicitly specify all ports"
    )]
    pub fn with_feagi_host(mut self, host: impl Into<String>) -> Self {
        let host = host.into();
        // @architecture:acceptable - deprecated method, kept for backwards compatibility only
        // Users should migrate to with_feagi_endpoints() or individual endpoint setters
        self.registration_endpoint = format!("tcp://{}:30001", host);
        self.sensory_endpoint = format!("tcp://{}:5558", host);
        self.motor_endpoint = format!("tcp://{}:5564", host);
        self.visualization_endpoint = format!("tcp://{}:5562", host);
        self.control_endpoint = format!("tcp://{}:5563", host);
        self
    }

    /// Set FEAGI endpoints with explicit ports (RECOMMENDED)
    ///
    /// All ports must be provided explicitly to match FEAGI's configuration.
    /// No default values are used.
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("camera", AgentType::Sensory)
    ///     .with_feagi_endpoints(
    ///         "192.168.1.100",
    ///         30001,  // registration_port
    ///         5558,   // sensory_port
    ///         5564,   // motor_port
    ///         5562,   // visualization_port
    ///         5563    // control_port
    ///     );
    /// ```
    pub fn with_feagi_endpoints(
        mut self,
        host: impl Into<String>,
        registration_port: u16,
        sensory_port: u16,
        motor_port: u16,
        visualization_port: u16,
        control_port: u16,
    ) -> Self {
        let host = host.into();
        self.registration_endpoint = format!("tcp://{}:{}", host, registration_port);
        self.sensory_endpoint = format!("tcp://{}:{}", host, sensory_port);
        self.motor_endpoint = format!("tcp://{}:{}", host, motor_port);
        self.visualization_endpoint = format!("tcp://{}:{}", host, visualization_port);
        self.control_endpoint = format!("tcp://{}:{}", host, control_port);
        self
    }

    /// Set registration endpoint
    pub fn with_registration_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.registration_endpoint = endpoint.into();
        self
    }

    /// Set sensory input endpoint
    pub fn with_sensory_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.sensory_endpoint = endpoint.into();
        self
    }

    /// Set motor output endpoint
    pub fn with_motor_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.motor_endpoint = endpoint.into();
        self
    }

    /// Set visualization stream endpoint
    pub fn with_visualization_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.visualization_endpoint = endpoint.into();
        self
    }

    /// Set control/API endpoint
    pub fn with_control_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.control_endpoint = endpoint.into();
        self
    }

    /// Set heartbeat interval in seconds (0 to disable)
    pub fn with_heartbeat_interval(mut self, interval: f64) -> Self {
        self.heartbeat_interval = interval;
        self
    }

    /// Set connection timeout in milliseconds
    pub fn with_connection_timeout_ms(mut self, timeout_ms: u64) -> Self {
        self.connection_timeout_ms = timeout_ms;
        self
    }

    /// Set registration retry attempts
    pub fn with_registration_retries(mut self, retries: u32) -> Self {
        self.registration_retries = retries;
        self
    }

    /// Configure sensory socket behaviour (ZMQ PUSH)
    pub fn with_sensory_socket_config(
        mut self,
        send_hwm: i32,
        linger_ms: i32,
        immediate: bool,
    ) -> Self {
        self.sensory_send_hwm = send_hwm;
        self.sensory_linger_ms = linger_ms;
        self.sensory_immediate = immediate;
        self
    }

    /// Add vision capability
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("camera", AgentType::Sensory)
    ///     .with_vision_capability("camera", (640, 480), 3, "i_vision");
    /// ```
    pub fn with_vision_capability(
        mut self,
        modality: impl Into<String>,
        dimensions: (usize, usize),
        channels: usize,
        target_cortical_area: impl Into<String>,
    ) -> Self {
        self.capabilities.vision = Some(VisionCapability {
            modality: modality.into(),
            dimensions,
            channels,
            target_cortical_area: target_cortical_area.into(),
            unit: None,
            group: None,
        });
        self
    }

    /// Add motor capability
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("arm", AgentType::Motor)
    ///     .with_motor_capability("servo", 4, vec!["o_motor".to_string()]);
    /// ```
    pub fn with_motor_capability(
        mut self,
        modality: impl Into<String>,
        output_count: usize,
        source_cortical_areas: Vec<String>,
    ) -> Self {
        self.capabilities.motor = Some(MotorCapability {
            modality: modality.into(),
            output_count,
            source_cortical_areas,
            unit: None,
            group: None,
            source_units: None,
        });
        self
    }

    /// Add vision capability using semantic unit + group (Option B contract).
    ///
    /// This avoids requiring SDK users to know FEAGI's internal cortical ID encoding.
    pub fn with_vision_unit(
        mut self,
        modality: impl Into<String>,
        dimensions: (usize, usize),
        channels: usize,
        unit: feagi_io::SensoryUnit,
        group: u8,
    ) -> Self {
        self.capabilities.vision = Some(VisionCapability {
            modality: modality.into(),
            dimensions,
            channels,
            target_cortical_area: String::new(),
            unit: Some(unit),
            group: Some(group),
        });
        self
    }

    /// Add motor capability using semantic unit + group (Option B contract).
    pub fn with_motor_unit(
        mut self,
        modality: impl Into<String>,
        output_count: usize,
        unit: feagi_io::MotorUnit,
        group: u8,
    ) -> Self {
        self.capabilities.motor = Some(MotorCapability {
            modality: modality.into(),
            output_count,
            source_cortical_areas: Vec::new(),
            unit: Some(unit),
            group: Some(group),
            source_units: None,
        });
        self
    }

    /// Add multiple motor units using semantic unit + group pairs (Option B contract).
    pub fn with_motor_units(
        mut self,
        modality: impl Into<String>,
        output_count: usize,
        source_units: Vec<feagi_io::MotorUnitSpec>,
    ) -> Self {
        self.capabilities.motor = Some(MotorCapability {
            modality: modality.into(),
            output_count,
            source_cortical_areas: Vec::new(),
            unit: None,
            group: None,
            source_units: Some(source_units),
        });
        self
    }

    /// Add visualization capability
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("brain_viz", AgentType::Visualization)
    ///     .with_visualization_capability("3d_brain", Some((1920, 1080)), Some(30.0), false);
    /// ```
    pub fn with_visualization_capability(
        mut self,
        visualization_type: impl Into<String>,
        resolution: Option<(usize, usize)>,
        refresh_rate: Option<f64>,
        bridge_proxy: bool,
    ) -> Self {
        self.capabilities.visualization = Some(VisualizationCapability {
            visualization_type: visualization_type.into(),
            resolution,
            refresh_rate,
            bridge_proxy,
        });
        self
    }

    /// Add sensory capability (generic)
    ///
    /// This is used for non-vision sensory modalities (text, audio, etc.)
    ///
    /// Device registrations are handled separately via ConnectorAgent and
    /// device_registrations in capabilities.
    ///
    /// # Arguments
    /// * `rate_hz` - Sensory data rate in Hz
    /// * `shm_path` - Optional shared memory path
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// let config = AgentConfig::new("text_input", AgentType::Sensory)
    ///     .with_sensory_capability(20.0, None);
    /// ```
    pub fn with_sensory_capability(mut self, rate_hz: f64, shm_path: Option<String>) -> Self {
        self.capabilities.sensory = Some(SensoryCapability { rate_hz, shm_path });
        self
    }

    /// Add custom capability
    ///
    /// # Example
    /// ```
    /// # use feagi_agent::{AgentConfig, AgentType};
    /// use serde_json::json;
    ///
    /// let config = AgentConfig::new("audio", AgentType::Sensory)
    ///     .with_custom_capability("audio", json!({
    ///         "sample_rate": 44100,
    ///         "channels": 2
    ///     }));
    /// ```
    pub fn with_custom_capability(
        mut self,
        key: impl Into<String>,
        value: serde_json::Value,
    ) -> Self {
        self.capabilities.custom.insert(key.into(), value);
        self
    }

    /// Validate configuration
    pub fn validate(&self) -> Result<()> {
        // Agent ID must not be empty
        if self.agent_id.is_empty() {
            return Err(SdkError::InvalidConfig(
                "agent_id cannot be empty".to_string(),
            ));
        }

        // Must have at least one capability
        if self.capabilities.vision.is_none()
            && self.capabilities.motor.is_none()
            && self.capabilities.visualization.is_none()
            && self.capabilities.sensory.is_none()
            && self.capabilities.custom.is_empty()
        {
            return Err(SdkError::InvalidConfig(
                "Agent must have at least one capability".to_string(),
            ));
        }

        // Validate agent type matches capabilities
        match self.agent_type {
            AgentType::Sensory => {
                if self.capabilities.vision.is_none()
                    && self.capabilities.sensory.is_none()
                    && self.capabilities.custom.is_empty()
                {
                    return Err(SdkError::InvalidConfig(
                        "Sensory agent must have vision or custom input capability".to_string(),
                    ));
                }
            }
            AgentType::Motor => {
                if self.capabilities.motor.is_none() {
                    return Err(SdkError::InvalidConfig(
                        "Motor agent must have motor capability".to_string(),
                    ));
                }
            }
            AgentType::Both => {
                if (self.capabilities.vision.is_none()
                    && self.capabilities.sensory.is_none()
                    && self.capabilities.custom.is_empty())
                    || self.capabilities.motor.is_none()
                {
                    return Err(SdkError::InvalidConfig(
                        "Bidirectional agent must have both input and output capabilities"
                            .to_string(),
                    ));
                }
            }
            AgentType::Visualization => {
                if self.capabilities.visualization.is_none() {
                    return Err(SdkError::InvalidConfig(
                        "Visualization agent must have visualization capability".to_string(),
                    ));
                }
            }
            AgentType::Infrastructure => {
                // Infrastructure agents can have any combination of capabilities
                // No strict requirements as they may proxy multiple types
                if self.capabilities.vision.is_none()
                    && self.capabilities.motor.is_none()
                    && self.capabilities.visualization.is_none()
                    && self.capabilities.custom.is_empty()
                {
                    return Err(SdkError::InvalidConfig(
                        "Infrastructure agent must declare at least one capability".to_string(),
                    ));
                }
            }
        }

        // Validate endpoints based on agent type
        // Registration endpoint is always required
        if self.registration_endpoint.is_empty() {
            return Err(SdkError::InvalidConfig(
                "registration_endpoint must be set (use with_registration_endpoint() or with_feagi_endpoints())".to_string()
            ));
        }
        if !self.registration_endpoint.starts_with("tcp://") {
            return Err(SdkError::InvalidConfig(
                "registration_endpoint must start with tcp://".to_string(),
            ));
        }

        // Validate sensory endpoint for sensory agents
        if matches!(self.agent_type, AgentType::Sensory | AgentType::Both) {
            if self.sensory_endpoint.is_empty() {
                return Err(SdkError::InvalidConfig(
                    "sensory_endpoint must be set for Sensory/Both agents (use with_sensory_endpoint() or with_feagi_endpoints())".to_string()
                ));
            }
            if !self.sensory_endpoint.starts_with("tcp://") {
                return Err(SdkError::InvalidConfig(
                    "sensory_endpoint must start with tcp://".to_string(),
                ));
            }
        }

        // Validate motor endpoint for motor agents
        if matches!(self.agent_type, AgentType::Motor | AgentType::Both) {
            if self.motor_endpoint.is_empty() {
                return Err(SdkError::InvalidConfig(
                    "motor_endpoint must be set for Motor/Both agents (use with_motor_endpoint() or with_feagi_endpoints())".to_string()
                ));
            }
            if !self.motor_endpoint.starts_with("tcp://") {
                return Err(SdkError::InvalidConfig(
                    "motor_endpoint must start with tcp://".to_string(),
                ));
            }
        }

        // Validate visualization endpoint for visualization agents
        if matches!(self.agent_type, AgentType::Visualization) {
            if self.visualization_endpoint.is_empty() {
                return Err(SdkError::InvalidConfig(
                    "visualization_endpoint must be set for Visualization agents (use with_visualization_endpoint() or with_feagi_endpoints())".to_string()
                ));
            }
            if !self.visualization_endpoint.starts_with("tcp://") {
                return Err(SdkError::InvalidConfig(
                    "visualization_endpoint must start with tcp://".to_string(),
                ));
            }
        }

        if self.sensory_send_hwm < 0 {
            return Err(SdkError::InvalidConfig(
                "sensory_send_hwm must be >= 0".to_string(),
            ));
        }

        Ok(())
    }
}

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

    #[test]
    fn test_config_builder() {
        #[allow(deprecated)]
        let config = AgentConfig::new("test_agent", AgentType::Sensory)
            .with_feagi_host("192.168.1.100")
            .with_vision_capability("camera", (640, 480), 3, "i_vision")
            .with_heartbeat_interval(10.0);

        assert_eq!(config.agent_id, "test_agent");
        assert_eq!(config.heartbeat_interval, 10.0);
        assert_eq!(config.registration_endpoint, "tcp://192.168.1.100:30001");
        assert!(config.capabilities.vision.is_some());
    }

    #[test]
    fn test_config_validation_empty_agent_id() {
        let config = AgentConfig::new("", AgentType::Sensory);
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_config_validation_no_capabilities() {
        let config = AgentConfig::new("test", AgentType::Sensory);
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_config_validation_sensory_without_input() {
        let mut config = AgentConfig::new("test", AgentType::Sensory);
        config.capabilities.motor = Some(MotorCapability {
            modality: "servo".to_string(),
            output_count: 1,
            source_cortical_areas: vec!["motor".to_string()],
            unit: None,
            group: None,
            source_units: None,
        });
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_config_validation_valid() {
        let config = AgentConfig::new("test", AgentType::Sensory)
            .with_vision_capability("camera", (640, 480), 3, "vision")
            .with_registration_endpoint("tcp://localhost:8000")
            .with_sensory_endpoint("tcp://localhost:5558");
        assert!(config.validate().is_ok());
    }
}
