//! Chat Session Manager - 多轮对话历史管理
//!
//! 提供内存中的会话管理实现，支持：
//! - 创建/获取/删除会话
//! - 追加消息到会话历史
//! - 自动过期清理（TTL）
//! - 线程安全（基于 DashMap）

use std::sync::Arc;
use std::time::{Duration, Instant};

use dashmap::DashMap;

use super::error::{RagError, RagResult};
use super::types::{ChatMessage, ChatSession};

// ============================================================================
// InMemorySessionManager
// ============================================================================

/// Thread-safe in-memory session manager using DashMap.
///
/// Sessions are stored in memory with optional TTL-based expiration.
/// Suitable for single-server deployments.
///
/// # Example
///
/// ```ignore
/// use aifastdb_core::rag::{InMemorySessionManager, ChatMessage};
///
/// let manager = InMemorySessionManager::new(Duration::from_secs(3600));
///
/// // Create a session
/// let session = manager.create_session()?;
/// let session_id = session.session_id.clone();
///
/// // Add messages
/// manager.push_message(&session_id, ChatMessage::user("Hello!"))?;
/// manager.push_message(&session_id, ChatMessage::assistant("Hi there!"))?;
///
/// // Get history
/// let history = manager.get_history(&session_id)?;
/// ```
pub struct InMemorySessionManager {
    sessions: Arc<DashMap<String, SessionEntry>>,
    /// Time-to-live for sessions (from last access)
    ttl: Duration,
    /// Maximum number of sessions
    max_sessions: usize,
}

struct SessionEntry {
    session: ChatSession,
    last_accessed: Instant,
}

impl InMemorySessionManager {
    /// Create a new session manager with a TTL.
    pub fn new(ttl: Duration) -> Self {
        Self {
            sessions: Arc::new(DashMap::new()),
            ttl,
            max_sessions: 10_000,
        }
    }

    /// Set the maximum number of concurrent sessions.
    pub fn with_max_sessions(mut self, max: usize) -> Self {
        self.max_sessions = max;
        self
    }

    /// Create a new chat session with a random ID.
    pub fn create_session(&self) -> RagResult<ChatSession> {
        self.cleanup_expired();

        if self.sessions.len() >= self.max_sessions {
            return Err(RagError::Session(format!(
                "Maximum sessions ({}) reached",
                self.max_sessions
            )));
        }

        let session = ChatSession::new(uuid::Uuid::new_v4().to_string());
        let entry = SessionEntry {
            session: session.clone(),
            last_accessed: Instant::now(),
        };
        self.sessions.insert(session.session_id.clone(), entry);
        Ok(session)
    }

    /// Create a session with a specific ID.
    pub fn create_session_with_id(&self, session_id: impl Into<String>) -> RagResult<ChatSession> {
        self.cleanup_expired();

        let id = session_id.into();
        if self.sessions.contains_key(&id) {
            return Err(RagError::Session(format!(
                "Session '{}' already exists",
                id
            )));
        }

        if self.sessions.len() >= self.max_sessions {
            return Err(RagError::Session(format!(
                "Maximum sessions ({}) reached",
                self.max_sessions
            )));
        }

        let session = ChatSession::new(&id);
        let entry = SessionEntry {
            session: session.clone(),
            last_accessed: Instant::now(),
        };
        self.sessions.insert(id, entry);
        Ok(session)
    }

    /// Get a session by ID (refreshes TTL).
    pub fn get_session(&self, session_id: &str) -> RagResult<ChatSession> {
        match self.sessions.get_mut(session_id) {
            Some(mut entry) => {
                entry.last_accessed = Instant::now();
                Ok(entry.session.clone())
            }
            None => Err(RagError::Session(format!(
                "Session '{}' not found",
                session_id
            ))),
        }
    }

    /// Get or create a session (idempotent).
    pub fn get_or_create(&self, session_id: &str) -> RagResult<ChatSession> {
        if let Some(mut entry) = self.sessions.get_mut(session_id) {
            entry.last_accessed = Instant::now();
            return Ok(entry.session.clone());
        }

        self.create_session_with_id(session_id)
    }

    /// Push a message to a session's history.
    pub fn push_message(&self, session_id: &str, message: ChatMessage) -> RagResult<()> {
        match self.sessions.get_mut(session_id) {
            Some(mut entry) => {
                entry.last_accessed = Instant::now();
                entry.session.push(message);
                Ok(())
            }
            None => Err(RagError::Session(format!(
                "Session '{}' not found",
                session_id
            ))),
        }
    }

    /// Get conversation history for a session.
    pub fn get_history(&self, session_id: &str) -> RagResult<Vec<ChatMessage>> {
        match self.sessions.get(session_id) {
            Some(entry) => Ok(entry.session.messages.clone()),
            None => Err(RagError::Session(format!(
                "Session '{}' not found",
                session_id
            ))),
        }
    }

    /// Get recent turns (user + assistant pairs) from a session.
    pub fn get_recent_history(
        &self,
        session_id: &str,
        max_turns: usize,
    ) -> RagResult<Vec<ChatMessage>> {
        match self.sessions.get(session_id) {
            Some(entry) => {
                let recent = entry.session.recent_turns(max_turns);
                Ok(recent.into_iter().cloned().collect())
            }
            None => Err(RagError::Session(format!(
                "Session '{}' not found",
                session_id
            ))),
        }
    }

    /// Delete a session.
    pub fn delete_session(&self, session_id: &str) -> RagResult<()> {
        self.sessions
            .remove(session_id)
            .map(|_| ())
            .ok_or_else(|| RagError::Session(format!("Session '{}' not found", session_id)))
    }

    /// Get the number of active sessions.
    pub fn session_count(&self) -> usize {
        self.sessions.len()
    }

    /// Remove expired sessions.
    pub fn cleanup_expired(&self) {
        let now = Instant::now();
        self.sessions
            .retain(|_, entry| now.duration_since(entry.last_accessed) < self.ttl);
    }

    /// List all active session IDs.
    pub fn list_sessions(&self) -> Vec<String> {
        self.sessions
            .iter()
            .map(|entry| entry.key().clone())
            .collect()
    }
}

impl Default for InMemorySessionManager {
    fn default() -> Self {
        Self::new(Duration::from_secs(3600)) // 1 hour TTL
    }
}

// Phase-82 T82.4: Implement SessionManager trait for InMemorySessionManager
impl super::traits::SessionManager for InMemorySessionManager {
    fn get_or_create(&self, session_id: &str) -> super::error::RagResult<ChatSession> {
        self.get_or_create(session_id)
    }

    fn push_message(
        &self,
        session_id: &str,
        message: ChatMessage,
    ) -> super::error::RagResult<()> {
        self.push_message(session_id, message)
    }

    fn get_recent_history(
        &self,
        session_id: &str,
        max_turns: usize,
    ) -> super::error::RagResult<Vec<ChatMessage>> {
        self.get_recent_history(session_id, max_turns)
    }

    fn get_history(
        &self,
        session_id: &str,
    ) -> super::error::RagResult<Vec<ChatMessage>> {
        self.get_history(session_id)
    }

    fn delete_session(&self, session_id: &str) -> super::error::RagResult<()> {
        self.delete_session(session_id)
    }

    fn session_count(&self) -> usize {
        self.session_count()
    }
}

// ============================================================================
// ConversationSessionAdapter (Phase-82: T82.4)
// ============================================================================

/// Adapter that bridges `ConversationManager` into the RAG `SessionManager` trait.
///
/// When used as the session backend for `RagPipeline`, all conversation history
/// is persisted through LLM Gateway's `ConversationManager`, gaining:
///
/// - **Persistence**: Messages survive server restarts (JSON file storage)
/// - **Ownership**: Conversations respect `owner_id` based access control
/// - **Unified tracking**: Token usage & cost tracked centrally
/// - **Model switch**: Conversation can switch models mid-chat
///
/// # Requires
///
/// - `llm-gateway` feature enabled
///
/// # Example
///
/// ```ignore
/// use aifastdb_core::rag::session::ConversationSessionAdapter;
/// use aifastdb_core::rag::RagPipeline;
///
/// let adapter = ConversationSessionAdapter::new(conv_manager.clone(), "user-1");
/// let pipeline = RagPipeline::builder()
///     .retriever(my_retriever)
///     .generator(my_generator)
///     .session_manager(Arc::new(adapter))
///     .build()?;
/// ```
#[cfg(feature = "llm-gateway")]
pub struct ConversationSessionAdapter {
    /// Reference to the ConversationManager.
    conv_manager: std::sync::Arc<crate::llm::conversation_manager::ConversationManager>,
    /// Default owner ID for new conversations created by this adapter.
    owner_id: String,
    /// Default provider/model info for new conversations.
    provider_id: String,
    model_id: String,
    model_name: String,
}

#[cfg(feature = "llm-gateway")]
impl ConversationSessionAdapter {
    /// Create a new adapter.
    ///
    /// # Arguments
    /// * `conv_manager` - Shared `ConversationManager` reference
    /// * `owner_id` - Default owner for conversations created via `get_or_create`
    pub fn new(
        conv_manager: std::sync::Arc<crate::llm::conversation_manager::ConversationManager>,
        owner_id: impl Into<String>,
    ) -> Self {
        Self {
            conv_manager,
            owner_id: owner_id.into(),
            provider_id: "rag".to_string(),
            model_id: "rag-pipeline".to_string(),
            model_name: "RAG Pipeline".to_string(),
        }
    }

    /// Set the model info used when creating new conversations.
    pub fn with_model(
        mut self,
        provider_id: impl Into<String>,
        model_id: impl Into<String>,
        model_name: impl Into<String>,
    ) -> Self {
        self.provider_id = provider_id.into();
        self.model_id = model_id.into();
        self.model_name = model_name.into();
        self
    }

    /// Convert LLM Gateway `StoredMessage` history to RAG `ChatMessage` list.
    fn convert_messages(
        msgs: &[crate::llm::conversation::StoredMessage],
    ) -> Vec<ChatMessage> {
        use crate::llm::client::MessageRole;
        msgs.iter()
            .filter_map(|m| {
                let role = match m.role {
                    MessageRole::User => super::types::ChatRole::User,
                    MessageRole::Assistant => super::types::ChatRole::Assistant,
                    MessageRole::System => super::types::ChatRole::System,
                    _ => return None, // Skip Tool messages
                };
                Some(ChatMessage {
                    role,
                    content: m.content.clone(),
                    timestamp: Some(m.created_at),
                })
            })
            .collect()
    }
}

#[cfg(feature = "llm-gateway")]
impl super::traits::SessionManager for ConversationSessionAdapter {
    fn get_or_create(
        &self,
        session_id: &str,
    ) -> super::error::RagResult<super::types::ChatSession> {
        // Try to get existing conversation
        match self.conv_manager.get_conversation(session_id, &self.owner_id) {
            Ok(conv) => {
                // Build a ChatSession from the conversation
                let query = crate::llm::conversation::MessageQuery::new(session_id);
                let msgs = self
                    .conv_manager
                    .get_messages(&query)
                    .map_err(|e| super::error::RagError::Session(format!("{e}")))?;

                let chat_msgs = Self::convert_messages(&msgs);
                let mut session = super::types::ChatSession::new(session_id);
                session.messages = chat_msgs;
                session.created_at = conv.created_at;
                session.updated_at = conv.updated_at;
                Ok(session)
            }
            Err(_) => {
                // Create a new conversation
                let conv = self
                    .conv_manager
                    .create_conversation(
                        &self.owner_id,
                        &self.provider_id,
                        &self.model_id,
                        &self.model_name,
                    )
                    .map_err(|e| super::error::RagError::Session(format!("{e}")))?;

                // Also create a mapping from session_id to conv_id if they differ
                // For simplicity, we use session_id as conv_id when possible
                let session = super::types::ChatSession::new(&conv.id);
                Ok(session)
            }
        }
    }

    fn push_message(
        &self,
        session_id: &str,
        message: ChatMessage,
    ) -> super::error::RagResult<()> {
        use crate::llm::client::MessageRole;

        let role = match message.role {
            super::types::ChatRole::User => MessageRole::User,
            super::types::ChatRole::Assistant => MessageRole::Assistant,
            super::types::ChatRole::System => MessageRole::System,
        };

        let stored = crate::llm::conversation::StoredMessage {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: session_id.to_string(),
            role,
            content: message.content,
            content_parts: Vec::new(),
            thinking: None,
            tool_calls: Vec::new(),
            tool_call_id: None,
            name: None,
            attachments: Vec::new(),
            token_usage: None,
            duration_ms: None,
            model_id: None,
            status: crate::llm::conversation::MessageStatus::Sent,
            created_at: message.timestamp.unwrap_or_else(|| {
                std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_millis() as i64
            }),
        };

        self.conv_manager
            .add_message(stored)
            .map(|_| ())
            .map_err(|e| super::error::RagError::Session(format!("{e}")))
    }

    fn get_recent_history(
        &self,
        session_id: &str,
        max_turns: usize,
    ) -> super::error::RagResult<Vec<ChatMessage>> {
        let query = crate::llm::conversation::MessageQuery::new(session_id);
        let msgs = self
            .conv_manager
            .get_messages(&query)
            .map_err(|e| super::error::RagError::Session(format!("{e}")))?;

        let chat_msgs = Self::convert_messages(&msgs);

        // Apply turn-based windowing (same logic as ChatSession::recent_turns)
        let non_system: Vec<ChatMessage> = chat_msgs
            .into_iter()
            .filter(|m| m.role != super::types::ChatRole::System)
            .collect();

        let keep = max_turns * 2; // Each turn = user + assistant = 2 messages
        if non_system.len() <= keep {
            Ok(non_system)
        } else {
            Ok(non_system[non_system.len() - keep..].to_vec())
        }
    }

    fn get_history(
        &self,
        session_id: &str,
    ) -> super::error::RagResult<Vec<ChatMessage>> {
        let query = crate::llm::conversation::MessageQuery::new(session_id);
        let msgs = self
            .conv_manager
            .get_messages(&query)
            .map_err(|e| super::error::RagError::Session(format!("{e}")))?;

        Ok(Self::convert_messages(&msgs))
    }

    fn delete_session(&self, session_id: &str) -> super::error::RagResult<()> {
        self.conv_manager
            .delete_conversation(session_id, &self.owner_id)
            .map(|_| ())
            .map_err(|e| super::error::RagError::Session(format!("{e}")))
    }

    fn session_count(&self) -> usize {
        self.conv_manager.conversation_count()
    }
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_create_and_get_session() {
        let mgr = InMemorySessionManager::default();
        let session = mgr.create_session().unwrap();
        assert!(!session.session_id.is_empty());
        assert!(session.messages.is_empty());

        let retrieved = mgr.get_session(&session.session_id).unwrap();
        assert_eq!(retrieved.session_id, session.session_id);
    }

    #[test]
    fn test_create_with_id() {
        let mgr = InMemorySessionManager::default();
        let session = mgr.create_session_with_id("my-session").unwrap();
        assert_eq!(session.session_id, "my-session");

        // Duplicate should fail
        let result = mgr.create_session_with_id("my-session");
        assert!(result.is_err());
    }

    #[test]
    fn test_get_or_create() {
        let mgr = InMemorySessionManager::default();

        // First call creates
        let s1 = mgr.get_or_create("test-1").unwrap();
        assert_eq!(s1.session_id, "test-1");
        assert_eq!(mgr.session_count(), 1);

        // Second call retrieves
        let s2 = mgr.get_or_create("test-1").unwrap();
        assert_eq!(s2.session_id, "test-1");
        assert_eq!(mgr.session_count(), 1);
    }

    #[test]
    fn test_push_and_get_history() {
        let mgr = InMemorySessionManager::default();
        let _session = mgr.create_session_with_id("chat-1").unwrap();

        mgr.push_message("chat-1", ChatMessage::user("Hello")).unwrap();
        mgr.push_message("chat-1", ChatMessage::assistant("Hi!")).unwrap();
        mgr.push_message("chat-1", ChatMessage::user("How are you?")).unwrap();

        let history = mgr.get_history("chat-1").unwrap();
        assert_eq!(history.len(), 3);
        assert_eq!(history[0].role, ChatRole::User);
        assert_eq!(history[0].content, "Hello");
        assert_eq!(history[1].role, ChatRole::Assistant);
        assert_eq!(history[2].content, "How are you?");
    }

    #[test]
    fn test_get_recent_history() {
        let mgr = InMemorySessionManager::default();
        mgr.create_session_with_id("chat-2").unwrap();

        // Add 3 turns (6 messages)
        for i in 1..=3 {
            mgr.push_message("chat-2", ChatMessage::user(format!("Q{}", i))).unwrap();
            mgr.push_message("chat-2", ChatMessage::assistant(format!("A{}", i))).unwrap();
        }

        // Get last 2 turns
        let recent = mgr.get_recent_history("chat-2", 2).unwrap();
        assert_eq!(recent.len(), 4); // 2 turns * 2 messages
        assert_eq!(recent[0].content, "Q2");
        assert_eq!(recent[3].content, "A3");
    }

    #[test]
    fn test_delete_session() {
        let mgr = InMemorySessionManager::default();
        mgr.create_session_with_id("to-delete").unwrap();
        assert_eq!(mgr.session_count(), 1);

        mgr.delete_session("to-delete").unwrap();
        assert_eq!(mgr.session_count(), 0);

        // Should fail now
        let result = mgr.get_session("to-delete");
        assert!(result.is_err());
    }

    #[test]
    fn test_session_not_found() {
        let mgr = InMemorySessionManager::default();
        assert!(mgr.get_session("nonexistent").is_err());
        assert!(mgr.push_message("nonexistent", ChatMessage::user("hello")).is_err());
        assert!(mgr.get_history("nonexistent").is_err());
        assert!(mgr.delete_session("nonexistent").is_err());
    }

    #[test]
    fn test_max_sessions_limit() {
        let mgr = InMemorySessionManager::new(Duration::from_secs(3600))
            .with_max_sessions(3);

        mgr.create_session().unwrap();
        mgr.create_session().unwrap();
        mgr.create_session().unwrap();

        // 4th should fail
        let result = mgr.create_session();
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Maximum sessions"));
    }

    #[test]
    fn test_cleanup_expired() {
        let mgr = InMemorySessionManager::new(Duration::from_millis(50));

        mgr.create_session_with_id("will-expire").unwrap();
        assert_eq!(mgr.session_count(), 1);

        // Wait for TTL to expire
        std::thread::sleep(Duration::from_millis(100));

        mgr.cleanup_expired();
        assert_eq!(mgr.session_count(), 0);
    }

    #[test]
    fn test_list_sessions() {
        let mgr = InMemorySessionManager::default();
        mgr.create_session_with_id("a").unwrap();
        mgr.create_session_with_id("b").unwrap();
        mgr.create_session_with_id("c").unwrap();

        let mut ids = mgr.list_sessions();
        ids.sort();
        assert_eq!(ids, vec!["a", "b", "c"]);
    }
}
