//! @docs ARCHITECTURE:Domain
//!
//! ### AI Assist Note
//! **Domain Merge Logic**: Authoritative state transition engine for AI Agents.
//! Orchestrates partial updates, status changes, and telemetry resets.
//! Shifted from imperative route handlers to domain methods to enforce
//! structural integrity and prevent state corruption during concurrent updates.
//!
//! ### 🔍 Debugging & Observability
//! - **Failure Path**: Field collision during concurrent `AgentConfigUpdate` applications or mismatch in CamelCase deserialization from frontend.
//! - **Telemetry Link**: Search `[AgentMerge]` in server traces.
//!

use crate::agent::types::{ConnectorConfig, EngineAgent, ModelConfig, ModelProvider};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

impl EngineAgent {
    /// Pauses a running agent, changing its operational status.
    pub fn pause(&mut self) -> bool {
        if self.health.status != "suspended" {
            self.health.status = "suspended".to_string();
            return true;
        }
        false
    }

    /// Resumes a suspended agent, returning it to idle.
    pub fn resume(&mut self) -> bool {
        if self.health.status == "suspended" {
            self.health.status = "idle".to_string();
            return true;
        }
        false
    }

    /// Resets agent failure telemetry and status.
    pub fn reset(&mut self) {
        self.health.failure_count = 0;
        self.health.last_failure_at = None;
        self.health.status = "idle".to_string();
    }

    /// Updates the active mission context.
    pub fn set_mission(&mut self, mission: serde_json::Value) {
        self.state.active_mission = Some(mission);
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, specta::Type)]
#[serde(rename_all = "camelCase")]
pub struct AgentConfigUpdate {
    pub name: Option<String>,
    pub role: Option<String>,
    pub department: Option<String>,
    pub provider: Option<ModelProvider>,
    #[serde(alias = "model")]
    pub model_id: Option<String>,
    #[serde(default, alias = "modelConfig")]
    pub model_config: Option<ModelConfig>,
    #[serde(default, alias = "model2")]
    pub model_2: Option<String>,
    #[serde(default, alias = "model3")]
    pub model_3: Option<String>,
    pub api_key: Option<String>,
    pub system_prompt: Option<String>,
    pub temperature: Option<f32>,
    pub top_p: Option<f32>,
    pub top_k: Option<u32>,
    pub base_url: Option<String>,
    pub reasoning_depth: Option<u32>,
    pub act_threshold: Option<f32>,
    pub theme_color: Option<String>,
    pub budget_usd: Option<f64>,
    pub external_id: Option<String>,
    pub skills: Option<Vec<String>>,
    pub workflows: Option<Vec<String>>,
    pub mcp_tools: Option<Vec<String>>,
    pub active_model_slot: Option<i32>,
    pub model_config2: Option<ModelConfig>,
    pub model_config3: Option<ModelConfig>,
    pub voice_id: Option<String>,
    pub voice_engine: Option<String>,
    pub category: Option<String>,
    pub requires_oversight: Option<bool>,
    pub connector_configs: Option<Vec<ConnectorConfig>>,
    pub input_tokens: Option<u32>,
    pub output_tokens: Option<u32>,
    pub total_tokens: Option<u32>,
    pub tokens_used: Option<u32>,
    pub created_at: Option<DateTime<Utc>>,
    pub last_pulse: Option<DateTime<Utc>>,
    pub current_task: Option<String>,
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

impl AgentConfigUpdate {
    pub fn apply_to(&self, agent: &mut EngineAgent) -> bool {
        let mut changed = false;

        if let Some(name) = &self.name {
            agent.identity.name = name.clone();
            changed = true;
        }
        if let Some(role) = &self.role {
            agent.identity.role = role.clone();
            changed = true;
        }
        if let Some(dept) = &self.department {
            agent.identity.department = dept.clone();
            changed = true;
        }

        // Slot 1: Combined nested and flat field support
        if let Some(mc) = &self.model_config {
            agent.models.model = mc.clone();
            if !mc.model_id.is_empty() {
                agent.models.model_id = Some(mc.model_id.clone());
            }
            changed = true;
        } else {
            // Fallback to flat fields if model_config is not provided
            if let Some(model_id) = &self.model_id {
                agent.models.model_id = Some(model_id.clone());
                agent.models.model.model_id = model_id.clone();
                changed = true;
            }
            if let Some(provider) = self.provider {
                agent.models.model.provider = provider;
                changed = true;
            }
            if let Some(temp) = self.temperature {
                agent.models.model.temperature = Some(temp);
                changed = true;
            }
            if let Some(top_p) = self.top_p {
                let mut extras = agent
                    .models
                    .model
                    .extra_parameters
                    .clone()
                    .unwrap_or_default();
                extras.insert("top_p".to_string(), serde_json::json!(top_p));
                agent.models.model.extra_parameters = Some(extras);
                changed = true;
            }
            if let Some(top_k) = self.top_k {
                let mut extras = agent
                    .models
                    .model
                    .extra_parameters
                    .clone()
                    .unwrap_or_default();
                extras.insert("top_k".to_string(), serde_json::json!(top_k));
                agent.models.model.extra_parameters = Some(extras);
                changed = true;
            }
            if let Some(prompt) = &self.system_prompt {
                agent.models.model.system_prompt = Some(prompt.clone());
                changed = true;
            }
            if let Some(reasoning_depth) = self.reasoning_depth {
                agent.models.model.reasoning_depth = Some(reasoning_depth);
                changed = true;
            }
            if let Some(act_threshold) = self.act_threshold {
                agent.models.model.act_threshold = Some(act_threshold);
                changed = true;
            }
            if let Some(api_key) = &self.api_key {
                agent.models.model.api_key = Some(api_key.clone());
                changed = true;
            }
            if let Some(base_url) = &self.base_url {
                agent.models.model.base_url = Some(base_url.clone());
                changed = true;
            }
        }

        if let Some(color) = &self.theme_color {
            agent.identity.theme_color = Some(color.clone());
            changed = true;
        }
        if let Some(budget) = self.budget_usd {
            agent.economics.budget_usd = budget;
            changed = true;
        }
        if let Some(skills) = &self.skills {
            agent.capabilities.skills = skills.clone();
            changed = true;
        }
        if let Some(workflow) = &self.workflows {
            agent.capabilities.workflows = workflow.clone();
            changed = true;
        }
        if let Some(mcp_tools) = &self.mcp_tools {
            agent.capabilities.mcp_tools = mcp_tools.clone();
            changed = true;
        }
        if let Some(m2) = &self.model_2 {
            if let Some(slot) = agent.models.planning_slot.as_mut() {
                slot.model_id = m2.clone();
            }
            changed = true;
        }
        if let Some(m3) = &self.model_3 {
            if let Some(slot) = agent.models.execution_slot.as_mut() {
                slot.model_id = m3.clone();
            }
            changed = true;
        }
        if let Some(active_slot) = self.active_model_slot {
            agent.models.active_model_slot = Some(match active_slot {
                1 => "planning".to_string(),
                2 => "execution".to_string(),
                _ => "default".to_string(),
            });
            changed = true;
        }
        if let Some(mc2) = &self.model_config2 {
            agent.models.planning_slot = Some(mc2.clone());
            changed = true;
        }
        if let Some(mc3) = &self.model_config3 {
            agent.models.execution_slot = Some(mc3.clone());
            changed = true;
        }
        if let Some(connector_configs) = &self.connector_configs {
            agent.connector_configs = connector_configs.clone();
            changed = true;
        }
        if let Some(voice_id) = &self.voice_id {
            agent.voice_id = Some(voice_id.clone());
            changed = true;
        }
        if let Some(voice_engine) = &self.voice_engine {
            agent.voice_engine = Some(voice_engine.clone());
            changed = true;
        }
        if let Some(base_url) = &self.base_url {
            agent.models.model.base_url = Some(base_url.clone());
            changed = true;
        }
        if let Some(oversight) = self.requires_oversight {
            agent.requires_oversight = oversight;
            changed = true;
        }
        if let Some(category) = &self.category {
            agent.identity.category = category.clone();
            changed = true;
        }
        if let Some(created_at) = self.created_at {
            agent.created_at = Some(created_at);
            changed = true;
        }
        if let Some(last_pulse) = self.last_pulse {
            agent.health.heartbeat_at = Some(last_pulse);
            changed = true;
        }
        if let Some(current_task) = &self.current_task {
            agent.state.current_task = Some(current_task.clone());
            changed = true;
        }
        if let Some(metadata) = &self.metadata {
            agent.metadata.extend(metadata.clone());
            changed = true;
        }

        if let Some(it) = self.input_tokens {
            agent.economics.token_usage.input_tokens = it as u64;
            changed = true;
        }
        if let Some(ot) = self.output_tokens {
            agent.economics.token_usage.output_tokens = ot as u64;
            changed = true;
        }
        if let Some(tt) = self.total_tokens {
            agent.economics.token_usage.total_tokens = tt as u64;
            changed = true;
        }
        if let Some(tu) = self.tokens_used {
            agent.economics.tokens_used = tu as u64;
            changed = true;
        }

        changed
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::agent::types::EngineAgent;

    #[test]
    fn test_agent_state_logic() {
        let mut agent = EngineAgent {
            health: crate::agent::types::AgentHealth {
                status: "idle".to_string(),
                ..Default::default()
            },
            ..Default::default()
        };

        assert!(agent.pause());
        assert_eq!(agent.health.status, "suspended");
        assert!(!agent.pause()); // Should not change if already suspended

        assert!(agent.resume());
        assert_eq!(agent.health.status, "idle");
        assert!(!agent.resume()); // Should not change if idle

        agent.health.failure_count = 5;
        agent.health.status = "failed".to_string();
        agent.reset();
        assert_eq!(agent.health.failure_count, 0);
        assert_eq!(agent.health.status, "idle");
    }

    #[test]
    fn test_agent_config_merge() {
        let mut agent = EngineAgent {
            identity: crate::agent::types::AgentIdentity {
                name: "Original".to_string(),
                ..Default::default()
            },
            ..Default::default()
        };

        let update = AgentConfigUpdate {
            name: Some("Updated".to_string()),
            budget_usd: Some(500.0),
            skills: Some(vec!["rust".to_string()]),
            input_tokens: Some(100),
            ..Default::default()
        };

        let changed = update.apply_to(&mut agent);
        assert!(changed);
        assert_eq!(agent.identity.name, "Updated");
        assert_eq!(agent.economics.budget_usd, 500.0);
        assert_eq!(agent.capabilities.skills, vec!["rust".to_string()]);
        assert_eq!(agent.economics.token_usage.input_tokens, 100);
    }

    #[test]
    fn test_update_deserialization_parity() {
        // Enforce that we can deserialize from camelCase matching frontend serializers
        let json = r#"{
            "name": "CamelCaseAgent",
            "budgetUsd": 123.45,
            "inputTokens": 1000
        }"#;

        let update: AgentConfigUpdate = serde_json::from_str(json).expect("Deserialization failed");
        assert_eq!(update.name, Some("CamelCaseAgent".to_string()));
        assert_eq!(update.budget_usd, Some(123.45));
        assert_eq!(update.input_tokens, Some(1000));
    }

    #[test]
    fn test_nested_model_config_deserialization() {
        // Verify that modelConfig (nested) correctly deserializes
        let json = r#"{
            "modelConfig": {
                "provider": "anthropic",
                "modelId": "claude-3-5-sonnet",
                "temperature": 0.5
            }
        }"#;

        let update: AgentConfigUpdate =
            serde_json::from_str(json).expect("Nested deserialization failed");
        assert!(update.model_config.is_some());
        let mc = update.model_config.unwrap();
        assert_eq!(mc.provider, ModelProvider::Anthropic);
        assert_eq!(mc.model_id, "claude-3-5-sonnet");
        assert_eq!(mc.temperature, Some(0.5));
    }

    #[test]
    fn test_apply_nested_model_config() {
        let mut agent = EngineAgent::default();
        let update = AgentConfigUpdate {
            model_config: Some(ModelConfig {
                provider: ModelProvider::Anthropic,
                model_id: "claude-3".to_string(),
                temperature: Some(0.2),
                ..Default::default()
            }),
            ..Default::default()
        };

        update.apply_to(&mut agent);
        assert_eq!(agent.models.model.provider, ModelProvider::Anthropic);
        assert_eq!(agent.models.model.model_id, "claude-3");
        assert_eq!(agent.models.model_id, Some("claude-3".to_string()));
        assert_eq!(agent.models.model.temperature, Some(0.2));
    }

    #[test]
    fn test_nested_model_config_slots_2_3_deserialization() {
        let json = r#"{
            "modelConfig2": {
                "provider": "openai",
                "modelId": "gpt-4"
            },
            "modelConfig3": {
                "provider": "google",
                "modelId": "gemini-1.5-pro"
            }
        }"#;

        let update: AgentConfigUpdate =
            serde_json::from_str(json).expect("Nested slots 2/3 deserialization failed");
        assert!(update.model_config2.is_some());
        assert!(update.model_config3.is_some());
        assert_eq!(update.model_config2.as_ref().unwrap().model_id, "gpt-4");
        assert_eq!(
            update.model_config3.as_ref().unwrap().model_id,
            "gemini-1.5-pro"
        );
    }

    #[test]
    fn test_flat_model_slots_2_3_deserialization() {
        let json = r#"{
            "model2": "llama-3",
            "model3": "mixtral-8x7b"
        }"#;

        let update: AgentConfigUpdate =
            serde_json::from_str(json).expect("Flat slots 2/3 deserialization failed");
        assert_eq!(update.model_2, Some("llama-3".to_string()));
        assert_eq!(update.model_3, Some("mixtral-8x7b".to_string()));
    }

    #[test]
    fn test_agent_config_sampling_params_merge() {
        let mut agent = EngineAgent {
            models: crate::agent::types::AgentModels {
                model: crate::agent::types::ModelConfig {
                    provider: crate::agent::types::ModelProvider::Anthropic,
                    model_id: "claude-3".to_string(),
                    ..Default::default()
                },
                ..Default::default()
            },
            ..Default::default()
        };

        let update = AgentConfigUpdate {
            top_p: Some(0.9),
            top_k: Some(50),
            ..Default::default()
        };

        let changed = update.apply_to(&mut agent);
        assert!(changed);

        let extras = agent
            .models
            .model
            .extra_parameters
            .as_ref()
            .expect("Extras missing");
        assert_eq!(extras.get("top_p").unwrap(), &serde_json::json!(0.9f32));
        assert_eq!(extras.get("top_k").unwrap(), &serde_json::json!(50));

        assert_eq!(
            agent.models.model.provider,
            crate::agent::types::ModelProvider::Anthropic
        );
        assert_eq!(agent.models.model.model_id, "claude-3");
    }
}

// Metadata: [AgentMerge]
