//! Multi-turn conversation support for RAG system
//!
//! Provides stateful Q&A sessions with:
//! - Turn history and context tracking
//! - Follow-up question detection
//! - Document scope management
//! - Conversation summarization
//! - Context-aware retrieval

use std::collections::VecDeque;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tracing::info;

use crate::agent::RagAgent;
use crate::error::Result;

/// A single turn in a conversation (question + answer)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationTurn {
    /// Turn number (0-indexed)
    pub turn_number: usize,

    /// User's question
    pub question: String,

    /// Agent's answer
    pub answer: String,

    /// Source documents used
    pub sources: Vec<String>,

    /// Confidence score for this answer
    pub confidence: f32,

    /// When this turn occurred
    pub timestamp: DateTime<Utc>,

    /// Whether this turn was a follow-up to previous questions
    pub is_followup: bool,

    /// References to previous turn numbers
    pub references_turns: Vec<usize>,
}

impl ConversationTurn {
    /// Create a new conversation turn
    pub fn new(
        turn_number: usize,
        question: String,
        answer: String,
        sources: Vec<String>,
        confidence: f32,
    ) -> Self {
        Self {
            turn_number,
            question,
            answer,
            sources,
            confidence,
            timestamp: Utc::now(),
            is_followup: false,
            references_turns: Vec::new(),
        }
    }

    /// Mark this turn as a follow-up
    pub fn mark_as_followup(mut self, references: Vec<usize>) -> Self {
        self.is_followup = true;
        self.references_turns = references;
        self
    }
}

/// Conversation context managing multiple turns
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationContext {
    /// Unique conversation ID
    pub conversation_id: String,

    /// History of turns (limited to max_history)
    turns: VecDeque<ConversationTurn>,

    /// Maximum turns to keep in memory
    max_history: usize,

    /// Current document scope (documents discussed so far)
    pub document_scope: Vec<String>,

    /// Conversation topic/title
    pub topic: Option<String>,

    /// When conversation started
    pub created_at: DateTime<Utc>,

    /// When conversation was last modified
    pub last_updated: DateTime<Utc>,

    /// Conversation metadata
    pub metadata: std::collections::HashMap<String, String>,
}

impl ConversationContext {
    /// Create a new conversation
    pub fn new(conversation_id: String) -> Self {
        Self {
            conversation_id,
            turns: VecDeque::new(),
            max_history: 20,
            document_scope: Vec::new(),
            topic: None,
            created_at: Utc::now(),
            last_updated: Utc::now(),
            metadata: std::collections::HashMap::new(),
        }
    }

    /// Set maximum history size
    pub fn with_max_history(mut self, max: usize) -> Self {
        self.max_history = max;
        self
    }

    /// Set conversation topic
    pub fn with_topic(mut self, topic: String) -> Self {
        self.topic = Some(topic);
        self
    }

    /// Add a turn to conversation history
    pub fn add_turn(&mut self, turn: ConversationTurn) {
        self.turns.push_back(turn);

        // Maintain max history size
        while self.turns.len() > self.max_history {
            self.turns.pop_front();
        }

        self.last_updated = Utc::now();
    }

    /// Get all turns in order
    pub fn get_turns(&self) -> Vec<ConversationTurn> {
        self.turns.iter().cloned().collect()
    }

    /// Get a specific turn by number
    pub fn get_turn(&self, turn_number: usize) -> Option<ConversationTurn> {
        self.turns
            .iter()
            .find(|t| t.turn_number == turn_number)
            .cloned()
    }

    /// Get the last N turns
    pub fn get_recent_turns(&self, n: usize) -> Vec<ConversationTurn> {
        self.turns
            .iter()
            .rev()
            .take(n)
            .cloned()
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .collect()
    }

    /// Build context string from conversation history
    pub fn build_context_string(&self) -> String {
        let mut context = String::new();

        if let Some(topic) = &self.topic {
            context.push_str(&format!("Topic: {}\n\n", topic));
        }

        for turn in &self.turns {
            context.push_str(&format!(
                "Turn {}: Q: {}\nA: {}\n\n",
                turn.turn_number + 1,
                turn.question,
                turn.answer
            ));
        }

        context
    }

    /// Update document scope
    pub fn update_document_scope(&mut self, new_docs: Vec<String>) {
        for doc in new_docs {
            if !self.document_scope.contains(&doc) {
                self.document_scope.push(doc);
            }
        }
    }

    /// Get conversation summary
    pub fn get_summary(&self) -> String {
        let turn_count = self.turns.len();
        let mut summary = format!("Conversation with {} turns\n", turn_count);

        if let Some(topic) = &self.topic {
            summary.push_str(&format!("Topic: {}\n", topic));
        }

        if !self.document_scope.is_empty() {
            summary.push_str(&format!("Documents: {} total\n", self.document_scope.len()));
        }

        let avg_confidence: f32 = if !self.turns.is_empty() {
            self.turns.iter().map(|t| t.confidence).sum::<f32>() / self.turns.len() as f32
        } else {
            0.0
        };

        summary.push_str(&format!("Average confidence: {:.2}\n", avg_confidence));
        summary
    }

    /// Clear conversation history
    pub fn clear_history(&mut self) {
        self.turns.clear();
        self.last_updated = Utc::now();
    }

    /// Get turn count
    pub fn turn_count(&self) -> usize {
        self.turns.len()
    }

    /// Check if conversation is empty
    pub fn is_empty(&self) -> bool {
        self.turns.is_empty()
    }
}

/// Detects follow-up questions
pub struct FollowupDetector;

impl FollowupDetector {
    /// Check if question is a follow-up to previous questions
    pub fn is_followup(question: &str, previous_turns: &[ConversationTurn]) -> bool {
        if previous_turns.is_empty() {
            return false;
        }

        // Keywords that indicate follow-ups
        let followup_keywords = [
            "what about",
            "and",
            "also",
            "furthermore",
            "moreover",
            "additionally",
            "tell me more",
            "explain",
            "how",
            "why",
            "can you",
            "could you",
            "would you",
            "that",
            "this",
            "same",
            "similar",
            "related",
            "difference",
            "example",
        ];

        let lower_question = question.to_lowercase();

        // Check for followup keywords
        for keyword in &followup_keywords {
            if lower_question.starts_with(keyword)
                || lower_question.contains(&format!(" {}", keyword))
            {
                return true;
            }
        }

        // Check for pronouns referring to previous context
        let context_pronouns = ["it", "that", "this", "they", "them", "those", "these"];
        for pronoun in &context_pronouns {
            if lower_question.starts_with(pronoun) {
                return true;
            }
        }

        false
    }

    /// Find which previous turns a follow-up references
    pub fn find_referenced_turns(
        question: &str,
        previous_turns: &[ConversationTurn],
    ) -> Vec<usize> {
        let mut referenced = Vec::new();
        let lower_question = question.to_lowercase();

        for turn in previous_turns {
            let lower_answer = turn.answer.to_lowercase();

            // Simple heuristic: check if key terms from previous answer appear in question
            let terms: Vec<&str> = lower_answer.split_whitespace().collect();
            let question_terms: Vec<&str> = lower_question.split_whitespace().collect();

            let mut match_count = 0;
            for term in terms.iter().skip(2).take(10) {
                // Skip first few words, check next 10 for keywords
                if term.len() > 4 && question_terms.contains(term) {
                    match_count += 1;
                }
            }

            if match_count > 0 {
                referenced.push(turn.turn_number);
            }
        }

        referenced
    }
}

/// Conversation-aware RAG agent
pub struct ConversationAgent {
    /// Base RAG agent
    agent: RagAgent,

    /// Active conversation context
    context: ConversationContext,
}

impl ConversationAgent {
    /// Create a new conversation agent
    pub fn new(agent: RagAgent, conversation_id: String) -> Self {
        Self {
            agent,
            context: ConversationContext::new(conversation_id),
        }
    }

    /// Set conversation topic
    pub fn with_topic(mut self, topic: String) -> Self {
        self.context = self.context.with_topic(topic);
        self
    }

    /// Ask a question with conversation context
    pub async fn ask(&mut self, question: &str) -> Result<ConversationTurn> {
        info!("Processing question in conversation context: {}", question);

        let turn_number = self.context.turn_count();

        // Build context from conversation history
        let history_context = self.context.build_context_string();

        // Check for follow-up
        let previous_turns = self.context.get_turns();
        let is_followup = FollowupDetector::is_followup(question, &previous_turns);
        let referenced_turns = FollowupDetector::find_referenced_turns(question, &previous_turns);

        // Create enriched query with context
        let enriched_question = if !history_context.is_empty() {
            format!(
                "{}\n\nPrevious context:\n{}\n\nCurrent question: {}",
                question, history_context, question
            )
        } else {
            question.to_string()
        };

        // Get answer from base agent
        let response = self.agent.ask(&enriched_question).await?;

        // Create conversation turn
        let mut turn = ConversationTurn::new(
            turn_number,
            question.to_string(),
            response.answer.clone(),
            response.sources.iter().map(|s| s.doc_id.clone()).collect(),
            response.confidence,
        );

        // Mark as follow-up if detected
        if is_followup {
            turn = turn.mark_as_followup(referenced_turns);
        }

        // Update document scope
        let doc_ids: Vec<String> = response.sources.iter().map(|s| s.doc_id.clone()).collect();
        self.context.update_document_scope(doc_ids);

        // Add to history
        self.context.add_turn(turn.clone());

        info!("Added turn {} to conversation", turn_number);

        Ok(turn)
    }

    /// Get conversation context
    pub fn context(&self) -> &ConversationContext {
        &self.context
    }

    /// Get conversation summary
    pub fn get_summary(&self) -> String {
        self.context.get_summary()
    }

    /// Get conversation transcript
    pub fn get_transcript(&self) -> String {
        let mut transcript = String::new();

        for turn in self.context.get_turns() {
            transcript.push_str(&format!(
                "Turn {}:\nQ: {}\nA: {}\n\n",
                turn.turn_number + 1,
                turn.question,
                turn.answer
            ));
        }

        transcript
    }

    /// Clear conversation history
    pub fn clear_history(&mut self) {
        self.context.clear_history();
    }

    /// Get recent conversation context (for display/context)
    pub fn get_recent_context(&self, turns: usize) -> String {
        let recent_turns = self.context.get_recent_turns(turns);
        let mut context = String::new();

        for turn in recent_turns {
            context.push_str(&format!("Q: {}\nA: {}\n\n", turn.question, turn.answer));
        }

        context
    }
}

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

    #[test]
    fn test_conversation_turn_creation() {
        let turn = ConversationTurn::new(
            0,
            "What is kubernetes?".to_string(),
            "Kubernetes is a container orchestration platform.".to_string(),
            vec!["doc1".to_string(), "doc2".to_string()],
            0.85,
        );

        assert_eq!(turn.turn_number, 0);
        assert_eq!(turn.question, "What is kubernetes?");
        assert_eq!(turn.confidence, 0.85);
        assert_eq!(turn.sources.len(), 2);
        assert!(!turn.is_followup);
    }

    #[test]
    fn test_conversation_turn_followup() {
        let turn = ConversationTurn::new(
            1,
            "Tell me more".to_string(),
            "Additional details...".to_string(),
            vec!["doc1".to_string()],
            0.80,
        );

        let followup = turn.mark_as_followup(vec![0]);

        assert!(followup.is_followup);
        assert_eq!(followup.references_turns, vec![0]);
    }

    #[test]
    fn test_conversation_context_creation() {
        let context = ConversationContext::new("conv_123".to_string());

        assert_eq!(context.conversation_id, "conv_123");
        assert!(context.is_empty());
        assert_eq!(context.turn_count(), 0);
    }

    #[test]
    fn test_add_turn_to_context() {
        let mut context = ConversationContext::new("conv_123".to_string());
        let turn = ConversationTurn::new(
            0,
            "Question?".to_string(),
            "Answer.".to_string(),
            vec![],
            0.8,
        );

        context.add_turn(turn);

        assert_eq!(context.turn_count(), 1);
        assert!(!context.is_empty());
    }

    #[test]
    fn test_context_history_limit() {
        let mut context = ConversationContext::new("conv_123".to_string()).with_max_history(3);

        for i in 0..5 {
            let turn = ConversationTurn::new(i, format!("Q{}", i), format!("A{}", i), vec![], 0.8);
            context.add_turn(turn);
        }

        assert_eq!(context.turn_count(), 3); // Only last 3 turns kept
    }

    #[test]
    fn test_get_recent_turns() {
        let mut context = ConversationContext::new("conv_123".to_string());

        for i in 0..5 {
            let turn = ConversationTurn::new(i, format!("Q{}", i), format!("A{}", i), vec![], 0.8);
            context.add_turn(turn);
        }

        let recent = context.get_recent_turns(2);
        assert_eq!(recent.len(), 2);
        assert_eq!(recent[0].turn_number, 3);
        assert_eq!(recent[1].turn_number, 4);
    }

    #[test]
    fn test_document_scope_update() {
        let mut context = ConversationContext::new("conv_123".to_string());

        context.update_document_scope(vec!["doc1".to_string(), "doc2".to_string()]);
        assert_eq!(context.document_scope.len(), 2);

        // Adding duplicate should not increase count
        context.update_document_scope(vec!["doc1".to_string()]);
        assert_eq!(context.document_scope.len(), 2);

        // Adding new should increase count
        context.update_document_scope(vec!["doc3".to_string()]);
        assert_eq!(context.document_scope.len(), 3);
    }

    #[test]
    fn test_context_string_building() {
        let mut context = ConversationContext::new("conv_123".to_string());
        let turn = ConversationTurn::new(
            0,
            "What is Docker?".to_string(),
            "Docker is a containerization platform.".to_string(),
            vec![],
            0.8,
        );
        context.add_turn(turn);

        let context_str = context.build_context_string();
        assert!(context_str.contains("What is Docker?"));
        assert!(context_str.contains("Docker is a containerization platform."));
    }

    #[test]
    fn test_conversation_summary() {
        let mut context =
            ConversationContext::new("conv_123".to_string()).with_topic("Kubernetes".to_string());

        let turn = ConversationTurn::new(0, "Q1".to_string(), "A1".to_string(), vec![], 0.8);
        context.add_turn(turn);

        let summary = context.get_summary();
        assert!(summary.contains("1 turns"));
        assert!(summary.contains("Kubernetes"));
        assert!(summary.contains("confidence"));
    }

    #[test]
    fn test_followup_detection_keywords() {
        let previous = vec![ConversationTurn::new(
            0,
            "What is Kubernetes?".to_string(),
            "Kubernetes is...".to_string(),
            vec![],
            0.8,
        )];

        let followup_questions = vec![
            "Tell me more about that",
            "And what about Docker?",
            "Can you explain further?",
            "Why is this important?",
        ];

        for q in followup_questions {
            let is_fu = FollowupDetector::is_followup(q, &previous);
            assert!(is_fu, "Failed for: {}", q);
        }
    }

    #[test]
    fn test_followup_detection_pronouns() {
        let previous = vec![ConversationTurn::new(
            0,
            "What is Kubernetes?".to_string(),
            "Kubernetes is...".to_string(),
            vec![],
            0.8,
        )];

        let is_fu = FollowupDetector::is_followup("It is popular because...", &previous);
        assert!(is_fu);
    }

    #[test]
    fn test_no_followup_for_new_topic() {
        let previous = vec![ConversationTurn::new(
            0,
            "What is Kubernetes?".to_string(),
            "Kubernetes is...".to_string(),
            vec![],
            0.8,
        )];

        let is_fu = FollowupDetector::is_followup("Tell me about Docker's architecture", &previous);
        assert!(!is_fu);
    }

    #[test]
    fn test_referenced_turns_detection() {
        let previous = vec![ConversationTurn::new(
            0,
            "What is Kubernetes?".to_string(),
            "Kubernetes is a container orchestration platform with clustering capabilities"
                .to_string(),
            vec![],
            0.8,
        )];

        let referenced = FollowupDetector::find_referenced_turns(
            "Tell me more about container orchestration",
            &previous,
        );

        // Should reference turn 0 because question contains terms from previous answer
        // (both "container" and "orchestration" appear in previous answer)
        assert!(!referenced.is_empty());
    }

    #[test]
    fn test_conversation_metadata() {
        let mut context = ConversationContext::new("conv_123".to_string());
        context
            .metadata
            .insert("user_id".to_string(), "user_456".to_string());
        context
            .metadata
            .insert("session".to_string(), "session_789".to_string());

        assert_eq!(
            context.metadata.get("user_id"),
            Some(&"user_456".to_string())
        );
        assert_eq!(
            context.metadata.get("session"),
            Some(&"session_789".to_string())
        );
    }

    #[test]
    fn test_clear_conversation() {
        let mut context = ConversationContext::new("conv_123".to_string());

        for i in 0..3 {
            let turn = ConversationTurn::new(i, format!("Q{}", i), format!("A{}", i), vec![], 0.8);
            context.add_turn(turn);
        }

        assert_eq!(context.turn_count(), 3);

        context.clear_history();

        assert_eq!(context.turn_count(), 0);
        assert!(context.is_empty());
    }

    #[test]
    fn test_get_turn_by_number() {
        let mut context = ConversationContext::new("conv_123".to_string());

        for i in 0..3 {
            let turn = ConversationTurn::new(i, format!("Q{}", i), format!("A{}", i), vec![], 0.8);
            context.add_turn(turn);
        }

        let turn = context.get_turn(1);
        assert!(turn.is_some());
        assert_eq!(turn.unwrap().question, "Q1");

        let missing = context.get_turn(10);
        assert!(missing.is_none());
    }
}
