//! Data models for SurrealDB storage
//!
//! Defines the schema for all SurrealDB tables with support for:
//! - Graph relationships between conversations, schemas, fields
//! - Full-text search on messages
//! - Reusable messages and RAG results
//! - Schema validation with SurrealQL contracts

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

/// Conversation record in SurrealDB
///
/// Represents a user conversation session with a particular schema
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conversation {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// Schema being configured
    pub schema_id: String,

    /// Creation timestamp
    pub created_at: DateTime<Utc>,

    /// Last update timestamp
    pub updated_at: DateTime<Utc>,

    /// Conversation status
    pub status: ConversationStatus,

    /// Collected field values as JSON
    #[serde(default)]
    pub collected_fields: serde_json::Value,
}

/// Conversation status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConversationStatus {
    #[serde(rename = "active")]
    Active,
    #[serde(rename = "completed")]
    Completed,
    #[serde(rename = "abandoned")]
    Abandoned,
}

/// Message record in SurrealDB
///
/// Individual message in a conversation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// Conversation this message belongs to
    pub conversation_id: String,

    /// Message role
    pub role: MessageRole,

    /// Message content
    pub content: String,

    /// Message timestamp
    pub timestamp: DateTime<Utc>,

    /// IDs of RAG results used for this message
    #[serde(default)]
    pub rag_result_ids: Vec<String>,

    /// Full-text search index
    #[serde(skip)]
    pub search_text: String,
}

/// Message role in conversation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MessageRole {
    #[serde(rename = "user")]
    User,
    #[serde(rename = "assistant")]
    Assistant,
}

/// RAG retrieval result
///
/// Reusable RAG results that can be linked to multiple messages
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RagResult {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// Source document ID
    pub doc_id: String,

    /// Retrieved content
    pub content: String,

    /// Relevance score (0-1)
    pub combined_score: f32,

    /// Semantic score component
    pub semantic_score: f32,

    /// Keyword score component
    pub keyword_score: f32,

    /// Creation timestamp
    pub created_at: DateTime<Utc>,
}

/// Schema record
///
/// Stores Nickel schemas for reuse across conversations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Schema {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// Schema name
    pub name: String,

    /// Schema file path
    pub path: String,

    /// Full Nickel schema content
    pub content: String,

    /// Creation timestamp
    pub created_at: DateTime<Utc>,

    /// Last update timestamp
    pub updated_at: DateTime<Utc>,
}

/// Schema field definition
///
/// Individual field extracted from a Nickel schema
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Field {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,

    /// Schema this field belongs to
    pub schema_id: String,

    /// Field name
    pub name: String,

    /// Field type (String, Number, Bool, etc.)
    pub field_type: String,

    /// Human-readable description
    pub description: String,

    /// Is field required?
    pub required: bool,

    /// Default value if any
    pub default_value: Option<String>,

    /// Creation timestamp
    pub created_at: DateTime<Utc>,
}

/// Query filter for messages
pub struct MessageFilter {
    pub conversation_id: Option<String>,
    pub role: Option<MessageRole>,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
}

impl Default for MessageFilter {
    fn default() -> Self {
        MessageFilter {
            conversation_id: None,
            role: None,
            limit: Some(50),
            offset: Some(0),
        }
    }
}

/// Query filter for conversations
pub struct ConversationFilter {
    pub schema_id: Option<String>,
    pub status: Option<ConversationStatus>,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
}

impl Default for ConversationFilter {
    fn default() -> Self {
        ConversationFilter {
            schema_id: None,
            status: None,
            limit: Some(50),
            offset: Some(0),
        }
    }
}

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

    #[test]
    fn test_conversation_creation() {
        let conv = Conversation {
            id: Some(Uuid::new_v4().to_string()),
            schema_id: "schema1".to_string(),
            created_at: Utc::now(),
            updated_at: Utc::now(),
            status: ConversationStatus::Active,
            collected_fields: serde_json::json!({}),
        };

        assert_eq!(conv.status, ConversationStatus::Active);
    }

    #[test]
    fn test_message_serialization() {
        let msg = Message {
            id: Some(Uuid::new_v4().to_string()),
            conversation_id: "conv1".to_string(),
            role: MessageRole::User,
            content: "Hello".to_string(),
            timestamp: Utc::now(),
            rag_result_ids: vec![],
            search_text: "hello".to_string(),
        };

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

    #[test]
    fn test_rag_result_scores() {
        let rag = RagResult {
            id: Some(Uuid::new_v4().to_string()),
            doc_id: "doc1".to_string(),
            content: "content".to_string(),
            combined_score: 0.85,
            semantic_score: 0.90,
            keyword_score: 0.80,
            created_at: Utc::now(),
        };

        assert!(rag.combined_score >= 0.0 && rag.combined_score <= 1.0);
    }
}
