//! Validator generator: produces Nickel validators using templates.

use crate::error::Result;
use crate::models::project_spec::ValidatorType;
use crate::models::{FieldType, ProjectSpec};
use std::path::Path;

/// Generates validator Nickel files from constraints.
pub struct ValidatorGenerator;

impl ValidatorGenerator {
    /// Generate validators for all domain features.
    pub fn generate(spec: &ProjectSpec, output_dir: impl AsRef<Path>) -> Result<()> {
        let output_dir = output_dir.as_ref();
        tracing::info!("Generating validators for project: {}", spec.name);

        // Ensure validators directory exists
        let validators_dir = output_dir.join("validators");
        std::fs::create_dir_all(&validators_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create validators directory: {}",
                e
            ))
        })?;

        // Always generate common.ncl with reusable validators
        Self::generate_common_validators(&validators_dir)?;

        // Generate feature-specific validators
        for feature in &spec.domain_features {
            if !feature.custom_validators.is_empty() {
                Self::generate_custom_validators(spec, feature, &validators_dir)?;
            } else {
                Self::generate_basic_validators(spec, feature, &validators_dir)?;
            }
        }

        // Generate main validators config
        Self::generate_main_validators(spec, &validators_dir)?;

        // Generate README
        Self::generate_validators_readme(spec, &validators_dir)?;

        tracing::info!("Successfully generated validators");
        Ok(())
    }

    /// Generate common.ncl with reusable validation contracts.
    fn generate_common_validators(validators_dir: &Path) -> Result<()> {
        let content = r#"# Common validators
# Reusable validation predicates and type contracts

{
  # Port number validation (1-65535)
  ValidPort = fun label =>
    std.contract.from_predicate (fun port =>
      std.is_number port &&
      port >= 1 &&
      port <= 65535
    ) {
      label = label,
      message = "must be a valid port number (1-65535)",
    },

  # Non-empty string validation
  NonEmptyString = fun label =>
    std.contract.from_predicate (fun s =>
      std.is_string s &&
      std.string.length s > 0
    ) {
      label = label,
      message = "must be a non-empty string",
    },

  # Valid bind address format (IP:PORT)
  ValidBindAddress = fun label =>
    std.contract.from_predicate (fun addr =>
      std.is_string addr &&
      std.string.contains ":" addr &&
      let parts = std.string.split ":" addr in
      std.array.length parts == 2
    ) {
      label = label,
      message = "must be a valid bind address (IP:PORT format)",
    },

  # Valid URL format
  ValidUrl = fun label =>
    std.contract.from_predicate (fun url =>
      std.is_string url &&
      (std.string.is_match "^https?://" url)
    ) {
      label = label,
      message = "must be a valid HTTP/HTTPS URL",
    },

  # Positive number validation
  PositiveNumber = fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n && n > 0
    ) {
      label = label,
      message = "must be a positive number",
    },

  # Non-negative number validation
  NonNegativeNumber = fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n && n >= 0
    ) {
      label = label,
      message = "must be a non-negative number",
    },

  # Range validation
  Range = fun min => fun max => fun label =>
    std.contract.from_predicate (fun n =>
      std.is_number n &&
      n >= min &&
      n <= max
    ) {
      label = label,
      message = "must be between %{std.to_string min} and %{std.to_string max}",
    },

  # String pattern matching
  MatchesPattern = fun pattern => fun label =>
    std.contract.from_predicate (fun s =>
      std.is_string s &&
      std.string.is_match pattern s
    ) {
      label = label,
      message = "must match pattern: %{pattern}",
    },

  # Enum validation
  OneOf = fun allowed => fun label =>
    std.contract.from_predicate (fun value =>
      std.array.any (fun v => v == value) allowed
    ) {
      label = label,
      message = "must be one of: %{std.serialize 'Json allowed}",
    },

  # Array length validation
  ArrayLength = fun min => fun max => fun label =>
    std.contract.from_predicate (fun arr =>
      std.is_array arr &&
      let len = std.array.length arr in
      len >= min && len <= max
    ) {
      label = label,
      message = "array length must be between %{std.to_string min} and %{std.to_string max}",
    },
}
"#;

        let common_file = validators_dir.join("common.ncl");
        std::fs::write(&common_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write common validators: {}",
                e
            ))
        })?;

        tracing::debug!("Generated common validators");
        Ok(())
    }

    /// Generate custom validators for features with custom validator specs.
    fn generate_custom_validators(
        spec: &ProjectSpec,
        feature: &crate::models::DomainFeature,
        validators_dir: &Path,
    ) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!(
            "# Custom validators for {}\n# Generated for project: {}\n\n",
            feature.name, spec.name
        ));

        content.push_str("let common = import \"./common.ncl\" in\n\n{\n");

        // Generate each custom validator
        for validator in &feature.custom_validators {
            content.push_str(&format!(
                "  # {}\n",
                validator
                    .description
                    .as_ref()
                    .unwrap_or(&format!("Validator for {}", validator.name))
            ));

            match validator.validator_type {
                ValidatorType::Range => {
                    content.push_str(&format!(
                        "  {} = common.Range 0 100 \"{}\",\n\n",
                        validator.name, validator.name
                    ));
                }
                ValidatorType::Pattern => {
                    content.push_str(&format!(
                        "  {} = common.MatchesPattern \".*\" \"{}\",\n\n",
                        validator.name, validator.name
                    ));
                }
                ValidatorType::ArrayUniqueness => {
                    content.push_str(&format!(
                        "  {} = fun label =>\n    std.contract.from_predicate (fun arr =>\n      std.is_array arr &&\n      let values = std.array.map (fun item => item.id) arr in\n      std.array.length values == std.array.length (std.array.sort values)\n    ) {{\n      label = label,\n      message = \"array items must have unique values\",\n    }},\n\n",
                        validator.name
                    ));
                }
                ValidatorType::Composite => {
                    content.push_str(&format!(
                        "  {} = fun label =>\n    std.contract.from_predicate (fun value =>\n      true  # Composite validation\n    ) {{\n      label = label,\n      message = \"composite validation failed\",\n    }},\n\n",
                        validator.name
                    ));
                }
                ValidatorType::CustomPredicate => {
                    content.push_str(&format!(
                        "  {} = fun label =>\n    std.contract.from_predicate (fun value =>\n      true  # Custom predicate\n    ) {{\n      label = label,\n      message = \"{}\",\n    }},\n\n",
                        validator.name,
                        validator.description.as_ref().unwrap_or(&"validation failed".to_string())
                    ));
                }
            }
        }

        content.push_str("  # Master validation function\n");
        content.push_str("  validate = fun config => config,\n");
        content.push_str("}\n");

        let validator_file = validators_dir.join(format!("{}.ncl", feature.name));
        std::fs::write(&validator_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write custom validators: {}",
                e
            ))
        })?;

        tracing::debug!("Generated custom validators for: {}", feature.name);
        Ok(())
    }

    /// Generate basic validators for features without custom validators.
    fn generate_basic_validators(
        spec: &ProjectSpec,
        feature: &crate::models::DomainFeature,
        validators_dir: &Path,
    ) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!(
            "# Validators for {} feature\n# Generated for project: {}\n\n",
            feature.name, spec.name
        ));

        content.push_str("let common = import \"./common.ncl\" in\n\n{\n");

        // Generate field validators
        for field in &feature.fields {
            if let Some(validator_fn) = Self::get_common_validator_for_field(field) {
                content.push_str(&format!(
                    "  # {} validator\n  validate_{} = {},\n\n",
                    field.name, field.name, validator_fn
                ));
            }
        }

        content.push_str("  # Validation function\n");
        content.push_str("  validate = fun config => config,\n");
        content.push_str("}\n");

        let validator_file = validators_dir.join(format!("{}.ncl", feature.name));
        std::fs::write(&validator_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!("Failed to write validators: {}", e))
        })?;

        Ok(())
    }

    /// Get appropriate common validator for a field.
    fn get_common_validator_for_field(field: &crate::models::ConfigField) -> Option<String> {
        match field.field_type {
            FieldType::Number => {
                if let (Some(min), Some(max)) = (field.min, field.max) {
                    Some(format!("common.Range {} {} \"{}\"", min, max, field.name))
                } else if field.min == Some(0) {
                    Some(format!("common.NonNegativeNumber \"{}\"", field.name))
                } else if field.min == Some(1) {
                    Some(format!("common.PositiveNumber \"{}\"", field.name))
                } else {
                    None
                }
            }
            FieldType::Text => {
                if field.name.contains("address") && field.name.contains("bind") {
                    Some(format!("common.ValidBindAddress \"{}\"", field.name))
                } else if field.name.contains("url") {
                    Some(format!("common.ValidUrl \"{}\"", field.name))
                } else if !field.required {
                    None
                } else {
                    Some(format!("common.NonEmptyString \"{}\"", field.name))
                }
            }
            FieldType::Password => Some(format!("common.NonEmptyString \"{}\"", field.name)),
            _ => None,
        }
    }

    /// Generate main validators.ncl.
    fn generate_main_validators(spec: &ProjectSpec, validators_dir: &Path) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!(
            "# Main validators configuration for {}\n\n",
            spec.name
        ));

        content.push_str("let common = import \"./common.ncl\" in\n");

        for feature in &spec.domain_features {
            content.push_str(&format!(
                "let {} = import \"./{}.ncl\" in\n",
                feature.name, feature.name
            ));
        }

        content.push_str("\n{\n  common,\n");
        for feature in &spec.domain_features {
            content.push_str(&format!("  {},\n", feature.name));
        }
        content.push_str("}\n");

        let config_file = validators_dir.join("config.ncl");
        std::fs::write(&config_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write main validators: {}",
                e
            ))
        })?;

        Ok(())
    }

    /// Generate README.md for validators directory.
    fn generate_validators_readme(spec: &ProjectSpec, validators_dir: &Path) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!("# Validators - {}\n\n", spec.name));

        content.push_str(
            "This directory contains Nickel validation contracts.\n\n\
             ## Overview\n\n\
             Validators enforce rules on configuration values using Nickel's contract system.\n\n\
             ## Files\n\n\
             ### `common.ncl` - Reusable Validators\n\n\
             Common validation contracts used across features:\n\n\
             - `ValidPort` - Port numbers (1-65535)\n\
             - `NonEmptyString` - Non-empty strings\n\
             - `ValidBindAddress` - IP:PORT format\n\
             - `ValidUrl` - HTTP/HTTPS URLs\n\
             - `PositiveNumber` - Numbers > 0\n\
             - `NonNegativeNumber` - Numbers >= 0\n\
             - `Range` - Number range validation\n\
             - `MatchesPattern` - Regex pattern matching\n\
             - `OneOf` - Enum validation\n\
             - `ArrayLength` - Array size constraints\n\n",
        );

        for feature in &spec.domain_features {
            content.push_str(&format!("### `{}.ncl`\n\n", feature.name));
            if !feature.custom_validators.is_empty() {
                content.push_str(&format!(
                    "Feature-specific validators ({} custom validators).\n\n",
                    feature.custom_validators.len()
                ));
            } else {
                content.push_str("Standard field validators.\n\n");
            }
        }

        content.push_str(
            "## Usage\n\n\
             ### Applying Validators\n\n\
             ```nickel\n\
             let validators = import \"./validators/config.ncl\" in\n\
             let config = {...} in\n\
             validators.validate config\n\
             ```\n\n\
             ### Custom Validation\n\n\
             ```nickel\n\
             let common = import \"./validators/common.ncl\" in\n\
             {\n\
               port | common.ValidPort \"config.port\" = 8080,\n\
             }\n\
             ```\n\n",
        );

        let readme_file = validators_dir.join("README.md");
        std::fs::write(&readme_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write validators README: {}",
                e
            ))
        })?;

        Ok(())
    }
}
