use provctl_core::ProvctlResult;
use serde::de::DeserializeOwned;
use std::fs;
use std::path::{Path, PathBuf};

use crate::defaults::Defaults;
use crate::messages::Messages;

/// Loads configuration files from disk.
///
/// This struct handles discovery and loading of TOML configuration files
/// with a predictable search order for configuration discovery.
///
/// # Examples
///
/// ```ignore
/// use provctl_config::ConfigLoader;
///
/// let loader = ConfigLoader::new("/etc/provctl")?;
/// let messages = loader.load_messages()?;
/// let defaults = loader.load_defaults()?;
/// ```
#[derive(Debug, Clone)]
pub struct ConfigLoader {
    /// Base configuration directory path
    config_dir: PathBuf,
}

impl ConfigLoader {
    /// Creates a new configuration loader for the given directory.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let loader = ConfigLoader::new("/etc/provctl")?;
    /// ```
    pub fn new(config_dir: impl AsRef<Path>) -> ProvctlResult<Self> {
        let config_dir = config_dir.as_ref().to_path_buf();

        if !config_dir.exists() {
            return Err(provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::ConfigError {
                    path: config_dir.to_string_lossy().to_string(),
                    reason: "Configuration directory does not exist".to_string(),
                },
                "Creating ConfigLoader".to_string(),
            ));
        }

        Ok(Self { config_dir })
    }

    /// Loads messages configuration from TOML file.
    ///
    /// Searches for `messages.toml` in the configuration directory.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The file does not exist
    /// - The file cannot be read
    /// - The TOML is invalid
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let messages = loader.load_messages()?;
    /// println!("{}", messages.service_start.started);
    /// ```
    pub fn load_messages(&self) -> ProvctlResult<Messages> {
        self.load_toml("messages.toml")
    }

    /// Loads defaults configuration from TOML file.
    ///
    /// Searches for `defaults.toml` in the configuration directory.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The file does not exist
    /// - The file cannot be read
    /// - The TOML is invalid
    /// - Default timeout values are unreasonable
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let defaults = loader.load_defaults()?;
    /// println!("Spawn timeout: {}s", defaults.spawn_timeout_secs);
    /// ```
    pub fn load_defaults(&self) -> ProvctlResult<Defaults> {
        let defaults: Defaults = self.load_toml("defaults.toml")?;

        // Validate reasonable ranges for timeouts
        if defaults.spawn_timeout_secs < 1 {
            return Err(provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::InvalidConfig {
                    detail: "spawn_timeout_secs must be >= 1".to_string(),
                },
                "Validating defaults configuration".to_string(),
            ));
        }

        if defaults.health_check_timeout_secs < 1 {
            return Err(provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::InvalidConfig {
                    detail: "health_check_timeout_secs must be >= 1".to_string(),
                },
                "Validating defaults configuration".to_string(),
            ));
        }

        Ok(defaults)
    }

    /// Generic TOML loader for any deserializable type.
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be read or parsed.
    fn load_toml<T: DeserializeOwned>(&self, filename: &str) -> ProvctlResult<T> {
        let path = self.config_dir.join(filename);

        let content = fs::read_to_string(&path).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::ConfigError {
                    path: path.to_string_lossy().to_string(),
                    reason: format!("Failed to read file: {}", e),
                },
                format!("Loading {}", filename),
            )
        })?;

        toml::from_str(&content).map_err(|e| {
            provctl_core::ProvctlError::new(
                provctl_core::ProvctlErrorKind::ConfigError {
                    path: path.to_string_lossy().to_string(),
                    reason: format!("Invalid TOML: {}", e),
                },
                format!("Parsing {}", filename),
            )
        })
    }
}

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

    #[test]
    fn test_loader_nonexistent_dir() {
        let result = ConfigLoader::new("/nonexistent/config/dir");
        assert!(result.is_err());
    }

    #[test]
    fn test_loader_creation() {
        let tmpdir = tempfile::TempDir::new().unwrap();
        let loader = ConfigLoader::new(tmpdir.path()).unwrap();
        assert!(loader.config_dir.exists());
    }
}
