//! Configuration management for Things 3 integration

use crate::error::{Result, ThingsError};
use std::path::{Path, PathBuf};

/// Configuration for Things 3 database access
#[derive(Debug, Clone)]
pub struct ThingsConfig {
    /// Path to the Things 3 database
    pub database_path: PathBuf,
    /// Whether to use the default database path if the specified path doesn't exist
    pub fallback_to_default: bool,
}

impl ThingsConfig {
    /// Create a new configuration with a custom database path
    ///
    /// # Arguments
    /// * `database_path` - Path to the Things 3 database
    /// * `fallback_to_default` - Whether to fall back to the default path if the specified path doesn't exist
    #[must_use]
    pub fn new<P: AsRef<Path>>(database_path: P, fallback_to_default: bool) -> Self {
        Self {
            database_path: database_path.as_ref().to_path_buf(),
            fallback_to_default,
        }
    }

    /// Create a configuration with the default database path
    #[must_use]
    pub fn with_default_path() -> Self {
        Self {
            database_path: Self::get_default_database_path(),
            fallback_to_default: false,
        }
    }

    /// Get the effective database path, falling back to default if needed
    ///
    /// # Errors
    /// Returns `ThingsError::Message` if neither the specified path nor the default path exists
    pub fn get_effective_database_path(&self) -> Result<PathBuf> {
        // Check if the specified path exists
        if self.database_path.exists() {
            return Ok(self.database_path.clone());
        }

        // If fallback is enabled, try the default path
        if self.fallback_to_default {
            let default_path = Self::get_default_database_path();
            if default_path.exists() {
                return Ok(default_path);
            }
        }

        Err(ThingsError::configuration(format!(
            "Database not found at {} and fallback is {}",
            self.database_path.display(),
            if self.fallback_to_default {
                "enabled but default path also not found"
            } else {
                "disabled"
            }
        )))
    }

    /// Get the default Things 3 database path
    #[must_use]
    pub fn get_default_database_path() -> PathBuf {
        let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
        PathBuf::from(format!(
            "{home}/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/ThingsData-0Z0Z2/Things Database.thingsdatabase/main.sqlite"
        ))
    }

    /// Create configuration from environment variables
    ///
    /// Reads the database path from `THINGS_DB_PATH` (preferred) or the legacy
    /// `THINGS_DATABASE_PATH`, and the fallback flag from `THINGS_FALLBACK_TO_DEFAULT`.
    #[must_use]
    pub fn from_env() -> Self {
        let database_path = match std::env::var("THINGS_DB_PATH") {
            Ok(v) => PathBuf::from(v),
            Err(_) => match std::env::var("THINGS_DATABASE_PATH") {
                Ok(v) => {
                    tracing::warn!(
                        "THINGS_DATABASE_PATH is deprecated; please use THINGS_DB_PATH instead"
                    );
                    PathBuf::from(v)
                }
                Err(_) => Self::get_default_database_path(),
            },
        };

        let fallback_to_default = if let Ok(v) = std::env::var("THINGS_FALLBACK_TO_DEFAULT") {
            let lower = v.to_lowercase();
            match lower.as_str() {
                "true" | "1" | "yes" | "on" => true,
                _ => false, // Default to false for invalid values
            }
        } else {
            true
        };

        Self::new(database_path, fallback_to_default)
    }

    /// Create configuration for testing with a temporary database
    ///
    /// # Errors
    /// Returns `ThingsError::Io` if the temporary file cannot be created
    pub fn for_testing() -> Result<Self> {
        use tempfile::NamedTempFile;
        let temp_file = NamedTempFile::new()?;
        let db_path = temp_file.path().to_path_buf();
        Ok(Self::new(db_path, false))
    }
}

impl Default for ThingsConfig {
    fn default() -> Self {
        Self::with_default_path()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serial_test::serial;
    use tempfile::NamedTempFile;

    #[test]
    fn test_config_creation() {
        let config = ThingsConfig::new("/path/to/db.sqlite", true);
        assert_eq!(config.database_path, PathBuf::from("/path/to/db.sqlite"));
        assert!(config.fallback_to_default);
    }

    #[test]
    fn test_default_config() {
        let config = ThingsConfig::default();
        assert!(config
            .database_path
            .to_string_lossy()
            .contains("Things Database.thingsdatabase"));
        assert!(!config.fallback_to_default);
    }

    #[test]
    #[serial]
    fn test_config_from_env() {
        let test_path = "/custom/path/db.sqlite";

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::set_var("THINGS_DATABASE_PATH", test_path);
        std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", "true");

        let config = ThingsConfig::from_env();
        let path_matches = config.database_path.as_os_str() == test_path;
        let fallback_set = config.fallback_to_default;

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        } else {
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");
        }

        assert!(path_matches);
        assert!(fallback_set);
    }

    #[test]
    #[serial]
    fn test_from_env_reads_things_db_path() {
        let test_path = "/custom/path/via_db_path.sqlite";

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();

        std::env::remove_var("THINGS_DATABASE_PATH");
        std::env::set_var("THINGS_DB_PATH", test_path);

        let config = ThingsConfig::from_env();
        assert_eq!(config.database_path, PathBuf::from(test_path));

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        } else {
            std::env::remove_var("THINGS_DB_PATH");
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        }
    }

    #[test]
    #[serial]
    fn test_from_env_prefers_things_db_path_over_legacy() {
        let new_path = "/new/preferred.sqlite";
        let legacy_path = "/legacy/ignored.sqlite";

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();

        std::env::set_var("THINGS_DB_PATH", new_path);
        std::env::set_var("THINGS_DATABASE_PATH", legacy_path);

        let config = ThingsConfig::from_env();
        assert_eq!(config.database_path, PathBuf::from(new_path));

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        } else {
            std::env::remove_var("THINGS_DB_PATH");
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
    }

    #[test]
    #[serial]
    fn test_from_env_falls_back_to_legacy_things_database_path() {
        let legacy_path = "/legacy/only.sqlite";

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::set_var("THINGS_DATABASE_PATH", legacy_path);

        let config = ThingsConfig::from_env();
        assert_eq!(config.database_path, PathBuf::from(legacy_path));

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
    }

    #[test]
    fn test_effective_database_path() {
        // Test with existing file
        let temp_file = NamedTempFile::new().unwrap();
        let db_path = temp_file.path();
        let config = ThingsConfig::new(db_path, false);

        let effective_path = config.get_effective_database_path().unwrap();
        assert_eq!(effective_path, db_path);
    }

    #[test]
    fn test_fallback_behavior() {
        // Test fallback when it should succeed (default path exists)
        let config = ThingsConfig::new("/nonexistent/path.sqlite", true);
        let result = config.get_effective_database_path();

        // If the default path exists, fallback should succeed
        if ThingsConfig::get_default_database_path().exists() {
            assert!(result.is_ok());
            assert_eq!(result.unwrap(), ThingsConfig::get_default_database_path());
        } else {
            // If default path doesn't exist, should get an error
            assert!(result.is_err());
        }
    }

    #[test]
    fn test_fallback_disabled() {
        // Test when fallback is disabled - should always fail if path doesn't exist
        let config = ThingsConfig::new("/nonexistent/path.sqlite", false);
        let result = config.get_effective_database_path();

        // Should always fail when fallback is disabled and path doesn't exist
        assert!(result.is_err());
    }

    #[test]
    fn test_config_with_fallback_enabled() {
        let config = ThingsConfig::new("/nonexistent/path", true);
        assert_eq!(config.database_path, PathBuf::from("/nonexistent/path"));
        assert!(config.fallback_to_default);
    }

    #[test]
    #[serial]
    fn test_config_from_env_with_custom_path() {
        let test_path = "/test/env/custom/path";

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::set_var("THINGS_DATABASE_PATH", test_path);
        std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", "false");

        let config = ThingsConfig::from_env();
        let path_matches = config.database_path.as_os_str() == test_path;
        let fallback_off = !config.fallback_to_default;

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        } else {
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");
        }

        assert!(path_matches);
        assert!(fallback_off);
    }

    #[test]
    #[serial]
    fn test_config_from_env_with_fallback() {
        let test_id = std::thread::current().id();
        let test_path = format!("/test/env/path/fallback_{test_id:?}");

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::set_var("THINGS_DATABASE_PATH", &test_path);
        std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", "true");

        let config = ThingsConfig::from_env();
        let path_matches =
            config.database_path.to_string_lossy() == PathBuf::from(&test_path).to_string_lossy();
        let fallback_set = config.fallback_to_default;

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        } else {
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");
        }

        assert!(path_matches);
        assert!(fallback_set);
    }

    #[test]
    #[serial]
    fn test_config_from_env_with_invalid_fallback() {
        let test_id = std::thread::current().id();
        let test_path = format!("/test/env/path/invalid_{test_id:?}");

        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::set_var("THINGS_DATABASE_PATH", &test_path);
        std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", "invalid");

        let config = ThingsConfig::from_env();
        let path_matches =
            config.database_path.to_string_lossy() == PathBuf::from(&test_path).to_string_lossy();
        let fallback_off = !config.fallback_to_default;

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        } else {
            std::env::remove_var("THINGS_DATABASE_PATH");
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        } else {
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");
        }

        assert!(path_matches);
        assert!(fallback_off);
    }

    #[test]
    fn test_config_debug_formatting() {
        let config = ThingsConfig::new("/test/path", true);
        let debug_str = format!("{config:?}");
        assert!(debug_str.contains("/test/path"));
        assert!(debug_str.contains("true"));
    }

    #[test]
    fn test_config_clone() {
        let config1 = ThingsConfig::new("/test/path", true);
        let config2 = config1.clone();

        assert_eq!(config1.database_path, config2.database_path);
        assert_eq!(config1.fallback_to_default, config2.fallback_to_default);
    }

    #[test]
    fn test_config_with_different_path_types() {
        // Test with relative path
        let config = ThingsConfig::new("relative/path", false);
        assert_eq!(config.database_path, PathBuf::from("relative/path"));

        // Test with absolute path
        let config = ThingsConfig::new("/absolute/path", false);
        assert_eq!(config.database_path, PathBuf::from("/absolute/path"));

        // Test with current directory
        let config = ThingsConfig::new(".", false);
        assert_eq!(config.database_path, PathBuf::from("."));
    }

    #[test]
    fn test_config_edge_cases() {
        // Test with empty string path
        let config = ThingsConfig::new("", false);
        assert_eq!(config.database_path, PathBuf::from(""));

        // Test with very long path
        let long_path = "/".repeat(1000);
        let config = ThingsConfig::new(&long_path, false);
        assert_eq!(config.database_path, PathBuf::from(&long_path));
    }

    #[test]
    fn test_get_default_database_path() {
        let default_path = ThingsConfig::get_default_database_path();

        // Should be a valid path (may or may not exist)
        assert!(!default_path.to_string_lossy().is_empty());

        // Should be a reasonable path (may or may not contain "Things3" depending on system)
        assert!(!default_path.to_string_lossy().is_empty());
    }

    #[test]
    fn test_for_testing() {
        // Test that for_testing creates a valid config
        let config = ThingsConfig::for_testing().unwrap();

        // Should have a valid database path
        assert!(!config.database_path.to_string_lossy().is_empty());

        // Should not have fallback enabled (as specified in the method)
        assert!(!config.fallback_to_default);

        // The path should be a valid file path (even if it doesn't exist yet)
        assert!(config.database_path.parent().is_some());
    }

    #[test]
    fn test_with_default_path() {
        let config = ThingsConfig::with_default_path();

        // Should use the default database path
        assert_eq!(
            config.database_path,
            ThingsConfig::get_default_database_path()
        );

        // Should not have fallback enabled
        assert!(!config.fallback_to_default);
    }

    #[test]
    fn test_effective_database_path_fallback_enabled_but_default_missing() {
        // Test the error case when fallback is enabled but default path doesn't exist
        let config = ThingsConfig::new("/nonexistent/path.sqlite", true);
        let result = config.get_effective_database_path();

        // Check if the default path exists - if it does, fallback will succeed
        let default_path = ThingsConfig::get_default_database_path();
        if default_path.exists() {
            // If default path exists, fallback should succeed
            assert!(result.is_ok());
            assert_eq!(result.unwrap(), default_path);
        } else {
            // If default path doesn't exist, should get an error
            assert!(result.is_err());
            let error = result.unwrap_err();
            match error {
                ThingsError::Configuration { message } => {
                    assert!(message.contains("Database not found at"));
                    assert!(message.contains("fallback is enabled but default path also not found"));
                }
                _ => panic!("Expected Configuration error, got: {error:?}"),
            }
        }
    }

    #[test]
    fn test_effective_database_path_fallback_disabled_error_message() {
        // Test the error case when fallback is disabled
        let config = ThingsConfig::new("/nonexistent/path.sqlite", false);
        let result = config.get_effective_database_path();

        // Should get an error with specific message about fallback being disabled
        assert!(result.is_err());
        let error = result.unwrap_err();
        match error {
            ThingsError::Configuration { message } => {
                assert!(message.contains("Database not found at"));
                assert!(message.contains("fallback is disabled"));
            }
            _ => panic!("Expected Configuration error, got: {error:?}"),
        }
    }

    #[test]
    #[serial]
    fn test_from_env_without_variables() {
        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::remove_var("THINGS_DATABASE_PATH");
        std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");

        let config = ThingsConfig::from_env();
        let expected_path = ThingsConfig::get_default_database_path();

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        }

        assert_eq!(config.database_path, expected_path);
        assert!(config.fallback_to_default);
    }

    #[test]
    fn test_from_env_fallback_parsing() {
        // Test various fallback value parsing without environment variable conflicts
        let test_cases = vec![
            ("true", true),
            ("TRUE", true),
            ("True", true),
            ("1", true),
            ("yes", true),
            ("YES", true),
            ("on", true),
            ("ON", true),
            ("false", false),
            ("FALSE", false),
            ("0", false),
            ("no", false),
            ("off", false),
            ("invalid", false),
            ("", false),
        ];

        for (value, expected) in test_cases {
            // Create a config manually to test the parsing logic
            let fallback = value.to_lowercase();
            let result =
                fallback == "true" || fallback == "1" || fallback == "yes" || fallback == "on";
            assert_eq!(result, expected, "Failed for value: '{value}'");
        }
    }

    #[test]
    fn test_default_trait_implementation() {
        // Test that Default trait works correctly
        let config = ThingsConfig::default();

        // Should be equivalent to with_default_path
        let expected = ThingsConfig::with_default_path();
        assert_eq!(config.database_path, expected.database_path);
        assert_eq!(config.fallback_to_default, expected.fallback_to_default);
    }

    #[test]
    fn test_config_with_path_reference() {
        // Test that the config works with different path reference types
        let path_str = "/test/path/string";
        let path_buf = PathBuf::from("/test/path/buf");

        let config1 = ThingsConfig::new(path_str, true);
        let config2 = ThingsConfig::new(&path_buf, false);

        assert_eq!(config1.database_path, PathBuf::from(path_str));
        assert_eq!(config2.database_path, path_buf);
    }

    #[test]
    fn test_effective_database_path_existing_file() {
        // Test when the specified path exists
        let temp_file = NamedTempFile::new().unwrap();
        let db_path = temp_file.path().to_path_buf();
        let config = ThingsConfig::new(&db_path, false);

        let effective_path = config.get_effective_database_path().unwrap();
        assert_eq!(effective_path, db_path);
    }

    #[test]
    fn test_effective_database_path_fallback_success() {
        // Test successful fallback when default path exists
        let default_path = ThingsConfig::get_default_database_path();

        // Only test if default path actually exists
        if default_path.exists() {
            let config = ThingsConfig::new("/nonexistent/path.sqlite", true);
            let effective_path = config.get_effective_database_path().unwrap();
            assert_eq!(effective_path, default_path);
        }
    }

    #[test]
    fn test_config_debug_implementation() {
        // Test that Debug trait is properly implemented
        let config = ThingsConfig::new("/test/debug/path", true);
        let debug_str = format!("{config:?}");

        // Should contain both fields
        assert!(debug_str.contains("database_path"));
        assert!(debug_str.contains("fallback_to_default"));
        assert!(debug_str.contains("/test/debug/path"));
        assert!(debug_str.contains("true"));
    }

    #[test]
    fn test_config_clone_implementation() {
        // Test that Clone trait works correctly
        let config1 = ThingsConfig::new("/test/clone/path", true);
        let config2 = config1.clone();

        // Should be equal
        assert_eq!(config1.database_path, config2.database_path);
        assert_eq!(config1.fallback_to_default, config2.fallback_to_default);

        // Should be independent (modifying one doesn't affect the other)
        let config3 = ThingsConfig::new("/different/path", false);
        assert_ne!(config1.database_path, config3.database_path);
        assert_ne!(config1.fallback_to_default, config3.fallback_to_default);
    }

    #[test]
    fn test_get_default_database_path_format() {
        // Test that the default path has the expected format
        let default_path = ThingsConfig::get_default_database_path();
        let path_str = default_path.to_string_lossy();

        // Should contain the expected macOS Things 3 path components
        assert!(path_str.contains("Library"));
        assert!(path_str.contains("Group Containers"));
        assert!(path_str.contains("JLMPQHK86H.com.culturedcode.ThingsMac"));
        assert!(path_str.contains("ThingsData-0Z0Z2"));
        assert!(path_str.contains("Things Database.thingsdatabase"));
        assert!(path_str.contains("main.sqlite"));
    }

    #[test]
    fn test_home_env_var_fallback() {
        // Test that the default path handles missing HOME environment variable
        // This is tricky to test without affecting the environment, so we'll test the logic indirectly
        let default_path = ThingsConfig::get_default_database_path();
        let path_str = default_path.to_string_lossy();

        // Should start with either a valid home path or "~" fallback
        assert!(path_str.starts_with('/') || path_str.starts_with('~'));
    }

    #[test]
    fn test_config_effective_database_path_existing_file() {
        // Create a temporary file for testing
        let temp_dir = std::env::temp_dir();
        let temp_file = temp_dir.join("test_db.sqlite");
        std::fs::File::create(&temp_file).unwrap();

        let config = ThingsConfig::new(temp_file.clone(), false);
        let effective_path = config.get_effective_database_path().unwrap();
        assert_eq!(effective_path, temp_file);

        // Clean up
        std::fs::remove_file(&temp_file).unwrap();
    }

    #[test]
    fn test_config_effective_database_path_fallback_success() {
        // Create a temporary file to simulate an existing database
        let temp_dir = std::env::temp_dir();
        let temp_file = temp_dir.join("test_database.sqlite");
        std::fs::File::create(&temp_file).unwrap();

        // Create a config with the temp file as the database path
        let config = ThingsConfig::new(temp_file.clone(), true);

        let effective_path = config.get_effective_database_path().unwrap();

        // Should return the existing file path
        assert_eq!(effective_path, temp_file);

        // Clean up
        std::fs::remove_file(&temp_file).unwrap();
    }

    #[test]
    fn test_config_effective_database_path_fallback_disabled_error_message() {
        let non_existent_path = PathBuf::from("/nonexistent/path/db.sqlite");
        let config = ThingsConfig::new(non_existent_path, false);

        // This should return an error when fallback is disabled and path doesn't exist
        let result = config.get_effective_database_path();
        assert!(result.is_err());
        let error = result.unwrap_err();
        assert!(matches!(error, ThingsError::Configuration { .. }));
    }

    #[test]
    #[serial]
    fn test_config_effective_database_path_fallback_enabled_but_default_missing() {
        // Temporarily change HOME to a non-existent directory to ensure default path doesn't exist
        let original_home = std::env::var("HOME").ok();
        std::env::set_var("HOME", "/nonexistent/home");

        // Create a config with a non-existent path and fallback enabled
        let non_existent_path = PathBuf::from("/nonexistent/path/db.sqlite");
        let config = ThingsConfig::new(non_existent_path, true);

        // This should return an error when both the configured path and default path don't exist
        let result = config.get_effective_database_path();

        // Restore original HOME
        if let Some(home) = original_home {
            std::env::set_var("HOME", home);
        } else {
            std::env::remove_var("HOME");
        }

        assert!(
            result.is_err(),
            "Expected error when both configured and default paths don't exist"
        );
        let error = result.unwrap_err();
        assert!(matches!(error, ThingsError::Configuration { .. }));

        // Check the error message contains the expected text
        let error_message = format!("{error}");
        assert!(error_message.contains("Database not found at /nonexistent/path/db.sqlite"));
        assert!(error_message.contains("fallback is enabled but default path also not found"));
    }

    #[test]
    fn test_config_fallback_behavior() {
        let path = PathBuf::from("/test/path/db.sqlite");

        // Test with fallback enabled
        let config_with_fallback = ThingsConfig::new(path.clone(), true);
        assert!(config_with_fallback.fallback_to_default);

        // Test with fallback disabled
        let config_without_fallback = ThingsConfig::new(path, false);
        assert!(!config_without_fallback.fallback_to_default);
    }

    #[test]
    fn test_config_fallback_disabled() {
        let path = PathBuf::from("/test/path/db.sqlite");
        let config = ThingsConfig::new(path, false);
        assert!(!config.fallback_to_default);
    }

    #[test]
    #[serial]
    fn test_config_from_env_without_variables() {
        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();
        let original_fallback = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::remove_var("THINGS_DATABASE_PATH");
        std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");

        let config = ThingsConfig::from_env();
        let contains_default = config
            .database_path
            .to_string_lossy()
            .contains("Things Database.thingsdatabase");

        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        }
        if let Some(v) = original_fallback {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", v);
        }

        assert!(contains_default);
    }

    #[test]
    fn test_config_from_env_fallback_parsing() {
        // Test the parsing logic directly without relying on environment variables
        // This avoids potential race conditions or environment variable isolation issues in CI

        let test_cases = vec![
            ("true", true),
            ("false", false),
            ("1", true),
            ("0", false),
            ("yes", true),
            ("no", false),
            ("invalid", false),
        ];

        for (value, expected) in test_cases {
            // Test the parsing logic directly
            let lower = value.to_lowercase();
            let result = match lower.as_str() {
                "true" | "1" | "yes" | "on" => true,
                _ => false, // Default to false for invalid values
            };

            assert_eq!(
                result, expected,
                "Failed for value: '{value}', expected: {expected}, got: {result}"
            );
        }
    }

    #[test]
    fn test_config_for_testing() {
        let result = ThingsConfig::for_testing();
        assert!(result.is_ok(), "Should create test config successfully");

        let config = result.unwrap();
        assert!(
            !config.fallback_to_default,
            "Test config should have fallback disabled"
        );

        // Test config should use a temporary database path
        let path_str = config.database_path.to_string_lossy();
        assert!(
            path_str.contains("tmp") || !path_str.is_empty(),
            "Test config should use a temporary path"
        );
    }

    #[test]
    fn test_config_effective_database_path_error_cases() {
        // Test with non-existent path and fallback disabled
        let non_existent_path = PathBuf::from("/absolutely/non/existent/path/database.db");
        let config = ThingsConfig::new(&non_existent_path, false);

        let result = config.get_effective_database_path();
        assert!(
            result.is_err(),
            "Should fail when file doesn't exist and fallback is disabled"
        );

        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("fallback is disabled"),
            "Error message should mention fallback is disabled"
        );
    }

    #[test]
    fn test_config_effective_database_path_with_existing_file() {
        // Create a temporary file to test with
        let temp_file = NamedTempFile::new().unwrap();
        let temp_path = temp_file.path().to_path_buf();

        let config = ThingsConfig::new(&temp_path, false);
        let effective_path = config.get_effective_database_path().unwrap();

        assert_eq!(effective_path, temp_path);
    }

    #[test]
    fn test_config_get_default_database_path_format() {
        let path = ThingsConfig::get_default_database_path();
        let path_str = path.to_string_lossy();

        // Test the specific format of the path
        assert!(
            path_str.contains("JLMPQHK86H.com.culturedcode.ThingsMac"),
            "Should contain the correct container identifier"
        );
        assert!(
            path_str.contains("ThingsData-0Z0Z2"),
            "Should contain the correct data directory"
        );
        assert!(
            path_str.contains("Things Database.thingsdatabase"),
            "Should contain Things database directory"
        );
        assert!(
            path_str.contains("main.sqlite"),
            "Should contain main.sqlite file"
        );
    }

    #[test]
    fn test_config_with_different_path_types_comprehensive() {
        // Test with string path
        let string_path = "/test/path/db.sqlite";
        let config1 = ThingsConfig::new(string_path, false);
        assert_eq!(config1.database_path, PathBuf::from(string_path));
        assert!(!config1.fallback_to_default);

        // Test with PathBuf
        let pathbuf_path = PathBuf::from("/another/test/path.db");
        let config2 = ThingsConfig::new(&pathbuf_path, true);
        assert_eq!(config2.database_path, pathbuf_path);
        assert!(config2.fallback_to_default);
    }

    #[test]
    fn test_config_from_env_edge_cases() {
        // Test the parsing logic for edge cases
        let test_cases = vec![
            ("true", true),
            ("TRUE", true),
            ("True", true),
            ("1", true),
            ("yes", true),
            ("YES", true),
            ("on", true),
            ("ON", true),
            ("false", false),
            ("FALSE", false),
            ("0", false),
            ("no", false),
            ("off", false),
            ("invalid", false),
            ("", false),
            ("random_string", false),
        ];

        for (value, expected) in test_cases {
            // Test the parsing logic directly (matches the implementation)
            let lower = value.to_lowercase();
            let result = matches!(lower.as_str(), "true" | "1" | "yes" | "on");
            assert_eq!(result, expected, "Failed for value: '{value}'");
        }
    }

    #[test]
    #[serial]
    fn test_config_from_env_fallback_parsing_with_env_vars() {
        // Save original value
        let original_value = std::env::var("THINGS_FALLBACK_TO_DEFAULT").ok();

        // Test different fallback values with actual environment variables
        let test_cases = vec![
            ("true", true),
            ("false", false),
            ("1", true),
            ("0", false),
            ("yes", true),
            ("no", false),
            ("invalid", false),
        ];

        for (value, expected) in test_cases {
            // Clear any existing value first
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");

            // Set the test value
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", value);

            // Verify the environment variable is set correctly
            let env_value = std::env::var("THINGS_FALLBACK_TO_DEFAULT")
                .unwrap_or_else(|_| "NOT_SET".to_string());
            println!("Environment variable set to: '{env_value}'");

            // Double-check the environment variable is still set right before calling from_env
            let env_value_check = std::env::var("THINGS_FALLBACK_TO_DEFAULT")
                .unwrap_or_else(|_| "NOT_SET".to_string());
            println!("Environment variable check before from_env: '{env_value_check}'");

            let config = ThingsConfig::from_env();

            // Debug: print what we're testing
            println!(
                "Testing value: '{}', expected: {}, got: {}",
                value, expected, config.fallback_to_default
            );

            assert_eq!(
                config.fallback_to_default, expected,
                "Failed for value: '{}', expected: {}, got: {}",
                value, expected, config.fallback_to_default
            );
        }

        // Restore original value
        if let Some(original) = original_value {
            std::env::set_var("THINGS_FALLBACK_TO_DEFAULT", original);
        } else {
            std::env::remove_var("THINGS_FALLBACK_TO_DEFAULT");
        }
    }

    #[test]
    #[serial]
    fn test_config_home_env_var_fallback() {
        // Snapshot all env vars from_env() reads, so we can assert the default
        // HOME-based path and restore cleanly even if prior serial tests leaked state.
        let original_home = std::env::var("HOME").ok();
        let original_db_path = std::env::var("THINGS_DB_PATH").ok();
        let original_legacy = std::env::var("THINGS_DATABASE_PATH").ok();

        std::env::remove_var("THINGS_DB_PATH");
        std::env::remove_var("THINGS_DATABASE_PATH");
        std::env::set_var("HOME", "/test/home");

        let config = ThingsConfig::from_env();
        let contains_default = config
            .database_path
            .to_string_lossy()
            .contains("Things Database.thingsdatabase");

        if let Some(home) = original_home {
            std::env::set_var("HOME", home);
        } else {
            std::env::remove_var("HOME");
        }
        if let Some(v) = original_db_path {
            std::env::set_var("THINGS_DB_PATH", v);
        }
        if let Some(v) = original_legacy {
            std::env::set_var("THINGS_DATABASE_PATH", v);
        }

        assert!(contains_default);
    }

    #[test]
    fn test_config_with_default_path() {
        let config = ThingsConfig::with_default_path();
        assert!(config
            .database_path
            .to_string_lossy()
            .contains("Things Database.thingsdatabase"));
        assert!(!config.fallback_to_default);
    }
}
