//! Database configuration for multi-backend support
//!
//! Provides configuration structures for different database backends.
//! Configuration is typically loaded from TOML files and can be overridden
//! by environment variables.

use serde::{Deserialize, Serialize};
use std::path::Path;

/// Main database configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
    /// Backend engine: "sqlite" or "surrealdb"
    pub engine: String,

    /// SQLite configuration (required if engine is "sqlite")
    pub sqlite: Option<SqliteConfig>,

    /// SurrealDB dual-engine configuration (required if engine is "surrealdb")
    #[cfg(feature = "surrealdb-backend")]
    pub surrealdb: Option<SurrealDbBackendConfig>,

    /// PostgreSQL configuration (future support)
    pub postgresql: Option<PostgresConfig>,
}

/// SQLite-specific configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SqliteConfig {
    /// Path to SQLite database file
    pub path: String,

    /// Maximum number of connections in pool
    #[serde(default = "default_sqlite_pool_size")]
    pub max_connections: u32,

    /// Connection timeout in seconds
    #[serde(default = "default_timeout_secs")]
    pub timeout_secs: u64,

    /// Enable write-ahead logging
    #[serde(default = "default_true")]
    pub wal_mode: bool,

    /// PRAGMA synchronous setting: OFF, NORMAL, FULL
    #[serde(default = "default_pragma_synchronous")]
    pub pragma_synchronous: String,

    /// PRAGMA cache_size in pages
    #[serde(default = "default_cache_size")]
    pub pragma_cache_size: i32,
}

/// SurrealDB engine selector — dispatches via `engine::any::connect(url)`.
///
/// Serialized with an internal `engine` tag so TOML records map directly:
/// `{ engine = "surreal_kv", path = ".data/syntaxis/core" }` → `SurrealKv`.
#[cfg(feature = "surrealdb-backend")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "engine", rename_all = "snake_case")]
pub enum SurrealEngineConfig {
    /// In-memory — tests and ephemeral use only
    Mem,
    /// SurrealKV embedded (B-tree) — default for relational data (projects, tasks, phases)
    SurrealKv {
        /// Filesystem path for the SurrealKV database directory
        path: String,
    },
    /// RocksDB embedded (LSM) — default for append-heavy data (audit logs)
    RocksDb {
        /// Filesystem path for the RocksDB database directory
        path: String,
    },
    /// Remote WebSocket — team and cloud deployments
    Ws {
        /// Full WebSocket URL, e.g. `ws://surrealdb.internal:8000`
        url: String,
    },
}

#[cfg(feature = "surrealdb-backend")]
impl SurrealEngineConfig {
    /// Produce the URL string consumed by `surrealdb::engine::any::connect`.
    #[must_use]
    pub fn to_url(&self) -> String {
        match self {
            Self::Mem => "mem://".to_string(),
            Self::SurrealKv { path } => format!("surrealkv://{path}"),
            Self::RocksDb { path } => format!("rocksdb://{path}"),
            Self::Ws { url } => url.clone(),
        }
    }
}

/// Dual-engine SurrealDB backend configuration.
///
/// `core` (default: SurrealKV) stores relational/graph data with a B-tree engine
/// suited for random-access patterns. `hot` (default: RocksDB) stores audit logs
/// and append-heavy data with an LSM engine suited for sequential writes.
#[cfg(feature = "surrealdb-backend")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurrealDbBackendConfig {
    /// Engine for relational data (projects, tasks, phases, checklists)
    #[serde(default = "default_core_engine")]
    pub core: SurrealEngineConfig,
    /// Engine for hot/append data (phase history, future audit embeddings)
    #[serde(default = "default_hot_engine")]
    pub hot: SurrealEngineConfig,
    /// SurrealDB namespace shared by both engines
    #[serde(default = "default_surreal_namespace")]
    pub namespace: String,
    /// Auth username (used for Ws engine only)
    pub username: Option<String>,
    /// Auth password (used for Ws engine only)
    pub password: Option<String>,
}

#[cfg(feature = "surrealdb-backend")]
impl Default for SurrealDbBackendConfig {
    fn default() -> Self {
        Self {
            core: default_core_engine(),
            hot: default_hot_engine(),
            namespace: default_surreal_namespace(),
            username: None,
            password: None,
        }
    }
}

/// PostgreSQL configuration (future support)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostgresConfig {
    /// Connection string
    pub url: String,

    /// Maximum connections
    #[serde(default = "default_postgres_pool_size")]
    pub max_connections: u32,

    /// Connection timeout in seconds
    #[serde(default = "default_timeout_secs")]
    pub timeout_secs: u64,
}

// ── default constructors ─────────────────────────────────────────────────────

fn default_sqlite_pool_size() -> u32 {
    5
}

fn default_postgres_pool_size() -> u32 {
    10
}

fn default_timeout_secs() -> u64 {
    30
}

fn default_true() -> bool {
    true
}

fn default_pragma_synchronous() -> String {
    "NORMAL".to_string()
}

fn default_cache_size() -> i32 {
    2000
}

#[cfg(feature = "surrealdb-backend")]
fn default_core_engine() -> SurrealEngineConfig {
    SurrealEngineConfig::SurrealKv {
        path: ".data/syntaxis/core".to_string(),
    }
}

#[cfg(feature = "surrealdb-backend")]
fn default_hot_engine() -> SurrealEngineConfig {
    SurrealEngineConfig::RocksDb {
        path: ".data/syntaxis/hot".to_string(),
    }
}

#[cfg(feature = "surrealdb-backend")]
fn default_surreal_namespace() -> String {
    "syntaxis".to_string()
}

// ── DatabaseConfig impl ──────────────────────────────────────────────────────

impl DatabaseConfig {
    /// Create a default SQLite configuration.
    pub fn sqlite_default(path: &str) -> Self {
        Self {
            engine: "sqlite".to_string(),
            sqlite: Some(SqliteConfig {
                path: path.to_string(),
                max_connections: default_sqlite_pool_size(),
                timeout_secs: default_timeout_secs(),
                wal_mode: true,
                pragma_synchronous: "NORMAL".to_string(),
                pragma_cache_size: 2000,
            }),
            #[cfg(feature = "surrealdb-backend")]
            surrealdb: None,
            postgresql: None,
        }
    }

    /// Create a default SurrealDB configuration using embedded engines.
    #[cfg(feature = "surrealdb-backend")]
    pub fn surrealdb_default() -> Self {
        Self {
            engine: "surrealdb".to_string(),
            sqlite: None,
            surrealdb: Some(SurrealDbBackendConfig::default()),
            postgresql: None,
        }
    }

    /// Create a WebSocket-connected SurrealDB configuration.
    #[cfg(feature = "surrealdb-backend")]
    pub fn surrealdb_ws(url: &str, username: &str, password: &str) -> Self {
        Self {
            engine: "surrealdb".to_string(),
            sqlite: None,
            surrealdb: Some(SurrealDbBackendConfig {
                core: SurrealEngineConfig::Ws { url: url.to_string() },
                hot: SurrealEngineConfig::Ws { url: url.to_string() },
                namespace: default_surreal_namespace(),
                username: Some(username.to_string()),
                password: Some(password.to_string()),
            }),
            postgresql: None,
        }
    }

    /// Load configuration from a TOML file.
    pub fn load_from_file<P: AsRef<Path>>(path: P) -> crate::error::Result<Self> {
        let contents = std::fs::read_to_string(path)?;
        Self::load_from_str(&contents)
    }

    /// Load configuration from a TOML string.
    pub fn load_from_str(toml: &str) -> crate::error::Result<Self> {
        toml::from_str(toml).map_err(|e| {
            crate::error::LifecycleError::Config(format!("Failed to parse config: {e}"))
        })
    }

    /// Validate that the selected engine has a matching configuration block.
    pub fn validate(&self) -> crate::error::Result<()> {
        match self.engine.as_str() {
            "sqlite" => {
                self.sqlite.as_ref().ok_or_else(|| {
                    crate::error::LifecycleError::Config(
                        "SQLite engine selected but no sqlite config provided".to_string(),
                    )
                })?;
                Ok(())
            }
            "surrealdb" => {
                #[cfg(feature = "surrealdb-backend")]
                {
                    self.surrealdb.as_ref().ok_or_else(|| {
                        crate::error::LifecycleError::Config(
                            "SurrealDB engine selected but no surrealdb config provided"
                                .to_string(),
                        )
                    })?;
                    Ok(())
                }
                #[cfg(not(feature = "surrealdb-backend"))]
                Err(crate::error::LifecycleError::Config(
                    "SurrealDB engine is not compiled in; enable feature 'surrealdb-backend'"
                        .to_string(),
                ))
            }
            "postgresql" => {
                self.postgresql.as_ref().ok_or_else(|| {
                    crate::error::LifecycleError::Config(
                        "PostgreSQL engine selected but no postgresql config provided".to_string(),
                    )
                })?;
                Ok(())
            }
            other => Err(crate::error::LifecycleError::Config(format!(
                "Unknown database engine: {other}"
            ))),
        }
    }

    /// Returns true if SQLite backend is configured.
    pub fn is_sqlite(&self) -> bool {
        self.engine == "sqlite"
    }

    /// Returns true if SurrealDB backend is configured.
    pub fn is_surrealdb(&self) -> bool {
        self.engine == "surrealdb"
    }
}

// ── tests ────────────────────────────────────────────────────────────────────

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

    #[test]
    fn test_sqlite_default_config() {
        let config = DatabaseConfig::sqlite_default("test.db");
        assert!(config.is_sqlite());
        assert!(config.sqlite.is_some());
        assert_eq!(config.sqlite.unwrap().path, "test.db");
    }

    #[test]
    fn test_validate_sqlite() {
        let config = DatabaseConfig::sqlite_default("test.db");
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_validate_invalid_engine() {
        let config = DatabaseConfig {
            engine: "mongodb".to_string(),
            sqlite: None,
            #[cfg(feature = "surrealdb-backend")]
            surrealdb: None,
            postgresql: None,
        };
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_load_from_str() {
        let toml = r#"
engine = "sqlite"

[sqlite]
path = "test.db"
max_connections = 10
"#;
        let config = DatabaseConfig::load_from_str(toml).unwrap();
        assert_eq!(config.engine, "sqlite");
        assert_eq!(config.sqlite.unwrap().max_connections, 10);
    }

    #[test]
    fn test_sqlite_config_defaults() {
        let config = SqliteConfig {
            path: "test.db".to_string(),
            max_connections: default_sqlite_pool_size(),
            timeout_secs: default_timeout_secs(),
            wal_mode: true,
            pragma_synchronous: "NORMAL".to_string(),
            pragma_cache_size: 2000,
        };

        assert_eq!(config.max_connections, 5);
        assert_eq!(config.timeout_secs, 30);
        assert!(config.wal_mode);
    }

    #[cfg(feature = "surrealdb-backend")]
    mod surreal_tests {
        use super::*;

        #[test]
        fn test_surrealdb_default_config() {
            let config = DatabaseConfig::surrealdb_default();
            assert!(config.is_surrealdb());
            assert!(config.surrealdb.is_some());
        }

        #[test]
        fn test_surrealdb_ws_config() {
            let config =
                DatabaseConfig::surrealdb_ws("ws://localhost:8000", "root", "root");
            assert!(config.is_surrealdb());
            let cfg = config.surrealdb.unwrap();
            assert_eq!(cfg.username, Some("root".to_string()));
            assert!(matches!(cfg.core, SurrealEngineConfig::Ws { .. }));
        }

        #[test]
        fn test_engine_to_url_mem() {
            assert_eq!(SurrealEngineConfig::Mem.to_url(), "mem://");
        }

        #[test]
        fn test_engine_to_url_surrealkv() {
            let eng = SurrealEngineConfig::SurrealKv {
                path: "/data/core".to_string(),
            };
            assert_eq!(eng.to_url(), "surrealkv:///data/core");
        }

        #[test]
        fn test_engine_to_url_rocksdb() {
            let eng = SurrealEngineConfig::RocksDb {
                path: "/data/hot".to_string(),
            };
            assert_eq!(eng.to_url(), "rocksdb:///data/hot");
        }

        #[test]
        fn test_engine_to_url_ws() {
            let eng = SurrealEngineConfig::Ws {
                url: "ws://host:8000".to_string(),
            };
            assert_eq!(eng.to_url(), "ws://host:8000");
        }

        #[test]
        fn test_backend_config_default() {
            let cfg = SurrealDbBackendConfig::default();
            assert_eq!(cfg.namespace, "syntaxis");
            assert!(cfg.username.is_none());
            assert!(matches!(cfg.core, SurrealEngineConfig::SurrealKv { .. }));
            assert!(matches!(cfg.hot, SurrealEngineConfig::RocksDb { .. }));
        }

        #[test]
        fn test_validate_surrealdb() {
            let config = DatabaseConfig::surrealdb_default();
            assert!(config.validate().is_ok());
        }

        #[test]
        fn test_validate_surrealdb_missing_config() {
            let config = DatabaseConfig {
                engine: "surrealdb".to_string(),
                sqlite: None,
                surrealdb: None,
                postgresql: None,
            };
            assert!(config.validate().is_err());
        }
    }
}
