//! Tests for enum support in cfgmatic configuration.
//!
//! This test file verifies that cfgmatic correctly handles structs
//! containing enum fields, both with and without #[config(...)] attributes.

use cfgmatic::{Config, ConfigFields, ConfigLoader};
use serde::{Deserialize, Serialize};

/// Test configuration containing an enum field.
#[derive(Debug, Clone, Deserialize, Serialize, Config, Default)]
#[config(prefix = "TEST")]
struct EnumConfig {
    /// Simple field with config attribute.
    #[config(default = "8080")]
    pub port: u16,

    /// Enum field WITHOUT config attribute - should be skipped by macro.
    pub mode: OperationMode,

    /// Another enum field WITHOUT config attribute.
    pub level: LogLevel,
}

/// Enum representing operation modes.
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
enum OperationMode {
    /// Default mode.
    #[default]
    Standard,
    /// High performance mode.
    Performance,
    /// Low power mode.
    Eco,
}

/// Enum for logging levels.
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
enum LogLevel {
    /// Trace level.
    Trace,
    /// Debug level.
    Debug,
    /// Info level.
    #[default]
    Info,
    /// Warning level.
    Warn,
    /// Error level.
    Error,
}

/// Test configuration with enum that implements `FromStr`.
///
/// This demonstrates using #[config(...)] with enum fields that
/// can be parsed from environment variables.
#[derive(Debug, Clone, Config, Default)]
#[config(prefix = "APP")]
struct EnumFromStrConfig {
    /// Simple field.
    #[config(default = "true")]
    pub enabled: bool,

    /// Enum field with `FromStr` and config attribute.
    #[config(default = "info")]
    pub log_level: LogLevelFromStr,
}

/// Enum that implements `FromStr` for environment variable parsing.
#[derive(Debug, Clone, PartialEq, Default)]
enum LogLevelFromStr {
    #[default]
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}

impl std::str::FromStr for LogLevelFromStr {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "trace" => Ok(Self::Trace),
            "debug" => Ok(Self::Debug),
            "info" => Ok(Self::Info),
            "warn" | "warning" => Ok(Self::Warn),
            "error" => Ok(Self::Error),
            _ => Err(format!("Invalid log level: {s}")),
        }
    }
}

/// Configuration with nested struct containing enum.
#[derive(Debug, Clone, Config, Default)]
#[config(prefix = "NESTED")]
struct NestedEnumConfig {
    /// Simple field.
    #[config(default = "42")]
    pub timeout: u32,

    /// Nested struct WITHOUT config attribute - should be skipped.
    pub server: ServerConfig,
}

/// Nested struct with enum field.
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub mode: OperationMode,
}

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

    #[test]
    fn test_enum_field_without_config_attr() {
        // This test verifies that enum fields without #[config(...)]
        // are properly skipped by the macro.
        // Use from_env() to apply defaults from #[config(default = "...")]
        let config = EnumConfig::from_env().unwrap();

        // Default values from #[config(default = "...")] should be applied
        assert_eq!(config.port, 8080);
        // Enum fields without #[config(...)] should retain their Rust Default
        assert_eq!(config.mode, OperationMode::Standard);
        assert_eq!(config.level, LogLevel::Info);
    }

    #[test]
    fn test_enum_from_env_with_default() {
        // Test loading config with enum that has FromStr
        let config = EnumFromStrConfig::from_env().unwrap();

        assert!(config.enabled);
        assert_eq!(config.log_level, LogLevelFromStr::Info);
    }

    #[test]
    fn test_load_from_env_respects_prefix() {
        // Test that the env_prefix method returns the correct prefix
        assert_eq!(EnumFromStrConfig::env_prefix(), "APP");
        assert_eq!(EnumConfig::env_prefix(), "TEST");
        assert_eq!(NestedEnumConfig::env_prefix(), "NESTED");
    }

    #[test]
    fn test_nested_struct_with_enum() {
        // Test that nested structs containing enums are skipped
        let config = NestedEnumConfig::from_env().unwrap();

        assert_eq!(config.timeout, 42);
        assert_eq!(config.server.host, "");
        assert_eq!(config.server.port, 0);
        assert_eq!(config.server.mode, OperationMode::Standard);
    }

    #[test]
    fn test_multiple_enums_in_one_struct() {
        // Test struct with multiple enum fields
        #[derive(Debug, Clone, Config, Default)]
        #[config(prefix = "MULTI")]
        struct MultiEnumConfig {
            #[config(default = "1")]
            value: u32,
            mode: OperationMode,
            level: LogLevel,
        }

        let config = MultiEnumConfig::from_env().unwrap();
        assert_eq!(config.value, 1);
        assert_eq!(config.mode, OperationMode::Standard);
        assert_eq!(config.level, LogLevel::Info);
    }

    #[test]
    fn test_enum_from_str_valid_values() {
        // Test that FromStr implementation works correctly
        assert_eq!(
            "info".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Info
        );
        assert_eq!(
            "debug".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Debug
        );
        assert_eq!(
            "warn".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Warn
        );
        assert_eq!(
            "warning".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Warn
        );
        assert_eq!(
            "error".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Error
        );
        assert_eq!(
            "trace".parse::<LogLevelFromStr>().unwrap(),
            LogLevelFromStr::Trace
        );
    }

    #[test]
    fn test_enum_from_str_invalid_value() {
        // Test that invalid enum values produce proper errors
        let result: Result<LogLevelFromStr, _> = "invalid".parse();
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Invalid log level"));
    }

    #[test]
    fn test_config_fields_trait_is_implemented() {
        // Verify that ConfigFields trait is implemented
        let mut config = EnumFromStrConfig::default();

        // This should not panic - load_from_env should work even with no env vars
        let result = config.load_from_env(None);
        assert!(result.is_ok());
    }

    #[test]
    fn test_config_fields_with_custom_prefix() {
        // Test load_from_env with custom prefix override
        let mut config = EnumFromStrConfig::default();

        // Loading with custom prefix should work (even if no vars are set)
        let result = config.load_from_env(Some("CUSTOM"));
        assert!(result.is_ok());

        // Values should remain at defaults since no CUSTOM__LOG_LEVEL is set
        assert_eq!(config.log_level, LogLevelFromStr::Info);
    }
}
