//! Runtime-tunable settings loaded from `settings.toml`.
//!
//! Bootstrap config (port, data paths, log paths, auth) lives in [`crate::config::Config`].
//! Edits to `settings.toml` take effect on the next server start — hot
//! reload is a follow-up plan.

use serde::Deserialize;

#[derive(Debug, Clone, Default, Deserialize)]
pub struct Settings {
    #[serde(default)]
    pub retention: RetentionSettings,
    #[serde(default)]
    pub whisper: WhisperSettings,
    #[serde(default)]
    pub canary: CanarySettings,
    #[serde(default)]
    pub ollama: OllamaSettings,
    /// Legacy `[llm]` section. Backend definitions now live in
    /// `analyze::backend` and the API key comes from `IONOS_API_KEY`
    /// (or the equivalent env var of another provider). Kept here only
    /// so existing `settings.toml` files with a `[llm]` block still
    /// load — the value is parsed and discarded.
    #[serde(default, rename = "llm")]
    _legacy_llm: Option<toml::Value>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct RetentionSettings {
    #[serde(default = "default_retention_audio_days")]
    pub audio_days: u32,
    #[serde(default = "default_retention_transcript_days")]
    pub transcript_days: u32,
    #[serde(default = "default_retention_document_days")]
    pub document_days: u32,
}

impl Default for RetentionSettings {
    fn default() -> Self {
        Self {
            audio_days: default_retention_audio_days(),
            transcript_days: default_retention_transcript_days(),
            document_days: default_retention_document_days(),
        }
    }
}

fn default_retention_audio_days() -> u32 {
    30
}
fn default_retention_transcript_days() -> u32 {
    30
}
fn default_retention_document_days() -> u32 {
    0
}

#[derive(Debug, Clone, Deserialize)]
pub struct WhisperSettings {
    #[serde(default = "default_whisper_url")]
    pub url: String,
    #[serde(default = "default_whisper_timeout_seconds")]
    pub timeout_seconds: u64,
}

impl Default for WhisperSettings {
    fn default() -> Self {
        Self {
            url: default_whisper_url(),
            timeout_seconds: default_whisper_timeout_seconds(),
        }
    }
}

fn default_whisper_url() -> String {
    "http://localhost:10300".into()
}
fn default_whisper_timeout_seconds() -> u64 {
    120
}

/// Endpoint for the optional NeMo Canary container service. Active only
/// when `ASR_BACKEND=canary` is set in `.env`. Both this block and
/// `[whisper]` are always loaded; the worker reads only the active one.
#[derive(Debug, Clone, Deserialize)]
pub struct CanarySettings {
    #[serde(default = "default_canary_url")]
    pub url: String,
    #[serde(default = "default_canary_timeout_seconds")]
    pub timeout_seconds: u64,
}

impl Default for CanarySettings {
    fn default() -> Self {
        Self {
            url: default_canary_url(),
            timeout_seconds: default_canary_timeout_seconds(),
        }
    }
}

fn default_canary_url() -> String {
    "http://localhost:9002".into()
}
/// Higher than Whisper's 120s default: Canary's buffered pipeline for
/// audio >25s adds chunk-stitching cost (see docs/canary.md §4.1), so
/// long dictations need a wider safety margin.
fn default_canary_timeout_seconds() -> u64 {
    180
}

#[derive(Debug, Clone, Deserialize)]
pub struct OllamaSettings {
    #[serde(default = "default_ollama_url")]
    pub url: String,
    #[serde(default = "default_ollama_model")]
    pub model: String,
    #[serde(default = "default_ollama_keep_alive")]
    pub keep_alive: u32,
}

impl Default for OllamaSettings {
    fn default() -> Self {
        Self {
            url: default_ollama_url(),
            model: default_ollama_model(),
            keep_alive: default_ollama_keep_alive(),
        }
    }
}

fn default_ollama_url() -> String {
    "http://localhost:11434".into()
}
fn default_ollama_model() -> String {
    "gemma3:4b".into()
}
fn default_ollama_keep_alive() -> u32 {
    0
}

impl Settings {
    /// Missing file → defaults + warn. Parse error → panic (startup-fail-fast).
    pub fn load_or_default(path: &str) -> Self {
        match std::fs::read_to_string(path) {
            Ok(content) => {
                let parsed: Settings = toml::from_str(&content)
                    .unwrap_or_else(|e| panic!("Failed to parse {path}: {e}"));
                tracing::info!(path, "settings loaded");
                parsed
            }
            Err(_) => {
                tracing::warn!(path, "settings file not found — using code defaults");
                Self::default()
            }
        }
    }
}

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

    #[test]
    fn parses_full_toml() {
        let s: Settings = toml::from_str(
            r#"
[whisper]
url = "http://h:1"
timeout_seconds = 60

[canary]
url = "http://h:9"
timeout_seconds = 240

[ollama]
url = "http://h:2"
model = "x"
keep_alive = 5
"#,
        )
        .unwrap();
        assert_eq!(s.whisper.url, "http://h:1");
        assert_eq!(s.whisper.timeout_seconds, 60);
        assert_eq!(s.canary.url, "http://h:9");
        assert_eq!(s.canary.timeout_seconds, 240);
        assert_eq!(s.ollama.keep_alive, 5);
    }

    #[test]
    fn empty_toml_yields_defaults() {
        let s: Settings = toml::from_str("").unwrap();
        assert_eq!(s.whisper.url, "http://localhost:10300");
        assert_eq!(s.whisper.timeout_seconds, 120);
        assert_eq!(s.canary.url, "http://localhost:9002");
        assert_eq!(s.canary.timeout_seconds, 180);
        assert_eq!(s.ollama.url, "http://localhost:11434");
        assert_eq!(s.ollama.model, "gemma3:4b");
    }

    #[test]
    fn partial_toml_fills_in_defaults() {
        let s: Settings = toml::from_str(
            r#"
[whisper]
url = "http://only-whisper"
"#,
        )
        .unwrap();
        assert_eq!(s.whisper.url, "http://only-whisper");
        assert_eq!(s.whisper.timeout_seconds, 120);
        assert_eq!(s.ollama.url, "http://localhost:11434");
    }

    /// Old `settings.toml` files that still carry an `[llm]` block must
    /// continue to parse — the section is read into a discarded
    /// `_legacy_llm` field rather than rejected.
    #[test]
    fn legacy_llm_section_is_tolerated() {
        let s: Settings = toml::from_str(
            r#"
[llm]
url = "http://h:3"
api_key = "k"
model = "m"
temperature = 0.5
timeout_seconds = 30
system_prompt = "p"
"#,
        )
        .unwrap();
        // Other sections still default.
        assert_eq!(s.whisper.url, "http://localhost:10300");
    }
}
