//! RAG configuration

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use platform_config::ConfigLoader;
use serde::{Deserialize, Serialize};

/// RAG system configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RagConfig {
    /// Enable RAG system
    #[serde(default = "default_enabled")]
    pub enabled: bool,

    /// Embedding configuration
    #[serde(default)]
    pub embeddings: EmbeddingConfig,

    /// Vector database configuration
    #[serde(default)]
    pub vector_db: VectorDbConfig,

    /// LLM configuration
    #[serde(default)]
    pub llm: LlmConfig,

    /// Retrieval configuration
    #[serde(default)]
    pub retrieval: RetrievalConfig,

    /// Ingestion configuration
    #[serde(default)]
    pub ingestion: IngestionConfig,
}

impl RagConfig {
    /// Load configuration from hierarchical sources with mode support
    ///
    /// Priority order:
    /// 1. RAG_CONFIG environment variable (explicit path)
    /// 2. RAG_MODE environment variable (mode-specific file)
    /// 3. Default configuration
    ///
    /// After loading, applies environment variable overrides.
    pub fn load_from_hierarchy() -> Result<Self, Box<dyn std::error::Error>> {
        let config = if let Ok(path) = env::var("RAG_CONFIG") {
            // Explicit config path provided
            let contents = fs::read_to_string(&path)?;
            toml::from_str(&contents)?
        } else if let Ok(mode) = env::var("RAG_MODE") {
            // Mode-specific config file
            let path = PathBuf::from(format!("provisioning/platform/config/rag.{}.toml", mode));
            let contents = fs::read_to_string(&path)?;
            toml::from_str(&contents)?
        } else {
            // Fall back to default
            Self::default()
        };

        Ok(config.apply_env_overrides())
    }

    /// Apply environment variable overrides to configuration
    ///
    /// Overrides take precedence over loaded config values.
    /// Pattern: RAG_{SUBSYSTEM}_{KEY}
    fn apply_env_overrides(mut self) -> Self {
        // Embeddings overrides
        if let Ok(val) = env::var("RAG_EMBEDDINGS_PROVIDER") {
            self.embeddings.provider = val;
        }
        if let Ok(val) = env::var("RAG_EMBEDDINGS_MODEL") {
            self.embeddings.model = val;
        }
        if let Ok(val) = env::var("RAG_EMBEDDINGS_DIMENSION") {
            if let Ok(dim) = val.parse() {
                self.embeddings.dimension = dim;
            }
        }
        if let Ok(val) = env::var("RAG_EMBEDDINGS_API_KEY") {
            self.embeddings.openai_api_key = Some(val);
        }
        if let Ok(val) = env::var("RAG_EMBEDDINGS_BATCH_SIZE") {
            if let Ok(size) = val.parse() {
                self.embeddings.batch_size = size;
            }
        }

        // Vector DB overrides
        if let Ok(val) = env::var("RAG_VECTOR_DB_TYPE") {
            self.vector_db.db_type = val;
        }
        if let Ok(val) = env::var("RAG_VECTOR_DB_URL") {
            self.vector_db.url = val;
        }
        if let Ok(val) = env::var("RAG_VECTOR_DB_NAMESPACE") {
            self.vector_db.namespace = val;
        }
        if let Ok(val) = env::var("RAG_VECTOR_DB_DATABASE") {
            self.vector_db.database = val;
        }
        if let Ok(val) = env::var("RAG_HNSW_M") {
            if let Ok(m) = val.parse() {
                self.vector_db.hnsw.m = m;
            }
        }
        if let Ok(val) = env::var("RAG_HNSW_EF_CONSTRUCTION") {
            if let Ok(ef) = val.parse() {
                self.vector_db.hnsw.ef_construction = ef;
            }
        }

        // LLM overrides
        if let Ok(val) = env::var("RAG_LLM_PROVIDER") {
            self.llm.provider = val;
        }
        if let Ok(val) = env::var("RAG_LLM_MODEL") {
            self.llm.model = val;
        }
        if let Ok(val) = env::var("RAG_LLM_API_KEY") {
            self.llm.api_key = Some(val);
        }
        if let Ok(val) = env::var("RAG_LLM_TEMPERATURE") {
            if let Ok(temp) = val.parse() {
                self.llm.temperature = temp;
            }
        }
        if let Ok(val) = env::var("RAG_LLM_MAX_TOKENS") {
            if let Ok(tokens) = val.parse() {
                self.llm.max_tokens = tokens;
            }
        }
        if let Ok(val) = env::var("RAG_LLM_BASE_URL") {
            self.llm.base_url = Some(val);
        }

        // Retrieval overrides
        if let Ok(val) = env::var("RAG_RETRIEVAL_TOP_K") {
            if let Ok(k) = val.parse() {
                self.retrieval.top_k = k;
            }
        }
        if let Ok(val) = env::var("RAG_RETRIEVAL_SIMILARITY_THRESHOLD") {
            if let Ok(threshold) = val.parse() {
                self.retrieval.similarity_threshold = threshold;
            }
        }
        if let Ok(val) = env::var("RAG_RETRIEVAL_RERANKING") {
            self.retrieval.use_reranking = val.parse().unwrap_or(self.retrieval.use_reranking);
        }
        if let Ok(val) = env::var("RAG_RETRIEVAL_HYBRID") {
            self.retrieval.use_hybrid = val.parse().unwrap_or(self.retrieval.use_hybrid);
        }

        // Ingestion overrides
        if let Ok(val) = env::var("RAG_INGESTION_AUTO_INGEST") {
            self.ingestion.auto_ingest = val.parse().unwrap_or(self.ingestion.auto_ingest);
        }
        if let Ok(val) = env::var("RAG_INGESTION_CHUNK_SIZE") {
            if let Ok(size) = val.parse() {
                self.ingestion.chunk_size = size;
            }
        }
        if let Ok(val) = env::var("RAG_INGESTION_CHUNK_OVERLAP") {
            if let Ok(overlap) = val.parse() {
                self.ingestion.chunk_overlap = overlap;
            }
        }

        // RAG enable/disable override
        if let Ok(val) = env::var("RAG_ENABLED") {
            self.enabled = val.parse().unwrap_or(self.enabled);
        }

        self
    }
}

impl ConfigLoader for RagConfig {
    fn service_name() -> &'static str {
        "rag"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        let service = Self::service_name();

        if let Some(path) = platform_config::resolve_config_path(service) {
            return Self::from_path(&path);
        }

        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        *self = self.clone().apply_env_overrides();
        Ok(())
    }

    fn from_path<P: AsRef<Path>>(
        path: P,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = platform_config::format::load_config(path).map_err(|e| {
            let err: Box<dyn std::error::Error + Send + Sync> = Box::new(e);
            err
        })?;

        serde_json::from_value(json_value).map_err(|e| {
            let err_msg = format!("Failed to deserialize rag config from {:?}: {}", path, e);
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                err_msg,
            )) as Box<dyn std::error::Error + Send + Sync>
        })
    }
}

/// Embedding model configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingConfig {
    /// Provider: "openai" or "local"
    #[serde(default = "default_embedding_provider")]
    pub provider: String,

    /// Model name (e.g., "text-embedding-3-small")
    #[serde(default = "default_embedding_model")]
    pub model: String,

    /// Embedding dimension (1536 for text-embedding-3-small)
    #[serde(default = "default_embedding_dimension")]
    pub dimension: usize,

    /// OpenAI API key (required for OpenAI provider)
    pub openai_api_key: Option<String>,

    /// Use local fallback if API fails
    #[serde(default = "default_true")]
    pub fallback_local: bool,

    /// Batch size for embedding generation
    #[serde(default = "default_batch_size")]
    pub batch_size: usize,
}

/// Vector database configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorDbConfig {
    /// Database type: "surrealdb"
    #[serde(default = "default_db_type")]
    pub db_type: String,

    /// SurrealDB connection URL
    #[serde(default = "default_surrealdb_url")]
    pub url: String,

    /// Namespace for RAG data
    #[serde(default = "default_namespace")]
    pub namespace: String,

    /// Database name
    #[serde(default = "default_database")]
    pub database: String,

    /// Documents table name
    #[serde(default = "default_documents_table")]
    pub documents_table: String,

    /// Deployments table name
    #[serde(default = "default_deployments_table")]
    pub deployments_table: String,

    /// HNSW index parameters
    #[serde(default)]
    pub hnsw: HnswConfig,
}

/// HNSW index configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HnswConfig {
    /// M parameter (number of bidirectional links created for each node)
    #[serde(default = "default_hnsw_m")]
    pub m: usize,

    /// EF construction parameter
    #[serde(default = "default_hnsw_ef_construction")]
    pub ef_construction: usize,

    /// EF search parameter
    #[serde(default = "default_hnsw_ef_search")]
    pub ef_search: usize,
}

/// LLM configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
    /// LLM provider: "anthropic", "openai", "ollama"
    #[serde(default = "default_llm_provider")]
    pub provider: String,

    /// Model name (e.g., "claude-opus-4-1")
    #[serde(default = "default_llm_model")]
    pub model: String,

    /// API key (provider-specific)
    pub api_key: Option<String>,

    /// Temperature for generation (0.0-1.0)
    #[serde(default = "default_temperature")]
    pub temperature: f32,

    /// Maximum tokens in response
    #[serde(default = "default_max_tokens")]
    pub max_tokens: usize,

    /// Base URL for local/self-hosted LLMs
    pub base_url: Option<String>,
}

/// Retrieval configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetrievalConfig {
    /// Number of documents to retrieve
    #[serde(default = "default_top_k")]
    pub top_k: usize,

    /// Similarity threshold (0.0-1.0)
    #[serde(default = "default_similarity_threshold")]
    pub similarity_threshold: f32,

    /// Use reranking to improve results
    #[serde(default = "default_use_reranking")]
    pub use_reranking: bool,

    /// Hybrid search (vector + keyword)
    #[serde(default = "default_use_hybrid")]
    pub use_hybrid: bool,
}

/// Document ingestion configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IngestionConfig {
    /// Enable automatic ingestion on startup
    #[serde(default = "default_auto_ingest")]
    pub auto_ingest: bool,

    /// Watch for file changes and re-index
    #[serde(default = "default_watch_files")]
    pub watch_files: bool,

    /// Chunk size in tokens
    #[serde(default = "default_chunk_size")]
    pub chunk_size: usize,

    /// Chunk overlap in tokens
    #[serde(default = "default_chunk_overlap")]
    pub chunk_overlap: usize,

    /// Document type filters
    #[serde(default = "default_doc_types")]
    pub doc_types: Vec<String>,
}

// Default value functions
fn default_enabled() -> bool {
    true
}

fn default_embedding_provider() -> String {
    "openai".to_string()
}

fn default_embedding_model() -> String {
    "text-embedding-3-small".to_string()
}

fn default_embedding_dimension() -> usize {
    1536
}

fn default_true() -> bool {
    true
}

fn default_batch_size() -> usize {
    10
}

fn default_db_type() -> String {
    "surrealdb".to_string()
}

fn default_surrealdb_url() -> String {
    "memory".to_string()
}

fn default_namespace() -> String {
    "control_center".to_string()
}

fn default_database() -> String {
    "rag".to_string()
}

fn default_documents_table() -> String {
    "documents".to_string()
}

fn default_deployments_table() -> String {
    "deployments".to_string()
}

fn default_hnsw_m() -> usize {
    16
}

fn default_hnsw_ef_construction() -> usize {
    200
}

fn default_hnsw_ef_search() -> usize {
    100
}

fn default_llm_provider() -> String {
    "anthropic".to_string()
}

fn default_llm_model() -> String {
    "claude-opus-4-1".to_string()
}

fn default_temperature() -> f32 {
    0.7
}

fn default_max_tokens() -> usize {
    2048
}

fn default_top_k() -> usize {
    5
}

fn default_similarity_threshold() -> f32 {
    0.5
}

fn default_use_reranking() -> bool {
    true
}

fn default_use_hybrid() -> bool {
    true
}

fn default_auto_ingest() -> bool {
    false
}

fn default_watch_files() -> bool {
    true
}

fn default_chunk_size() -> usize {
    1024
}

fn default_chunk_overlap() -> usize {
    100
}

fn default_doc_types() -> Vec<String> {
    vec![
        "markdown".to_string(),
        "kcl".to_string(),
        "nushell".to_string(),
    ]
}

impl Default for RagConfig {
    fn default() -> Self {
        Self {
            enabled: default_enabled(),
            embeddings: EmbeddingConfig::default(),
            vector_db: VectorDbConfig::default(),
            llm: LlmConfig::default(),
            retrieval: RetrievalConfig::default(),
            ingestion: IngestionConfig::default(),
        }
    }
}

impl Default for EmbeddingConfig {
    fn default() -> Self {
        Self {
            provider: default_embedding_provider(),
            model: default_embedding_model(),
            dimension: default_embedding_dimension(),
            openai_api_key: None,
            fallback_local: default_true(),
            batch_size: default_batch_size(),
        }
    }
}

impl Default for VectorDbConfig {
    fn default() -> Self {
        Self {
            db_type: default_db_type(),
            url: default_surrealdb_url(),
            namespace: default_namespace(),
            database: default_database(),
            documents_table: default_documents_table(),
            deployments_table: default_deployments_table(),
            hnsw: HnswConfig::default(),
        }
    }
}

impl Default for HnswConfig {
    fn default() -> Self {
        Self {
            m: default_hnsw_m(),
            ef_construction: default_hnsw_ef_construction(),
            ef_search: default_hnsw_ef_search(),
        }
    }
}

impl Default for LlmConfig {
    fn default() -> Self {
        Self {
            provider: default_llm_provider(),
            model: default_llm_model(),
            api_key: None,
            temperature: default_temperature(),
            max_tokens: default_max_tokens(),
            base_url: None,
        }
    }
}

impl Default for RetrievalConfig {
    fn default() -> Self {
        Self {
            top_k: default_top_k(),
            similarity_threshold: default_similarity_threshold(),
            use_reranking: default_use_reranking(),
            use_hybrid: default_use_hybrid(),
        }
    }
}

impl Default for IngestionConfig {
    fn default() -> Self {
        Self {
            auto_ingest: default_auto_ingest(),
            watch_files: default_watch_files(),
            chunk_size: default_chunk_size(),
            chunk_overlap: default_chunk_overlap(),
            doc_types: default_doc_types(),
        }
    }
}

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

    #[test]
    fn test_default_config() {
        let config = RagConfig::default();
        assert!(config.enabled);
        assert_eq!(config.embeddings.model, "text-embedding-3-small");
        assert_eq!(config.vector_db.database, "rag");
        assert_eq!(config.llm.model, "claude-opus-4-1");
    }
}
