use crate::types::meta::Meta;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Session {
    pub id: String,
    pub title: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub metadata: SessionMetadata,
    /// Working directory for this session (if known)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cwd: Option<String>,
    /// ACP model state (available models and current model)
    /// Populated from archivist for archived sessions, or from SSE events for live sessions.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub models: Option<SessionModelState>,
    /// ACP mode state (available modes and current mode)
    /// Populated from archivist for archived sessions, or from SSE events for live sessions.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub modes: Option<SessionModeState>,
    /// ACP config options (replaces modes/models in future ACP versions)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub config_options: Option<Vec<ConfigOption>>,
    /// ACP client ID that owns this session.
    /// For sessions created via ACP Server (incoming connections), this identifies
    /// which connected client created/owns this session.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub acp_client_id: Option<String>,
}

// ============================================================================
// ACP Session Mode/Model Types
// ============================================================================
// These types match the Agent-Client Protocol (ACP) specification exactly.
// They use camelCase serialization to match Claude-ACP's JSON format.
// See: docs/architecture/agent_client_protocol/schema.md

/// Type alias for session mode identifiers (e.g., "default", "plan", "bypassPermissions")
pub type SessionModeId = String;

/// Type alias for model identifiers (e.g., "default", "sonnet", "haiku", "opus")
pub type ModelId = String;

/// Session mode state from ACP `session/new` response
///
/// Contains the list of available modes and the currently active mode.
/// This is part of the stable ACP specification.
///
/// # Example (from Claude-ACP)
/// ```json
/// {
///   "currentModeId": "default",
///   "availableModes": [
///     {"id": "default", "name": "Always Ask", "description": "..."},
///     {"id": "plan", "name": "Plan Mode", "description": "..."}
///   ]
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SessionModeState {
    /// The currently active mode ID
    pub current_mode_id: SessionModeId,
    /// List of all available modes for this session
    pub available_modes: Vec<SessionMode>,
}

/// A single session mode definition
///
/// Modes affect agent behavior, tool availability, and permission handling.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SessionMode {
    /// Unique identifier for this mode
    pub id: SessionModeId,
    /// Human-readable display name
    pub name: String,
    /// Optional description of what this mode does
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

/// Session model state from ACP `session/new` response
///
/// Contains the list of available models and the currently selected model.
/// Note: This field is marked UNSTABLE in the ACP spec but is used by Claude-ACP.
///
/// # Example (from Claude-ACP)
/// ```json
/// {
///   "availableModels": [
///     {"modelId": "default", "name": "Default (recommended)", "description": "..."},
///     {"modelId": "sonnet", "name": "Sonnet", "description": "..."}
///   ],
///   "currentModelId": "default"
/// }
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SessionModelState {
    /// List of all available models for this session
    pub available_models: Vec<ModelInfo>,
    /// The currently selected model ID
    pub current_model_id: ModelId,
}

/// Information about a single model
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModelInfo {
    /// Unique identifier for this model
    pub model_id: ModelId,
    /// Human-readable display name
    pub name: String,
    /// Optional description of the model's capabilities
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

// ============================================================================
// ACP Config Options (replaces modes/models in future ACP versions)
// ============================================================================

/// A configuration option for a session (ACP configOptions).
///
/// Agents provide config options in session/new and session/load responses.
/// Clients should use these instead of the legacy `modes`/`models` fields.
/// See: docs/architecture/agent_client_protocol/session-config-options.md
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigOption {
    /// Unique identifier (e.g., "mode", "model")
    pub id: String,
    /// Human-readable label
    pub name: String,
    /// Optional description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Semantic category for UX grouping (e.g., "mode", "model", "thought_level")
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    /// Input type (currently only "select" is defined)
    #[serde(rename = "type")]
    pub option_type: ConfigOptionType,
    /// Currently selected value
    pub current_value: String,
    /// Available values for select-type options
    pub options: Vec<ConfigOptionValue>,
}

/// Type of configuration option input
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ConfigOptionType {
    Select,
}

/// A single value choice within a config option
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigOptionValue {
    /// Value identifier (sent back when setting this option)
    pub value: String,
    /// Human-readable display name
    pub name: String,
    /// Optional description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SessionMetadata {
    pub project_path: String,
    pub model: Option<String>,
    /// Total count of user and assistant messages in the session (excludes system messages).
    /// A value of 0 may indicate either an empty session or that the count has not yet been calculated.
    /// Counts are populated lazily when messages are loaded for a session.
    /// See `docs/architecture/session_message_counts.md` for details.
    pub total_messages: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_message: Option<String>,
    /// Current mode identifier for future mode tracking
    #[serde(skip_serializing_if = "Option::is_none")]
    pub current_mode_id: Option<String>,
    /// Provider metadata for tracking original IDs and debugging information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub _meta: Option<Meta>,
    /// Optional project ID linking this session to a dirigent_projects Project.
    /// When set, the session belongs to the specified project for organizational purposes.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub project_id: Option<uuid::Uuid>,
}

// ============================================================================
// Session Ownership Model
// ============================================================================
// These types define how sessions are owned and how tool execution is routed.
// See: docs/architecture/session_ownership.md (Phase 7)

/// The origin of a session - who initiated it
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SessionOrigin {
    /// Session created by Dirigent UI user
    Internal,

    /// Session forwarded from external ACP client
    External {
        /// The ACP client ID that owns this session
        client_id: String,
        /// Cached client capabilities (from initialization)
        #[serde(default, skip_serializing_if = "Option::is_none")]
        client_capabilities: Option<serde_json::Value>,
    },

    /// Session representing a subagent or internal task (future)
    Subagent {
        /// Parent session that spawned this subagent
        parent_session_id: String,
        /// Task identifier
        task_id: String,
    },
}

impl Default for SessionOrigin {
    fn default() -> Self {
        Self::Internal
    }
}

/// Who handles tool execution for this session
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ToolHandler {
    /// Agent handles its own tools (default)
    #[default]
    Agent,

    /// Dirigent intercepts and handles tools via dirigent_tools (future)
    Dirigent,

    /// Forward tool requests to originating client (External sessions only)
    ForwardToClient,
}

/// Complete ownership model for a session
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SessionOwnership {
    /// Where this session originated
    #[serde(default)]
    pub origin: SessionOrigin,

    /// How tool requests are handled
    #[serde(default)]
    pub tool_handler: ToolHandler,
}

impl SessionOwnership {
    /// Internal session with agent handling tools (default UI case)
    pub fn internal() -> Self {
        Self {
            origin: SessionOrigin::Internal,
            tool_handler: ToolHandler::Agent,
        }
    }

    /// External session with tools forwarded to client
    pub fn external_forwarded(client_id: String, capabilities: Option<serde_json::Value>) -> Self {
        Self {
            origin: SessionOrigin::External {
                client_id,
                client_capabilities: capabilities,
            },
            tool_handler: ToolHandler::ForwardToClient,
        }
    }

    /// External session but Dirigent handles tools
    pub fn external_handled(client_id: String, capabilities: Option<serde_json::Value>) -> Self {
        Self {
            origin: SessionOrigin::External {
                client_id,
                client_capabilities: capabilities,
            },
            tool_handler: ToolHandler::Dirigent,
        }
    }

    /// Get capabilities to advertise to agent based on ownership
    pub fn capabilities_for_agent(&self) -> serde_json::Value {
        match (&self.origin, &self.tool_handler) {
            // External + ForwardToClient: use client's capabilities
            (
                SessionOrigin::External {
                    client_capabilities: Some(caps),
                    ..
                },
                ToolHandler::ForwardToClient,
            ) => caps.clone(),
            // Dirigent handles tools: advertise dirigent_tools capabilities
            (_, ToolHandler::Dirigent) => {
                serde_json::json!({
                    "fs": { "readTextFile": true, "writeTextFile": true },
                    "terminal": true
                })
            }
            // Agent handles tools or no client caps: empty (agent uses its own)
            _ => serde_json::json!({}),
        }
    }

    /// Get the client ID if this should forward requests to a client
    pub fn forward_to_client(&self) -> Option<&str> {
        match (&self.origin, &self.tool_handler) {
            (SessionOrigin::External { client_id, .. }, ToolHandler::ForwardToClient) => {
                Some(client_id.as_str())
            }
            _ => None,
        }
    }

    /// Check if this is an external (forwarded) session
    pub fn is_external(&self) -> bool {
        matches!(self.origin, SessionOrigin::External { .. })
    }

    /// Get the originating client ID if external
    pub fn client_id(&self) -> Option<&str> {
        match &self.origin {
            SessionOrigin::External { client_id, .. } => Some(client_id.as_str()),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::meta::{Meta, ProviderMeta};
    use std::collections::HashMap;

    // ========================================================================
    // ACP Session Mode/Model Type Tests
    // ========================================================================

    #[test]
    fn test_session_mode_state_serialization_camel_case() {
        // Verify camelCase serialization matches Claude-ACP format
        let mode_state = SessionModeState {
            current_mode_id: "default".to_string(),
            available_modes: vec![
                SessionMode {
                    id: "default".to_string(),
                    name: "Always Ask".to_string(),
                    description: Some(
                        "Prompts for permission on first use of each tool".to_string(),
                    ),
                },
                SessionMode {
                    id: "plan".to_string(),
                    name: "Plan Mode".to_string(),
                    description: Some("Claude can analyze but not modify files".to_string()),
                },
            ],
        };

        let json = serde_json::to_string(&mode_state).unwrap();
        // Verify camelCase field names
        assert!(json.contains("currentModeId"));
        assert!(json.contains("availableModes"));
        // Verify content
        assert!(json.contains(r#""currentModeId":"default"#));
        assert!(json.contains(r#""name":"Always Ask"#));
    }

    #[test]
    fn test_session_mode_state_deserialization_from_claude_format() {
        // Test deserialization of actual Claude-ACP format
        let json = r#"{
            "currentModeId": "default",
            "availableModes": [
                {
                    "id": "default",
                    "name": "Always Ask",
                    "description": "Prompts for permission on first use of each tool"
                },
                {
                    "id": "acceptEdits",
                    "name": "Accept Edits",
                    "description": "Automatically accepts file edit permissions for the session"
                },
                {
                    "id": "plan",
                    "name": "Plan Mode",
                    "description": "Claude can analyze but not modify files or execute commands"
                },
                {
                    "id": "bypassPermissions",
                    "name": "Bypass Permissions",
                    "description": "Skips all permission prompts"
                }
            ]
        }"#;

        let mode_state: SessionModeState = serde_json::from_str(json).unwrap();
        assert_eq!(mode_state.current_mode_id, "default");
        assert_eq!(mode_state.available_modes.len(), 4);
        assert_eq!(mode_state.available_modes[0].id, "default");
        assert_eq!(mode_state.available_modes[0].name, "Always Ask");
        assert_eq!(mode_state.available_modes[3].id, "bypassPermissions");
    }

    #[test]
    fn test_session_mode_state_roundtrip() {
        let original = SessionModeState {
            current_mode_id: "plan".to_string(),
            available_modes: vec![
                SessionMode {
                    id: "default".to_string(),
                    name: "Default".to_string(),
                    description: None,
                },
                SessionMode {
                    id: "plan".to_string(),
                    name: "Plan".to_string(),
                    description: Some("Planning mode".to_string()),
                },
            ],
        };

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: SessionModeState = serde_json::from_str(&json).unwrap();
        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_session_mode_skip_none_description() {
        // Test that None descriptions are not serialized
        let mode = SessionMode {
            id: "test".to_string(),
            name: "Test".to_string(),
            description: None,
        };

        let json = serde_json::to_string(&mode).unwrap();
        assert!(!json.contains("description"));
    }

    #[test]
    fn test_session_model_state_serialization_camel_case() {
        // Verify camelCase serialization matches Claude-ACP format
        let model_state = SessionModelState {
            available_models: vec![
                ModelInfo {
                    model_id: "default".to_string(),
                    name: "Default (recommended)".to_string(),
                    description: Some("Opus 4.5 · Most capable for complex work".to_string()),
                },
                ModelInfo {
                    model_id: "sonnet".to_string(),
                    name: "Sonnet".to_string(),
                    description: Some("Sonnet 4.5 · Best for everyday tasks".to_string()),
                },
            ],
            current_model_id: "default".to_string(),
        };

        let json = serde_json::to_string(&model_state).unwrap();
        // Verify camelCase field names
        assert!(json.contains("availableModels"));
        assert!(json.contains("currentModelId"));
        assert!(json.contains("modelId"));
        // Verify content
        assert!(json.contains(r#""currentModelId":"default"#));
        assert!(json.contains(r#""name":"Sonnet"#));
    }

    #[test]
    fn test_session_model_state_deserialization_from_claude_format() {
        // Test deserialization of actual Claude-ACP format (from zed_claude_code_direct_acp_log.txt)
        let json = r#"{
            "availableModels": [
                {
                    "modelId": "default",
                    "name": "Default (recommended)",
                    "description": "Opus 4.5 · Most capable for complex work"
                },
                {
                    "modelId": "sonnet",
                    "name": "Sonnet",
                    "description": "Sonnet 4.5 · Best for everyday tasks"
                },
                {
                    "modelId": "haiku",
                    "name": "Haiku",
                    "description": "Haiku 4.5 · Fastest for quick answers"
                },
                {
                    "modelId": "opus",
                    "name": "opus",
                    "description": "Custom model"
                }
            ],
            "currentModelId": "default"
        }"#;

        let model_state: SessionModelState = serde_json::from_str(json).unwrap();
        assert_eq!(model_state.current_model_id, "default");
        assert_eq!(model_state.available_models.len(), 4);
        assert_eq!(model_state.available_models[0].model_id, "default");
        assert_eq!(
            model_state.available_models[0].name,
            "Default (recommended)"
        );
        assert_eq!(model_state.available_models[2].model_id, "haiku");
        assert_eq!(model_state.available_models[3].model_id, "opus");
    }

    #[test]
    fn test_session_model_state_roundtrip() {
        let original = SessionModelState {
            available_models: vec![ModelInfo {
                model_id: "default".to_string(),
                name: "Default".to_string(),
                description: None,
            }],
            current_model_id: "default".to_string(),
        };

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: SessionModelState = serde_json::from_str(&json).unwrap();
        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_model_info_skip_none_description() {
        // Test that None descriptions are not serialized
        let model = ModelInfo {
            model_id: "test".to_string(),
            name: "Test".to_string(),
            description: None,
        };

        let json = serde_json::to_string(&model).unwrap();
        assert!(!json.contains("description"));
    }

    // ========================================================================
    // SessionMetadata Tests (existing)
    // ========================================================================

    #[test]
    fn test_session_metadata_backward_compatibility() {
        // Test that existing SessionMetadata without new fields can be deserialized
        let json = r#"{
            "project_path": "/test/path",
            "model": "gpt-4",
            "total_messages": 10,
            "system_message": "System prompt"
        }"#;

        let metadata: SessionMetadata = serde_json::from_str(json).unwrap();
        assert_eq!(metadata.project_path, "/test/path");
        assert_eq!(metadata.model, Some("gpt-4".to_string()));
        assert_eq!(metadata.total_messages, 10);
        assert_eq!(metadata.system_message, Some("System prompt".to_string()));
        assert_eq!(metadata.current_mode_id, None);
        assert_eq!(metadata._meta, None);
    }

    #[test]
    fn test_session_metadata_skip_serializing_none() {
        // Test that None values are skipped during serialization
        let metadata = SessionMetadata {
            project_path: "/test".to_string(),
            model: Some("gpt-4".to_string()),
            total_messages: 0,
            system_message: None,
            current_mode_id: None,
            _meta: None,
            project_id: None,
        };

        let json = serde_json::to_string(&metadata).unwrap();
        // Should not contain system_message, current_mode_id, or _meta fields
        assert!(!json.contains("system_message"));
        assert!(!json.contains("current_mode_id"));
        assert!(!json.contains("_meta"));
        // Should contain the present fields
        assert!(json.contains("project_path"));
        assert!(json.contains("model"));
        assert!(json.contains("total_messages"));
    }

    #[test]
    fn test_session_metadata_with_current_mode_id() {
        // Test serialization/deserialization with current_mode_id
        let metadata = SessionMetadata {
            project_path: "/test".to_string(),
            model: Some("gpt-4".to_string()),
            total_messages: 5,
            system_message: None,
            current_mode_id: Some("code_mode".to_string()),
            _meta: None,
            project_id: None,
        };

        let json = serde_json::to_string(&metadata).unwrap();
        assert!(json.contains("current_mode_id"));
        assert!(json.contains("code_mode"));

        let deserialized: SessionMetadata = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.current_mode_id, Some("code_mode".to_string()));
    }

    #[test]
    fn test_session_metadata_with_meta() {
        // Test serialization/deserialization with provider metadata
        let meta = Meta {
            provider: Some(ProviderMeta {
                name: "opencode".to_string(),
                original_ids: Some(HashMap::from([(
                    "session_id".to_string(),
                    "ses_abc123".to_string(),
                )])),
                raw_excerpt: None,
            }),
            extra: HashMap::new(),
        };

        let metadata = SessionMetadata {
            project_path: "/test".to_string(),
            model: Some("gpt-4".to_string()),
            total_messages: 5,
            system_message: None,
            current_mode_id: None,
            _meta: Some(meta),
            project_id: None,
        };

        let json = serde_json::to_string(&metadata).unwrap();
        assert!(json.contains("_meta"));
        assert!(json.contains("opencode"));
        assert!(json.contains("ses_abc123"));

        let deserialized: SessionMetadata = serde_json::from_str(&json).unwrap();
        assert!(deserialized._meta.is_some());
        let deserialized_meta = deserialized._meta.unwrap();
        assert!(deserialized_meta.provider.is_some());
        assert_eq!(deserialized_meta.provider.unwrap().name, "opencode");
    }

    #[test]
    fn test_session_metadata_with_all_fields() {
        // Test with all fields populated
        let meta = Meta {
            provider: Some(ProviderMeta {
                name: "anthropic".to_string(),
                original_ids: Some(HashMap::from([(
                    "conversation_id".to_string(),
                    "conv_xyz".to_string(),
                )])),
                raw_excerpt: Some(serde_json::json!({"version": "1.0"})),
            }),
            extra: HashMap::new(),
        };

        let metadata = SessionMetadata {
            project_path: "/project".to_string(),
            model: Some("claude-3".to_string()),
            total_messages: 42,
            system_message: Some("Be helpful".to_string()),
            current_mode_id: Some("architect".to_string()),
            _meta: Some(meta),
            project_id: None,
        };

        let json = serde_json::to_string(&metadata).unwrap();
        let deserialized: SessionMetadata = serde_json::from_str(&json).unwrap();

        assert_eq!(metadata, deserialized);
        assert!(json.contains("system_message"));
        assert!(json.contains("current_mode_id"));
        assert!(json.contains("_meta"));
    }

    #[test]
    fn test_session_roundtrip_with_new_fields() {
        // Test that a Session with new metadata fields survives roundtrip
        let now = Utc::now();
        let session = Session {
            id: "ses_test123".to_string(),
            title: "Test Session".to_string(),
            created_at: now,
            updated_at: now,
            metadata: SessionMetadata {
                project_path: "/workspace".to_string(),
                model: Some("gpt-4-turbo".to_string()),
                total_messages: 7,
                system_message: Some("You are a coding assistant".to_string()),
                current_mode_id: Some("debug_mode".to_string()),
                _meta: Some(Meta {
                    provider: Some(ProviderMeta {
                        name: "test_provider".to_string(),
                        original_ids: None,
                        raw_excerpt: None,
                    }),
                    extra: HashMap::new(),
                }),
                project_id: None,
            },
            cwd: None,
            models: None,
            modes: None,
            config_options: None,
            acp_client_id: None,
        };

        let json = serde_json::to_string(&session).unwrap();
        let deserialized: Session = serde_json::from_str(&json).unwrap();

        assert_eq!(session, deserialized);
    }

    #[test]
    fn test_session_with_models_and_modes() {
        // Test Session with models and modes populated
        let now = Utc::now();
        let session = Session {
            id: "ses_test456".to_string(),
            title: "Test Session with ACP".to_string(),
            created_at: now,
            updated_at: now,
            metadata: SessionMetadata {
                project_path: "/workspace".to_string(),
                model: Some("default".to_string()),
                total_messages: 5,
                system_message: None,
                current_mode_id: Some("default".to_string()),
                _meta: None,
                project_id: None,
            },
            cwd: None,
            models: Some(SessionModelState {
                available_models: vec![
                    ModelInfo {
                        model_id: "default".to_string(),
                        name: "Default".to_string(),
                        description: Some("Default model".to_string()),
                    },
                    ModelInfo {
                        model_id: "sonnet".to_string(),
                        name: "Sonnet".to_string(),
                        description: None,
                    },
                ],
                current_model_id: "default".to_string(),
            }),
            modes: Some(SessionModeState {
                current_mode_id: "default".to_string(),
                available_modes: vec![SessionMode {
                    id: "default".to_string(),
                    name: "Always Ask".to_string(),
                    description: None,
                }],
            }),
            config_options: None,
            acp_client_id: Some("test-client-123".to_string()),
        };

        let json = serde_json::to_string(&session).unwrap();
        assert!(json.contains("models"));
        assert!(json.contains("modes"));
        assert!(json.contains("acp_client_id"));
        assert!(json.contains("availableModels"));
        assert!(json.contains("currentModeId"));

        let deserialized: Session = serde_json::from_str(&json).unwrap();
        assert_eq!(session, deserialized);
    }

    #[test]
    fn test_session_backward_compatibility_no_models_modes() {
        // Test that old Session JSON without models/modes can be deserialized
        let json = r#"{
            "id": "ses_old",
            "title": "Old Session",
            "created_at": "2024-01-01T00:00:00Z",
            "updated_at": "2024-01-01T00:00:00Z",
            "metadata": {
                "project_path": "/test",
                "model": "gpt-4",
                "total_messages": 10
            }
        }"#;

        let session: Session = serde_json::from_str(json).unwrap();
        assert_eq!(session.id, "ses_old");
        assert!(session.models.is_none());
        assert!(session.modes.is_none());
    }

    // ========================================================================
    // Session Ownership Model Tests
    // ========================================================================

    #[test]
    fn test_session_origin_default() {
        // Verify Internal is the default
        let origin = SessionOrigin::default();
        assert_eq!(origin, SessionOrigin::Internal);
    }

    #[test]
    fn test_session_origin_serialization() {
        // Test Internal variant
        let internal = SessionOrigin::Internal;
        let json = serde_json::to_string(&internal).unwrap();
        assert!(json.contains(r#""type":"internal"#));

        // Test External variant
        let external = SessionOrigin::External {
            client_id: "client-123".to_string(),
            client_capabilities: Some(serde_json::json!({"tools": ["bash"]})),
        };
        let json = serde_json::to_string(&external).unwrap();
        assert!(json.contains(r#""type":"external"#));
        assert!(json.contains(r#""client_id":"client-123"#));
        assert!(json.contains("tools"));

        // Test Subagent variant
        let subagent = SessionOrigin::Subagent {
            parent_session_id: "parent-456".to_string(),
            task_id: "task-789".to_string(),
        };
        let json = serde_json::to_string(&subagent).unwrap();
        assert!(json.contains(r#""type":"subagent"#));
        assert!(json.contains(r#""parent_session_id":"parent-456"#));
        assert!(json.contains(r#""task_id":"task-789"#));
    }

    #[test]
    fn test_tool_handler_default() {
        // Verify Agent is the default
        let handler = ToolHandler::default();
        assert_eq!(handler, ToolHandler::Agent);
    }

    #[test]
    fn test_tool_handler_serialization() {
        let agent = ToolHandler::Agent;
        let json = serde_json::to_string(&agent).unwrap();
        assert_eq!(json, r#""agent""#);

        let dirigent = ToolHandler::Dirigent;
        let json = serde_json::to_string(&dirigent).unwrap();
        assert_eq!(json, r#""dirigent""#);

        let forward = ToolHandler::ForwardToClient;
        let json = serde_json::to_string(&forward).unwrap();
        assert_eq!(json, r#""forward_to_client""#);
    }

    #[test]
    fn test_session_ownership_internal() {
        let ownership = SessionOwnership::internal();
        assert_eq!(ownership.origin, SessionOrigin::Internal);
        assert_eq!(ownership.tool_handler, ToolHandler::Agent);
        assert!(!ownership.is_external());
        assert_eq!(ownership.client_id(), None);
        assert_eq!(ownership.forward_to_client(), None);
    }

    #[test]
    fn test_session_ownership_external_forwarded() {
        let caps = serde_json::json!({"tools": ["bash", "edit"]});
        let ownership =
            SessionOwnership::external_forwarded("client-123".to_string(), Some(caps.clone()));

        match &ownership.origin {
            SessionOrigin::External {
                client_id,
                client_capabilities,
            } => {
                assert_eq!(client_id, "client-123");
                assert_eq!(client_capabilities.as_ref().unwrap(), &caps);
            }
            _ => panic!("Expected External origin"),
        }

        assert_eq!(ownership.tool_handler, ToolHandler::ForwardToClient);
        assert!(ownership.is_external());
        assert_eq!(ownership.client_id(), Some("client-123"));
        assert_eq!(ownership.forward_to_client(), Some("client-123"));
    }

    #[test]
    fn test_session_ownership_external_handled() {
        let ownership = SessionOwnership::external_handled("client-456".to_string(), None);

        match &ownership.origin {
            SessionOrigin::External {
                client_id,
                client_capabilities,
            } => {
                assert_eq!(client_id, "client-456");
                assert!(client_capabilities.is_none());
            }
            _ => panic!("Expected External origin"),
        }

        assert_eq!(ownership.tool_handler, ToolHandler::Dirigent);
        assert!(ownership.is_external());
        assert_eq!(ownership.client_id(), Some("client-456"));
        assert_eq!(ownership.forward_to_client(), None); // Dirigent handles, not forwarded
    }

    #[test]
    fn test_capabilities_for_agent_external_forwarded() {
        let client_caps = serde_json::json!({"fs": true, "terminal": true});
        let ownership = SessionOwnership::external_forwarded(
            "client-123".to_string(),
            Some(client_caps.clone()),
        );

        let caps = ownership.capabilities_for_agent();
        assert_eq!(caps, client_caps);
    }

    #[test]
    fn test_capabilities_for_agent_dirigent() {
        let ownership = SessionOwnership {
            origin: SessionOrigin::Internal,
            tool_handler: ToolHandler::Dirigent,
        };

        let caps = ownership.capabilities_for_agent();
        assert!(caps.is_object());
        assert!(caps.get("fs").is_some());
        assert!(caps.get("terminal").is_some());
    }

    #[test]
    fn test_capabilities_for_agent_agent_handled() {
        let ownership = SessionOwnership::internal();
        let caps = ownership.capabilities_for_agent();
        assert_eq!(caps, serde_json::json!({}));
    }

    #[test]
    fn test_session_ownership_serialization_roundtrip() {
        let original = SessionOwnership::external_forwarded(
            "test-client".to_string(),
            Some(serde_json::json!({"test": true})),
        );

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: SessionOwnership = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.tool_handler, original.tool_handler);
        assert_eq!(deserialized.client_id(), Some("test-client"));
    }

    #[test]
    fn test_session_ownership_default() {
        let ownership = SessionOwnership::default();
        assert_eq!(ownership.origin, SessionOrigin::Internal);
        assert_eq!(ownership.tool_handler, ToolHandler::Agent);
    }
}
