//! Configuration validation using Nickel typechecking
//!
//! Validates generated configurations against schema constraints
//! using the existing Nickel CLI integration.

use anyhow::Result;
use std::path::Path;
use typedialog_core::nickel::cli::NickelCli;

/// Validation result for a configuration
#[derive(Debug, Clone)]
pub struct ValidationResult {
    /// Whether the configuration is valid
    pub is_valid: bool,

    /// Validation error messages if any
    pub errors: Vec<String>,

    /// Validation warnings if any
    pub warnings: Vec<String>,
}

impl ValidationResult {
    /// Create a successful validation result
    pub fn success() -> Self {
        ValidationResult {
            is_valid: true,
            errors: vec![],
            warnings: vec![],
        }
    }

    /// Create a failed validation result
    pub fn failure(errors: Vec<String>) -> Self {
        ValidationResult {
            is_valid: false,
            errors,
            warnings: vec![],
        }
    }

    /// Add a warning to the result
    pub fn with_warning(mut self, warning: String) -> Self {
        self.warnings.push(warning);
        self
    }
}

/// Configuration validator
pub struct ConfigValidator;

impl ConfigValidator {
    /// Validate a configuration file against a Nickel schema
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to the configuration file (JSON, YAML, or TOML)
    /// * `schema_path` - Path to the Nickel schema (.ncl file)
    ///
    /// # Returns
    ///
    /// Validation result with any errors or warnings
    ///
    /// # Errors
    ///
    /// Returns error if the files cannot be read or nickel validation fails
    pub fn validate_config(config_path: &Path, schema_path: &Path) -> Result<ValidationResult> {
        tracing::debug!(
            config = %config_path.display(),
            schema = %schema_path.display(),
            "Validating configuration"
        );

        // Use nickel typecheck to validate
        match NickelCli::verify() {
            Ok(_) => {
                // Nickel is available, use it for validation
                Self::validate_with_nickel(config_path, schema_path)
            }
            Err(_) => {
                // Nickel not available, do basic validation
                tracing::warn!("Nickel CLI not available, skipping typecheck");
                Ok(ValidationResult::success().with_warning(
                    "Nickel CLI not available - configuration not typechecked".to_string(),
                ))
            }
        }
    }

    /// Validate configuration string against schema
    ///
    /// # Arguments
    ///
    /// * `config_content` - Configuration content as string
    /// * `format` - Configuration format ("json", "yaml", "toml")
    /// * `schema_path` - Path to the Nickel schema
    ///
    /// # Returns
    ///
    /// Validation result
    pub fn validate_string(
        config_content: &str,
        format: &str,
        _schema_path: &Path,
    ) -> Result<ValidationResult> {
        // Basic validation without file operations
        Ok(Self::validate_content(config_content, format))
    }

    /// Validate that configuration has all required fields from schema
    ///
    /// # Arguments
    ///
    /// * `config_content` - Configuration content as string
    /// * `required_fields` - List of required field names
    ///
    /// # Returns
    ///
    /// Validation result
    pub fn validate_required_fields(
        config_content: &str,
        required_fields: &[&str],
    ) -> Result<ValidationResult> {
        let mut missing_fields = Vec::new();

        for field in required_fields {
            // Check if field appears in the configuration
            let patterns = [
                format!("{} =", field),
                format!("{}: ", field),
                format!("\"{}\": ", field),
                format!("'{}': ", field),
            ];

            let found = patterns.iter().any(|p| config_content.contains(p));

            if !found {
                missing_fields.push(format!("Missing required field: {}", field));
            }
        }

        if missing_fields.is_empty() {
            Ok(ValidationResult::success())
        } else {
            Ok(ValidationResult::failure(missing_fields))
        }
    }

    // ============================================================================
    // PRIVATE HELPER METHODS
    // ============================================================================

    /// Validate configuration using Nickel CLI
    fn validate_with_nickel(_config_path: &Path, _schema_path: &Path) -> Result<ValidationResult> {
        // TODO: Implement actual Nickel validation
        // For now, return success as a placeholder
        Ok(ValidationResult::success())
    }

    /// Validate configuration content without file I/O
    fn validate_content(config_content: &str, format: &str) -> ValidationResult {
        let mut errors = Vec::new();

        // Basic format-specific validation
        match format {
            "json" => {
                if let Err(e) = serde_json::from_str::<serde_json::Value>(config_content) {
                    errors.push(format!("Invalid JSON: {}", e));
                }
            }
            "yaml" => {
                if let Err(e) = serde_yaml::from_str::<serde_yaml::Value>(config_content) {
                    errors.push(format!("Invalid YAML: {}", e));
                }
            }
            "toml" => {
                if let Err(e) = toml::from_str::<toml::Table>(config_content) {
                    errors.push(format!("Invalid TOML: {}", e));
                }
            }
            _ => {
                errors.push(format!("Unknown configuration format: {}", format));
            }
        }

        if errors.is_empty() {
            ValidationResult::success()
        } else {
            ValidationResult::failure(errors)
        }
    }
}

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

    #[test]
    fn test_validation_result_success() {
        let result = ValidationResult::success();
        assert!(result.is_valid);
        assert!(result.errors.is_empty());
    }

    #[test]
    fn test_validation_result_failure() {
        let errors = vec!["Error 1".to_string(), "Error 2".to_string()];
        let result = ValidationResult::failure(errors);
        assert!(!result.is_valid);
        assert_eq!(result.errors.len(), 2);
    }

    #[test]
    fn test_validation_result_with_warning() {
        let result = ValidationResult::success().with_warning("A warning".to_string());
        assert!(result.is_valid);
        assert_eq!(result.warnings.len(), 1);
    }

    #[test]
    fn test_validate_json_valid() {
        let valid_json = r#"{"name": "test", "value": 42}"#;
        let result = ConfigValidator::validate_content(valid_json, "json");
        assert!(result.is_valid);
    }

    #[test]
    fn test_validate_json_invalid() {
        let invalid_json = r#"{"name": "test", "value": 42"#;
        let result = ConfigValidator::validate_content(invalid_json, "json");
        assert!(!result.is_valid);
        assert!(!result.errors.is_empty());
    }

    #[test]
    fn test_validate_yaml_valid() {
        let valid_yaml = "name: test\nvalue: 42";
        let result = ConfigValidator::validate_content(valid_yaml, "yaml");
        assert!(result.is_valid);
    }

    #[test]
    fn test_validate_yaml_invalid() {
        let invalid_yaml = "name: test\n  value: 42\n invalid";
        let result = ConfigValidator::validate_content(invalid_yaml, "yaml");
        assert!(!result.is_valid);
    }

    #[test]
    fn test_validate_toml_valid() {
        let valid_toml = "name = \"test\"\nvalue = 42";
        let result = ConfigValidator::validate_content(valid_toml, "toml");
        assert!(result.is_valid);
    }

    #[test]
    fn test_validate_toml_invalid() {
        let invalid_toml = "name = \"test\"\nvalue = ";
        let result = ConfigValidator::validate_content(invalid_toml, "toml");
        assert!(!result.is_valid);
    }

    #[test]
    fn test_validate_unknown_format() {
        let content = "some content";
        let result = ConfigValidator::validate_content(content, "unknown");
        assert!(!result.is_valid);
    }

    #[test]
    fn test_validate_required_fields_all_present() {
        let config = r#"{"username": "admin", "password": "secret"}"#;
        let result =
            ConfigValidator::validate_required_fields(config, &["username", "password"]).unwrap();
        assert!(result.is_valid);
    }

    #[test]
    fn test_validate_required_fields_missing() {
        let config = r#"{"username": "admin"}"#;
        let result =
            ConfigValidator::validate_required_fields(config, &["username", "password"]).unwrap();
        assert!(!result.is_valid);
        assert!(result.errors[0].contains("password"));
    }

    #[test]
    fn test_validate_required_fields_yaml_format() {
        let config = "username: admin\npassword: secret";
        let result =
            ConfigValidator::validate_required_fields(config, &["username", "password"]).unwrap();
        assert!(result.is_valid);
    }
}
