//! LLM Gateway — Conversation & Message Data Models (Phase-79: T79.1 + T79.2)
//!
//! Defines:
//! - `LlmConversation` — A chat session with an LLM model
//! - `ConversationConfig` — Per-conversation inference parameters
//! - `StoredMessage` — A message persisted in the conversation
//! - Conversation-level aggregation (message_count, total_tokens, total_cost)
//!
//! Integrates with Phase-15's `OwnershipMeta` for multi-user access control.

use serde::{Deserialize, Serialize};
use crate::types::ownership::{Visibility, ProtectionLevel};

use super::types::TokenUsage;

// ============================================================================
// Conversation Config
// ============================================================================

/// Per-conversation inference parameters.
///
/// These override the model defaults when set, and can be changed mid-conversation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationConfig {
    /// Temperature override.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f32>,

    /// Max output tokens override.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_tokens: Option<usize>,

    /// Top-p sampling override.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub top_p: Option<f32>,

    /// Top-k sampling override.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub top_k: Option<usize>,

    /// Enable thinking/reasoning mode.
    #[serde(default)]
    pub enable_thinking: bool,

    /// Enable RAG augmentation for this conversation.
    #[serde(default)]
    pub enable_rag: bool,

    /// RAG collection name (when enable_rag is true).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rag_collection: Option<String>,

    /// Disable sending conversation history (stateless mode).
    #[serde(default)]
    pub disable_context: bool,

    /// Maximum number of history messages to include as context.
    /// None = use all available history (default behavior).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub context_window: Option<usize>,

    /// Response format hint (e.g., "json_object").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub response_format: Option<String>,

    /// Enable long-term conversational memory (Phase-84).
    ///
    /// When enabled, the MemoryManager will:
    /// - Recall relevant past context before each message
    /// - Summarize old messages when they exceed the window
    /// - Extract user profiles periodically
    #[serde(default)]
    pub enable_memory: bool,
}

impl Default for ConversationConfig {
    fn default() -> Self {
        Self {
            temperature: None,
            max_tokens: None,
            top_p: None,
            top_k: None,
            enable_thinking: false,
            enable_rag: false,
            rag_collection: None,
            disable_context: false,
            context_window: None,
            response_format: None,
            enable_memory: false,
        }
    }
}

// ============================================================================
// LlmConversation
// ============================================================================

/// A chat conversation (session) with an LLM.
///
/// Each conversation belongs to an owner (user) and is associated with a
/// provider + model. Messages are stored separately and linked by
/// `conversation_id`.
///
/// # Ownership
///
/// - `owner_id`: The user who created this conversation
/// - `visibility`: Controls who can see this conversation (Private/Public/Shared)
/// - All CRUD operations check ownership via `can_read()`/`can_edit()`/`can_delete()`
///
/// # Example
///
/// ```
/// use aifastdb_core::llm::conversation::{LlmConversation, ConversationConfig};
///
/// let conv = LlmConversation::new(
///     "user-001",
///     "deepseek-cloud",
///     "deepseek-chat",
///     "DeepSeek Chat",
/// );
/// assert_eq!(conv.owner_id, "user-001");
/// assert_eq!(conv.message_count, 0);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConversation {
    /// Unique conversation ID (UUID v4).
    pub id: String,

    /// Display title (auto-generated or user-set).
    pub title: String,

    /// Associated provider ID.
    pub provider_id: String,

    /// Associated model ID.
    pub model_id: String,

    /// Human-readable model name (for display).
    pub model_name: String,

    /// System prompt for this conversation.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub system_prompt: Option<String>,

    /// Per-conversation config overrides.
    #[serde(default)]
    pub config: ConversationConfig,

    /// Whether this conversation is favorited.
    #[serde(default)]
    pub is_favorite: bool,

    /// Whether this conversation is archived.
    #[serde(default)]
    pub is_archived: bool,

    /// Number of messages in this conversation (denormalized counter).
    #[serde(default)]
    pub message_count: u32,

    /// Total tokens used across all messages (denormalized).
    #[serde(default)]
    pub total_tokens: u64,

    /// Total cost across all messages (denormalized).
    #[serde(default)]
    pub total_cost: f64,

    // ── Ownership (Phase-15 integration) ──

    /// Owner's user ID.
    pub owner_id: String,

    /// Visibility setting.
    #[serde(default)]
    pub visibility: Visibility,

    /// Data protection level.
    #[serde(default)]
    pub protection: ProtectionLevel,

    /// Arbitrary metadata.
    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
    pub metadata: serde_json::Value,

    /// Creation timestamp (Unix milliseconds).
    pub created_at: i64,

    /// Last update timestamp (Unix milliseconds).
    pub updated_at: i64,
}

impl LlmConversation {
    /// Create a new conversation with default settings.
    pub fn new(
        owner_id: impl Into<String>,
        provider_id: impl Into<String>,
        model_id: impl Into<String>,
        model_name: impl Into<String>,
    ) -> Self {
        let now = now_ms();
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            title: "New Conversation".to_string(),
            provider_id: provider_id.into(),
            model_id: model_id.into(),
            model_name: model_name.into(),
            system_prompt: None,
            config: ConversationConfig::default(),
            is_favorite: false,
            is_archived: false,
            message_count: 0,
            total_tokens: 0,
            total_cost: 0.0,
            owner_id: owner_id.into(),
            visibility: Visibility::Private,
            protection: ProtectionLevel::None,
            metadata: serde_json::Value::Null,
            created_at: now,
            updated_at: now,
        }
    }

    /// Create with a specific title.
    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Create with a system prompt.
    pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
        self.system_prompt = Some(prompt.into());
        self
    }

    /// Create with a specific config.
    pub fn with_config(mut self, config: ConversationConfig) -> Self {
        self.config = config;
        self
    }

    /// Check if a user can read this conversation.
    pub fn can_read(&self, user_id: &str) -> bool {
        if self.owner_id == user_id {
            return true;
        }
        match &self.visibility {
            Visibility::Private => false,
            Visibility::Public => true,
            Visibility::Shared { user_ids } => user_ids.contains(&user_id.to_string()),
            Visibility::RoleRestricted { roles: _ } => false, // Role-based not implemented for conversations
        }
    }

    /// Check if a user can edit this conversation.
    pub fn can_edit(&self, user_id: &str) -> bool {
        if self.protection == ProtectionLevel::ReadOnly
            || self.protection == ProtectionLevel::Immutable
            || self.protection == ProtectionLevel::System
        {
            return false;
        }
        self.owner_id == user_id
    }

    /// Check if a user can delete this conversation.
    pub fn can_delete(&self, user_id: &str) -> bool {
        if self.protection == ProtectionLevel::Immutable
            || self.protection == ProtectionLevel::System
        {
            return false;
        }
        self.owner_id == user_id
    }

    /// Update the aggregation counters after a new message.
    pub fn add_message_stats(&mut self, usage: &TokenUsage) {
        self.message_count += 1;
        self.total_tokens += usage.total_tokens as u64;
        if let Some(cost) = usage.cost {
            self.total_cost += cost;
        }
        self.updated_at = now_ms();
    }

    /// Touch the updated_at timestamp.
    pub fn touch(&mut self) {
        self.updated_at = now_ms();
    }
}

// ============================================================================
// StoredMessage
// ============================================================================

/// A persisted message in a conversation.
///
/// This extends `LlmMessage` (which is the input format for `LlmClient`)
/// with storage-specific fields: `id`, `conversation_id`, timestamps,
/// `token_usage`, `duration_ms`, `model_id`, and `status`.
///
/// Messages are stored in the `LlmStore` keyed by conversation_id.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredMessage {
    /// Unique message ID (UUID v4).
    pub id: String,

    /// Parent conversation ID.
    pub conversation_id: String,

    /// Message role.
    pub role: super::client::MessageRole,

    /// Primary text content.
    #[serde(default)]
    pub content: String,

    /// Multi-modal content parts.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub content_parts: Vec<super::client::ContentPart>,

    /// Thinking/reasoning content (for models with thinking enabled).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thinking: Option<String>,

    /// Tool calls requested by the assistant.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tool_calls: Vec<super::types::ToolCall>,

    /// Tool call ID this message is responding to (for role == Tool).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,

    /// Name (for multi-participant or tool responses).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    /// Attachments (file references, images, etc.).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub attachments: Vec<Attachment>,

    /// Token usage for this message (assistant messages only).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub token_usage: Option<TokenUsage>,

    /// Generation duration in milliseconds (assistant messages only).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,

    /// The model that generated this message (assistant messages only).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model_id: Option<String>,

    /// Message status.
    #[serde(default)]
    pub status: MessageStatus,

    /// Creation timestamp (Unix milliseconds).
    pub created_at: i64,
}

impl StoredMessage {
    /// Create a new user message.
    pub fn user(conversation_id: &str, content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: conversation_id.to_string(),
            role: super::client::MessageRole::User,
            content: content.into(),
            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: MessageStatus::Sent,
            created_at: now_ms(),
        }
    }

    /// Create a new assistant message from a ChatResponse.
    pub fn from_response(
        conversation_id: &str,
        response: &super::types::ChatResponse,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: conversation_id.to_string(),
            role: super::client::MessageRole::Assistant,
            content: response.content.clone(),
            content_parts: Vec::new(),
            thinking: response.thinking.clone(),
            tool_calls: response.tool_calls.clone(),
            tool_call_id: None,
            name: None,
            attachments: Vec::new(),
            token_usage: response.usage.clone(),
            duration_ms: response.duration_ms,
            model_id: Some(response.model.clone()),
            status: MessageStatus::Sent,
            created_at: now_ms(),
        }
    }

    /// Create an assistant message from a RAG pipeline response.
    ///
    /// Converts `RagTokenUsage` → `TokenUsage` so cost/token data flows
    /// into `ConversationManager` for unified tracking.
    ///
    /// # Phase-82 T82.3
    #[cfg(feature = "llm-gateway")]
    pub fn from_rag_response(
        conversation_id: &str,
        rag_response: &crate::rag::RagResponse,
    ) -> Self {
        let token_usage = rag_response
            .usage
            .as_ref()
            .map(|u| super::types::TokenUsage::from(u));

        let model_id = rag_response
            .metadata
            .get("model")
            .and_then(|v| v.as_str())
            .map(String::from);

        Self {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: conversation_id.to_string(),
            role: super::client::MessageRole::Assistant,
            content: rag_response.answer.clone(),
            content_parts: Vec::new(),
            thinking: rag_response
                .metadata
                .get("thinking")
                .and_then(|v| v.as_str())
                .map(String::from),
            tool_calls: Vec::new(),
            tool_call_id: None,
            name: None,
            attachments: Vec::new(),
            token_usage,
            duration_ms: rag_response.latency_ms,
            model_id,
            status: MessageStatus::Sent,
            created_at: now_ms(),
        }
    }

    /// Create a new system message.
    pub fn system(conversation_id: &str, content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: conversation_id.to_string(),
            role: super::client::MessageRole::System,
            content: content.into(),
            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: MessageStatus::Sent,
            created_at: now_ms(),
        }
    }

    /// Create a tool result message.
    pub fn tool_result(
        conversation_id: &str,
        call_id: &str,
        name: &str,
        result: impl Into<String>,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            conversation_id: conversation_id.to_string(),
            role: super::client::MessageRole::Tool,
            content: result.into(),
            content_parts: Vec::new(),
            thinking: None,
            tool_calls: Vec::new(),
            tool_call_id: Some(call_id.to_string()),
            name: Some(name.to_string()),
            attachments: Vec::new(),
            token_usage: None,
            duration_ms: None,
            model_id: None,
            status: MessageStatus::Sent,
            created_at: now_ms(),
        }
    }

    /// Convert to `LlmMessage` for sending to LlmClient.
    pub fn to_llm_message(&self) -> super::client::LlmMessage {
        super::client::LlmMessage {
            role: self.role,
            content: self.content.clone(),
            content_parts: self.content_parts.clone(),
            tool_calls: self.tool_calls.clone(),
            tool_call_id: self.tool_call_id.clone(),
            name: self.name.clone(),
        }
    }
}

// ============================================================================
// Attachment
// ============================================================================

/// A file attachment on a message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
    /// Unique attachment ID.
    pub id: String,
    /// Original file name.
    pub filename: String,
    /// MIME type (e.g., "image/png", "application/pdf").
    pub mime_type: String,
    /// File size in bytes.
    pub size_bytes: u64,
    /// Storage URL or path (local or remote).
    pub url: String,
}

// ============================================================================
// MessageStatus
// ============================================================================

/// Status of a stored message.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageStatus {
    /// Being generated (streaming in progress).
    Sending,
    /// Successfully delivered / generated.
    Sent,
    /// Partially generated (interrupted by user or error).
    Partial,
    /// Error during generation.
    Error,
}

impl Default for MessageStatus {
    fn default() -> Self {
        Self::Sent
    }
}

// ============================================================================
// Conversation list query
// ============================================================================

/// Query parameters for listing conversations.
#[derive(Debug, Clone, Default)]
pub struct ConversationQuery {
    /// Filter by owner.
    pub owner_id: Option<String>,
    /// Current user (for visibility check).
    pub viewer_id: Option<String>,
    /// Filter favorites only.
    pub is_favorite: Option<bool>,
    /// Filter archived/non-archived.
    pub is_archived: Option<bool>,
    /// Search title (substring match).
    pub title_search: Option<String>,
    /// Filter by model_id.
    pub model_id: Option<String>,
    /// Filter by provider_id.
    pub provider_id: Option<String>,
    /// Pagination offset.
    pub offset: usize,
    /// Pagination limit.
    pub limit: usize,
}

impl ConversationQuery {
    pub fn new() -> Self {
        Self {
            limit: 50,
            ..Default::default()
        }
    }

    pub fn for_user(user_id: impl Into<String>) -> Self {
        let uid = user_id.into();
        Self {
            owner_id: Some(uid.clone()),
            viewer_id: Some(uid),
            limit: 50,
            ..Default::default()
        }
    }
}

/// Query parameters for listing messages.
#[derive(Debug, Clone)]
pub struct MessageQuery {
    /// Conversation ID (required).
    pub conversation_id: String,
    /// Pagination offset.
    pub offset: usize,
    /// Pagination limit.
    pub limit: usize,
    /// Only return messages after this timestamp.
    pub after_timestamp: Option<i64>,
    /// Only return messages before this timestamp.
    pub before_timestamp: Option<i64>,
}

impl MessageQuery {
    pub fn new(conversation_id: impl Into<String>) -> Self {
        Self {
            conversation_id: conversation_id.into(),
            offset: 0,
            limit: 100,
            after_timestamp: None,
            before_timestamp: None,
        }
    }
}

// ============================================================================
// Usage Statistics
// ============================================================================

/// Aggregated token usage statistics.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UsageStats {
    /// Total prompt tokens.
    pub total_prompt_tokens: u64,
    /// Total completion tokens.
    pub total_completion_tokens: u64,
    /// Total tokens.
    pub total_tokens: u64,
    /// Total cost.
    pub total_cost: f64,
    /// Number of messages counted.
    pub message_count: u32,
    /// Number of conversations counted.
    pub conversation_count: u32,
}

// ============================================================================
// Helpers
// ============================================================================

fn now_ms() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as i64
}

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

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

    #[test]
    fn test_conversation_new() {
        let conv = LlmConversation::new("user-001", "deepseek", "deepseek-chat", "DeepSeek Chat");
        assert_eq!(conv.owner_id, "user-001");
        assert_eq!(conv.provider_id, "deepseek");
        assert_eq!(conv.model_id, "deepseek-chat");
        assert_eq!(conv.message_count, 0);
        assert_eq!(conv.total_tokens, 0);
        assert!(!conv.is_favorite);
        assert!(!conv.is_archived);
        assert!(conv.can_read("user-001"));
        assert!(!conv.can_read("user-002"));
        assert!(conv.can_edit("user-001"));
        assert!(conv.can_delete("user-001"));
    }

    #[test]
    fn test_conversation_builder() {
        let conv = LlmConversation::new("u1", "p1", "m1", "Model")
            .with_title("My Chat")
            .with_system_prompt("You are helpful.");
        assert_eq!(conv.title, "My Chat");
        assert_eq!(conv.system_prompt.as_deref(), Some("You are helpful."));
    }

    #[test]
    fn test_conversation_visibility() {
        let mut conv = LlmConversation::new("owner", "p", "m", "M");

        // Private: only owner
        assert!(conv.can_read("owner"));
        assert!(!conv.can_read("other"));

        // Public: everyone
        conv.visibility = Visibility::Public;
        assert!(conv.can_read("owner"));
        assert!(conv.can_read("other"));

        // Shared: specific users
        conv.visibility = Visibility::Shared {
            user_ids: vec!["friend".into()],
        };
        assert!(conv.can_read("owner"));
        assert!(conv.can_read("friend"));
        assert!(!conv.can_read("stranger"));
    }

    #[test]
    fn test_conversation_protection() {
        let mut conv = LlmConversation::new("owner", "p", "m", "M");
        assert!(conv.can_edit("owner"));
        assert!(conv.can_delete("owner"));

        conv.protection = ProtectionLevel::ReadOnly;
        assert!(!conv.can_edit("owner"));
        assert!(conv.can_delete("owner"));

        conv.protection = ProtectionLevel::Immutable;
        assert!(!conv.can_edit("owner"));
        assert!(!conv.can_delete("owner"));
    }

    #[test]
    fn test_conversation_add_stats() {
        let mut conv = LlmConversation::new("u", "p", "m", "M");
        let usage = TokenUsage::new(100, 50);
        conv.add_message_stats(&usage);
        assert_eq!(conv.message_count, 1);
        assert_eq!(conv.total_tokens, 150);

        let usage2 = TokenUsage {
            prompt_tokens: 200,
            completion_tokens: 100,
            total_tokens: 300,
            cost: Some(0.005),
            ..Default::default()
        };
        conv.add_message_stats(&usage2);
        assert_eq!(conv.message_count, 2);
        assert_eq!(conv.total_tokens, 450);
        assert!((conv.total_cost - 0.005).abs() < f64::EPSILON);
    }

    #[test]
    fn test_stored_message_user() {
        let msg = StoredMessage::user("conv-1", "Hello!");
        assert_eq!(msg.conversation_id, "conv-1");
        assert_eq!(msg.role, super::super::client::MessageRole::User);
        assert_eq!(msg.content, "Hello!");
        assert_eq!(msg.status, MessageStatus::Sent);
    }

    #[test]
    fn test_stored_message_to_llm_message() {
        let msg = StoredMessage::user("conv-1", "Hi");
        let llm_msg = msg.to_llm_message();
        assert_eq!(llm_msg.content, "Hi");
        assert_eq!(llm_msg.role, super::super::client::MessageRole::User);
    }

    #[test]
    fn test_conversation_serde() {
        let conv = LlmConversation::new("u1", "p1", "m1", "Model");
        let json = serde_json::to_string(&conv).unwrap();
        let conv2: LlmConversation = serde_json::from_str(&json).unwrap();
        assert_eq!(conv2.id, conv.id);
        assert_eq!(conv2.owner_id, "u1");
    }

    #[test]
    fn test_message_serde() {
        let msg = StoredMessage::user("c1", "test");
        let json = serde_json::to_string(&msg).unwrap();
        let msg2: StoredMessage = serde_json::from_str(&json).unwrap();
        assert_eq!(msg2.content, "test");
    }

    #[test]
    fn test_conversation_query() {
        let q = ConversationQuery::for_user("user-1");
        assert_eq!(q.owner_id.as_deref(), Some("user-1"));
        assert_eq!(q.limit, 50);
    }
}
