//! Server runtime configuration.
//!
//! Configuration that varies per deployment (e.g. `DATABASE_URL`,
//! `POLYMODEL_BASE_URL`) and is only needed by the server binary. These values
//! are read from the process environment at startup, not baked into the WASM
//! bundle.
//!
//! The server binary calls `dotenvy::dotenv()` in `main` before
//! [`ServerConfig::load`]. Hydrant reads its own `HYDRANT_*` variables via its
//! own `Config::from_env`; this struct holds only app-level server config and is
//! the single place server-side environment variables are read.

use std::env;

/// App-level server configuration loaded from the environment.
#[derive(Debug, Clone)]
pub struct ServerConfig {
    /// sqlx connect URL, e.g. `sqlite:./data/polymodel.db`.
    pub database_url: String,
    /// Public origin of this server, e.g. `https://polymodel.example.com`.
    ///
    /// The OAuth `client_id` is `{base_url}/oauth-client-metadata.json` and the
    /// redirect URI is `{base_url}/oauth/callback`. When unset, bootstrap
    /// defaults the OAuth client origin to `https://polymodel.space`, a functional
    /// hosted client; set `POLYMODEL_BASE_URL` for local or alternate origins.
    pub base_url: Option<String>,
    /// Local/demo sample-data mode. `auto` seeds conservative local SQLite DBs
    /// only when empty; `force` seeds any SQLite DB when empty; `off` disables it.
    pub sample_data: SampleDataMode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SampleDataMode {
    Auto,
    Off,
    Force,
}

impl SampleDataMode {
    fn from_env(value: Option<String>) -> Self {
        match value.as_deref().map(str::trim).map(str::to_ascii_lowercase) {
            Some(value) if value == "off" => Self::Off,
            Some(value) if value == "force" => Self::Force,
            Some(value) if value == "auto" => Self::Auto,
            Some(value) => {
                tracing::warn!(%value, "unknown POLYMODEL_SAMPLE_DATA value; falling back to off");
                Self::Off
            }
            None => Self::Auto,
        }
    }

    pub fn should_seed(self, database_url: &str) -> bool {
        match self {
            Self::Off => false,
            Self::Force => database_url.starts_with("sqlite:"),
            Self::Auto => is_local_sqlite(database_url),
        }
    }
}

fn is_local_sqlite(database_url: &str) -> bool {
    database_url == "sqlite::memory:"
        || database_url.starts_with("sqlite:./")
        || database_url.starts_with("sqlite:../")
        || database_url.starts_with("sqlite:/")
}

impl ServerConfig {
    /// Read the server config from the process environment.
    ///
    /// Call `dotenvy::dotenv()` first so variables in `.env` are visible. This
    /// is the only place server-side env reads happen.
    pub fn load() -> Self {
        let database_url =
            env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:./data/polymodel.db".to_string());
        let base_url = env::var("POLYMODEL_BASE_URL")
            .ok()
            .map(|s| s.trim().to_owned())
            .filter(|s| !s.is_empty());
        let sample_data = SampleDataMode::from_env(env::var("POLYMODEL_SAMPLE_DATA").ok());
        Self {
            database_url,
            base_url,
            sample_data,
        }
    }
}

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

    #[test]
    fn sample_data_auto_is_limited_to_local_sqlite_urls() {
        assert!(SampleDataMode::Auto.should_seed("sqlite:./data/polymodel.db"));
        assert!(SampleDataMode::Auto.should_seed("sqlite:/tmp/polymodel.db"));
        assert!(!SampleDataMode::Auto.should_seed("postgres://example"));
    }

    #[test]
    fn sample_data_env_parses_known_values() {
        assert_eq!(
            SampleDataMode::from_env(Some("off".into())),
            SampleDataMode::Off
        );
        assert_eq!(
            SampleDataMode::from_env(Some("force".into())),
            SampleDataMode::Force
        );
        assert_eq!(
            SampleDataMode::from_env(Some("auto".into())),
            SampleDataMode::Auto
        );
    }
}
