#![allow(dead_code)]

//! AI Configuration Assistant Engine
//!
//! Core conversation loop integrating:
//! - LLM provider (OpenAI, Claude, Ollama)
//! - RAG system for example retrieval
//! - Nickel schema understanding
//! - Configuration generation and validation
//! - Persistent conversation storage

use crate::llm::{
    prompts::system::config_assistant_system, rag_integration::format_rag_context,
    GenerationOptions, LlmProvider, Message as LlmMessage,
};
use crate::storage::{MessageRole, SurrealDbClient};
use anyhow::Result;
use chrono::Utc;
use futures::stream::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use typedialog_core::ai::rag::RagSystem;

use super::schema_analyzer::AnalyzedSchema;

/// Structured response from the assistant
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantResponse {
    /// The assistant's text response
    pub text: String,

    /// Field suggestions extracted from the response
    pub suggestions: Vec<FieldSuggestion>,

    /// RAG results that informed this response
    pub rag_context: Option<String>,

    /// Message ID in storage
    pub message_id: String,
}

/// Suggestion for a configuration field
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldSuggestion {
    /// Field name (e.g., "port", "database_url")
    pub field: String,

    /// Suggested value
    pub value: String,

    /// Explanation of the suggestion
    pub reasoning: String,

    /// Confidence score (0-1)
    pub confidence: f32,
}

/// Configuration generation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneratedConfig {
    /// Generated configuration (JSON, YAML, or TOML)
    pub content: String,

    /// Format of the configuration
    pub format: String,

    /// Whether the configuration is valid
    pub is_valid: bool,

    /// Validation errors if any
    pub errors: Vec<String>,
}

/// AI Configuration Assistant Engine
///
/// Manages multi-turn conversations for intelligent configuration generation
/// using RAG-powered suggestions and LLM generation.
pub struct ConfigAssistant {
    /// LLM provider for text generation
    llm: Arc<dyn LlmProvider>,

    /// RAG system for retrieving relevant examples
    rag: RagSystem,

    /// Analyzed schema for the current conversation
    schema: AnalyzedSchema,

    /// Database client for persistence
    storage: Arc<SurrealDbClient>,

    /// Current conversation ID
    conversation_id: String,

    /// Conversation history for context
    history: Vec<ConversationTurn>,
}

/// Single turn in a conversation
#[derive(Debug, Clone)]
pub struct ConversationTurn {
    /// User or assistant message
    role: MessageRole,

    /// Message content
    content: String,

    /// Timestamp
    timestamp: chrono::DateTime<Utc>,
}

impl ConfigAssistant {
    /// Create a new configuration assistant for a conversation
    ///
    /// # Arguments
    ///
    /// * `llm` - LLM provider (OpenAI, Claude, Ollama)
    /// * `rag` - Initialized RAG system
    /// * `schema` - Analyzed schema for the conversation
    /// * `storage` - Database client for persistence
    /// * `conversation_id` - Unique conversation identifier
    ///
    /// # Returns
    ///
    /// Configured assistant ready for use
    pub fn new(
        llm: Arc<dyn LlmProvider>,
        rag: RagSystem,
        schema: AnalyzedSchema,
        storage: Arc<SurrealDbClient>,
        conversation_id: String,
    ) -> Self {
        ConfigAssistant {
            llm,
            rag,
            schema,
            storage,
            conversation_id,
            history: Vec::new(),
        }
    }

    /// Send a user message and get assistant response
    ///
    /// The complete flow:
    /// 1. Store user message
    /// 2. Retrieve relevant examples via RAG
    /// 3. Format prompt with schema and RAG context
    /// 4. Generate response with LLM
    /// 5. Extract field suggestions
    /// 6. Store assistant response
    /// 7. Return structured response
    pub async fn send_message(&mut self, user_message: &str) -> Result<AssistantResponse> {
        tracing::debug!(conv_id = %self.conversation_id, "Processing user message");

        // 1. Store user message
        let user_msg_id = self
            .storage
            .create_message(&self.conversation_id, MessageRole::User, user_message)
            .await?;
        self.history.push(ConversationTurn {
            role: MessageRole::User,
            content: user_message.to_string(),
            timestamp: Utc::now(),
        });

        // 2. Retrieve relevant examples via RAG
        let rag_results = self.rag.retrieve(user_message)?;

        // Convert to llm::rag_integration::RetrievalResult for formatting
        let llm_rag_results: Vec<_> = rag_results
            .iter()
            .map(|r| crate::llm::rag_integration::RetrievalResult {
                doc_id: r.doc_id.clone(),
                content: r.content.clone(),
                combined_score: r.combined_score,
            })
            .collect();

        let rag_context = format_rag_context(&llm_rag_results);

        tracing::debug!(results = rag_results.len(), "RAG retrieval complete");

        // 3. Format prompt with schema and RAG context
        let system_prompt = config_assistant_system();
        let schema_description = self.schema.format_for_prompt();

        let context_section = if !rag_context.is_empty() {
            format!("## Relevant Examples\n\n{}\n\n", rag_context)
        } else {
            String::new()
        };

        let user_prompt = format!(
            "{}{}## Current Question\n\n{}",
            schema_description, context_section, user_message
        );

        let mut messages = vec![LlmMessage::system(&system_prompt)];

        // Add conversation history for context
        for turn in &self.history[..self.history.len().saturating_sub(1)] {
            match turn.role {
                MessageRole::User => {
                    messages.push(LlmMessage::user(&turn.content));
                }
                MessageRole::Assistant => {
                    messages.push(LlmMessage::assistant(&turn.content));
                }
            }
        }

        messages.push(LlmMessage::user(&user_prompt));

        // 4. Generate response with LLM
        let options = GenerationOptions::analytical();
        let response_text = self.llm.generate(&messages, &options).await?;

        tracing::debug!("LLM generation complete");

        // 5. Extract field suggestions
        let suggestions = self.extract_suggestions(&response_text)?;

        // 6. Store assistant response
        let assistant_msg_id = self
            .storage
            .create_message(
                &self.conversation_id,
                MessageRole::Assistant,
                &response_text,
            )
            .await?;
        self.history.push(ConversationTurn {
            role: MessageRole::Assistant,
            content: response_text.clone(),
            timestamp: Utc::now(),
        });

        // Link RAG results to user message for debugging/analytics
        let rag_ids: Vec<String> = rag_results.iter().map(|r| r.doc_id.clone()).collect();
        if !rag_ids.is_empty() {
            self.storage.link_rag_results(&user_msg_id, rag_ids).await?;
        }

        Ok(AssistantResponse {
            text: response_text,
            suggestions,
            rag_context: Some(rag_context),
            message_id: assistant_msg_id,
        })
    }

    /// Get suggestions for a specific field
    ///
    /// Uses RAG to find relevant examples and extract suggested values
    pub async fn suggest_values(&mut self, field_name: &str) -> Result<Vec<FieldSuggestion>> {
        if self.schema.find_field(field_name).is_none() {
            return Ok(vec![]);
        }

        // Query RAG for this field
        let query = format!(
            "examples for {} configuration with {} field",
            self.schema.name, field_name
        );

        let rag_results = self.rag.retrieve(&query)?;

        // Extract values from RAG results
        let mut suggestions = Vec::new();

        for result in rag_results.iter().take(3) {
            // Extract values from the content
            if let Some(value) = self.extract_field_value(&result.content, field_name) {
                suggestions.push(FieldSuggestion {
                    field: field_name.to_string(),
                    value,
                    reasoning: format!("Found in example: {}", result.doc_id),
                    confidence: result.combined_score,
                });
            }
        }

        Ok(suggestions)
    }

    /// Generate final configuration from conversation
    ///
    /// Creates a complete configuration based on the collected fields
    /// and validates it against the schema
    pub async fn generate_config(&mut self, format: &str) -> Result<GeneratedConfig> {
        tracing::debug!(format = %format, "Generating configuration");

        // Collect all values from conversation
        let collected_values = self.collect_field_values()?;

        // Build generation prompt
        let generation_prompt = format!(
            "Based on our conversation, generate a {} configuration for {}.\n\
             Collected values: {}\n\
             Schema: {}\n\
             Ensure all required fields are included and all values match the schema constraints.",
            format,
            self.schema.name,
            serde_json::to_string(&collected_values)?,
            self.schema.format_for_prompt()
        );

        #[allow(clippy::useless_vec)]
        let messages = vec![
            LlmMessage::system(config_assistant_system()),
            LlmMessage::user(&generation_prompt),
        ];

        // Generate configuration
        let options = GenerationOptions::config_generation();
        let config_content = self.llm.generate(&messages, &options).await?;

        tracing::debug!("Configuration generated, validating");

        // TODO: Validate configuration with Nickel
        // For now, mark as valid if it's not empty
        let is_valid = !config_content.trim().is_empty();

        Ok(GeneratedConfig {
            content: config_content,
            format: format.to_string(),
            is_valid,
            errors: vec![],
        })
    }

    /// Get conversation history
    pub fn history(&self) -> &[ConversationTurn] {
        &self.history
    }

    /// Get conversation ID
    pub fn conversation_id(&self) -> &str {
        &self.conversation_id
    }

    /// Get schema
    pub fn schema(&self) -> &AnalyzedSchema {
        &self.schema
    }

    /// Stream a response for real-time WebSocket delivery
    ///
    /// Similar to send_message but returns a stream instead of full response
    /// for token-by-token delivery via WebSocket
    pub async fn stream_message(
        &mut self,
        user_message: &str,
    ) -> Result<futures::stream::BoxStream<'static, String>> {
        tracing::debug!(conv_id = %self.conversation_id, "Streaming user message");

        // Store user message
        let _user_msg_id = self
            .storage
            .create_message(&self.conversation_id, MessageRole::User, user_message)
            .await?;
        self.history.push(ConversationTurn {
            role: MessageRole::User,
            content: user_message.to_string(),
            timestamp: Utc::now(),
        });

        // Retrieve relevant examples via RAG
        let rag_results = self.rag.retrieve(user_message)?;

        // Convert to llm::rag_integration::RetrievalResult for formatting
        let llm_rag_results: Vec<_> = rag_results
            .iter()
            .map(|r| crate::llm::rag_integration::RetrievalResult {
                doc_id: r.doc_id.clone(),
                content: r.content.clone(),
                combined_score: r.combined_score,
            })
            .collect();

        let rag_context = format_rag_context(&llm_rag_results);

        // Format prompt with schema and RAG context
        let system_prompt = config_assistant_system();
        let schema_description = self.schema.format_for_prompt();

        let context_section = if !rag_context.is_empty() {
            format!("## Relevant Examples\n\n{}\n\n", rag_context)
        } else {
            String::new()
        };

        let user_prompt = format!(
            "{}{}## Current Question\n\n{}",
            schema_description, context_section, user_message
        );

        let mut messages = vec![LlmMessage::system(&system_prompt)];

        // Add conversation history for context
        for turn in &self.history[..self.history.len().saturating_sub(1)] {
            match turn.role {
                MessageRole::User => {
                    messages.push(LlmMessage::user(&turn.content));
                }
                MessageRole::Assistant => {
                    messages.push(LlmMessage::assistant(&turn.content));
                }
            }
        }

        messages.push(LlmMessage::user(&user_prompt));

        // Create stream
        let options = GenerationOptions::analytical();
        let stream = self.llm.stream(&messages, &options).await?;

        // Map stream to handle Result types - take only Ok values
        let mapped_stream = stream
            .filter_map(|result| async move {
                match result {
                    Ok(chunk) => Some(chunk),
                    Err(e) => {
                        tracing::error!("Stream error: {:?}", e);
                        None
                    }
                }
            })
            .boxed();

        Ok(mapped_stream)
    }

    // ============================================================================
    // PRIVATE HELPER METHODS
    // ============================================================================

    /// Format conversation history for inclusion in prompts
    fn format_conversation_history(&self) -> String {
        if self.history.len() <= 1 {
            return String::new();
        }

        let mut formatted = String::from("## Conversation History\n\n");

        for turn in &self.history[..self.history.len().saturating_sub(1)] {
            match turn.role {
                MessageRole::User => {
                    formatted.push_str(&format!("**User:** {}\n\n", turn.content));
                }
                MessageRole::Assistant => {
                    formatted.push_str(&format!("**Assistant:** {}\n\n", turn.content));
                }
            }
        }

        formatted
    }

    /// Extract field suggestions from LLM response
    fn extract_suggestions(&self, response: &str) -> Result<Vec<FieldSuggestion>> {
        let mut suggestions = Vec::new();

        // Simple pattern matching for field suggestions
        // Looks for patterns like "field_name: value" or "field_name should be value"
        for field in self.schema.fields.iter() {
            if let Some(pos) = response.find(&field.flat_name) {
                // Look for value near the field name
                let context = &response[pos..std::cmp::min(pos + 200, response.len())];

                if let Some(value) = self.extract_value_from_context(context, &field.flat_name) {
                    suggestions.push(FieldSuggestion {
                        field: field.flat_name.clone(),
                        value,
                        reasoning: "Extracted from LLM response".to_string(),
                        confidence: 0.7,
                    });
                }
            }
        }

        Ok(suggestions)
    }

    /// Extract field value from RAG result content
    fn extract_field_value(&self, content: &str, field_name: &str) -> Option<String> {
        // Look for patterns like: field_name = "value" or field_name: value
        let patterns = vec![
            format!("{} = \"", field_name),
            format!("{} = '", field_name),
            format!("{} = ", field_name),
            format!("{}: ", field_name),
        ];

        for pattern in patterns {
            if let Some(pos) = content.find(&pattern) {
                let start = pos + pattern.len();
                let remainder = &content[start..];

                // Extract value until quote or line end
                if pattern.contains("\"") {
                    if let Some(end) = remainder.find('"') {
                        return Some(remainder[..end].to_string());
                    }
                } else if pattern.contains("'") {
                    if let Some(end) = remainder.find('\'') {
                        return Some(remainder[..end].to_string());
                    }
                } else {
                    // Find next whitespace or comma
                    let end = remainder
                        .find(|c: char| c.is_whitespace() || c == ',' || c == ';')
                        .unwrap_or(remainder.len());
                    let value = remainder[..end].trim().to_string();
                    if !value.is_empty() {
                        return Some(value);
                    }
                }
            }
        }

        None
    }

    /// Extract value from response context
    fn extract_value_from_context(&self, context: &str, _field_name: &str) -> Option<String> {
        // Simple extraction: look for colon followed by value
        if let Some(colon_pos) = context.find(':') {
            let after_colon = &context[colon_pos + 1..];
            let value = after_colon
                .split_whitespace()
                .next()
                .unwrap_or("")
                .trim_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-' && c != '.');

            if !value.is_empty() {
                return Some(value.to_string());
            }
        }

        None
    }

    /// Collect field values from conversation history
    fn collect_field_values(&self) -> Result<serde_json::Value> {
        let mut values = serde_json::json!({});

        // Extract values from all assistant responses
        for turn in &self.history {
            if turn.role == MessageRole::Assistant {
                for suggestion in self.extract_suggestions(&turn.content)? {
                    values[suggestion.field] = serde_json::json!(suggestion.value);
                }
            }
        }

        Ok(values)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::assistant::schema_analyzer::SchemaAnalyzer;
    use typedialog_core::nickel::schema_ir::{NickelFieldIR, NickelSchemaIR, NickelType};

    fn create_test_schema() -> AnalyzedSchema {
        let fields = vec![NickelFieldIR {
            path: vec!["port".to_string()],
            flat_name: "port".to_string(),
            alias: Some("Port".to_string()),
            nickel_type: NickelType::Number,
            doc: Some("Server port".to_string()),
            default: None,
            optional: false,
            contract: None,
            contract_call: None,
            group: None,
            fragment_marker: None,
            is_array_of_records: false,
            array_element_fields: None,
            encryption_metadata: None,
        }];

        let schema_ir = NickelSchemaIR {
            name: "ServerConfig".to_string(),
            description: Some("Server configuration".to_string()),
            fields,
        };

        SchemaAnalyzer::analyze_schema_ir(schema_ir).unwrap()
    }

    #[test]
    fn test_assistant_response_serialization() {
        let response = AssistantResponse {
            text: "Configuration looks good".to_string(),
            suggestions: vec![FieldSuggestion {
                field: "port".to_string(),
                value: "8080".to_string(),
                reasoning: "Standard web port".to_string(),
                confidence: 0.9,
            }],
            rag_context: Some("Example context".to_string()),
            message_id: "msg-123".to_string(),
        };

        let json = serde_json::to_string(&response).unwrap();
        drop(serde_json::from_str::<AssistantResponse>(&json).unwrap());
    }

    #[test]
    fn test_generated_config_structure() {
        let config = GeneratedConfig {
            content: "port = 8080".to_string(),
            format: "toml".to_string(),
            is_valid: true,
            errors: vec![],
        };

        assert!(config.is_valid);
        assert_eq!(config.format, "toml");
    }

    #[test]
    fn test_field_suggestion_serialization() {
        let suggestion = FieldSuggestion {
            field: "database".to_string(),
            value: "localhost".to_string(),
            reasoning: "Default development server".to_string(),
            confidence: 0.85,
        };

        let json = serde_json::to_string(&suggestion).unwrap();
        let deserialized: FieldSuggestion = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.field, "database");
    }

    #[test]
    fn test_extract_field_value_equals_quote() {
        let content = r#"port = "8080""#;

        // Verify content parsing
        assert!(content.contains("8080"));
    }

    #[test]
    fn test_conversation_turn_storage() {
        let turn = ConversationTurn {
            role: MessageRole::User,
            content: "What port should I use?".to_string(),
            timestamp: Utc::now(),
        };

        assert_eq!(turn.role, MessageRole::User);
        assert!(turn.content.contains("port"));
    }

    #[test]
    fn test_message_role_equality() {
        assert_eq!(MessageRole::User, MessageRole::User);
        assert_ne!(MessageRole::User, MessageRole::Assistant);
    }
}
