//! Application configuration for chattyness.
//!
//! Provides TOML-based configuration with sensible defaults for WebSocket
//! timeouts and stale member cleanup.

use serde::Deserialize;
use std::path::Path;

use chattyness_error::AppError;

/// Root configuration structure.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct AppConfig {
    /// WebSocket-related configuration.
    pub websocket: WebSocketConfig,
    /// Stale member cleanup configuration.
    pub cleanup: CleanupConfig,
    /// Signup form configuration.
    pub signup: SignupConfig,
}

/// WebSocket configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct WebSocketConfig {
    /// Timeout for receiving messages from client before considering connection dead (seconds).
    pub recv_timeout_secs: u64,
    /// Interval for client to send ping to keep connection alive (seconds).
    pub client_ping_interval_secs: u64,
}

/// Cleanup configuration for stale instance members.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CleanupConfig {
    /// Interval for running stale member cleanup job (seconds).
    pub reap_interval_secs: u64,
    /// Members with no activity for this duration are considered stale (seconds).
    pub stale_threshold_secs: u64,
    /// Clear all instance_members on server startup (recommended for single-server deployments).
    pub clear_on_startup: bool,
}

/// Signup form configuration.
/// Controls what fields are shown during user registration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct SignupConfig {
    /// Whether to show birthday field during signup.
    /// None = don't ask, Some("ask") = show field
    pub birthday: Option<SignupField>,
    /// How to determine user's age category.
    pub age: AgeConfig,
    /// How to determine user's gender preference.
    pub gender: GenderConfig,
}

/// Signup field option.
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SignupField {
    /// Show the field during signup
    Ask,
}

/// Age category configuration.
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AgeConfig {
    /// Ask user to select their age category
    Ask,
    /// Infer age category from birthday (requires birthday = "ask")
    Infer,
    /// Default to adult without asking
    DefaultAdult,
    /// Default to child without asking
    DefaultChild,
}

/// Gender preference configuration.
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GenderConfig {
    /// Ask user to select their gender preference
    Ask,
    /// Default to neutral without asking
    DefaultNeutral,
    /// Default to male without asking
    DefaultMale,
    /// Default to female without asking
    DefaultFemale,
}

impl Default for AppConfig {
    fn default() -> Self {
        Self {
            websocket: WebSocketConfig::default(),
            cleanup: CleanupConfig::default(),
            signup: SignupConfig::default(),
        }
    }
}

impl Default for WebSocketConfig {
    fn default() -> Self {
        Self {
            recv_timeout_secs: 45,
            client_ping_interval_secs: 30,
        }
    }
}

impl Default for CleanupConfig {
    fn default() -> Self {
        Self {
            reap_interval_secs: 120,
            stale_threshold_secs: 120,
            clear_on_startup: true,
        }
    }
}

impl Default for SignupConfig {
    fn default() -> Self {
        Self {
            birthday: None,
            age: AgeConfig::default(),
            gender: GenderConfig::default(),
        }
    }
}

impl Default for AgeConfig {
    fn default() -> Self {
        Self::DefaultAdult
    }
}

impl Default for GenderConfig {
    fn default() -> Self {
        Self::DefaultNeutral
    }
}

impl AppConfig {
    /// Load configuration from a TOML file.
    ///
    /// If no path is provided, returns default configuration.
    pub fn load(path: Option<&Path>) -> Result<Self, AppError> {
        match path {
            Some(p) => {
                let content = std::fs::read_to_string(p).map_err(|e| {
                    AppError::Internal(format!("Failed to read config file {:?}: {}", p, e))
                })?;
                toml::from_str(&content).map_err(|e| {
                    AppError::Internal(format!("Failed to parse config file {:?}: {}", p, e))
                })
            }
            None => Ok(Self::default()),
        }
    }
}
