//! Constraint generator: produces constraints.toml from domain features.

use crate::error::Result;
use crate::models::ProjectSpec;
use std::path::Path;

/// Generates constraints.toml from ProjectSpec.
pub struct ConstraintGenerator;

impl ConstraintGenerator {
    /// Generate constraints.toml file and README.
    pub fn generate(spec: &ProjectSpec, output_dir: impl AsRef<Path>) -> Result<()> {
        let output_dir = output_dir.as_ref();
        tracing::info!("Generating constraints for project: {}", spec.name);

        let mut constraints_content = String::new();

        // Add header with interpolation documentation
        constraints_content.push_str(&format!(
            "# Constraint definitions for {}\n\
             # Single source of truth for validation rules\n\
             #\n\
             # Constraints can be referenced in fragments using interpolation:\n\
             #   max_items = \"${{constraint.tracker.udp.max_items}}\"\n\
             #\n\
             # This enables centralized constraint management.\n\n",
            spec.name
        ));

        // Generate hierarchical constraint sections for each feature
        for feature in &spec.domain_features {
            // Add feature comment
            if let Some(desc) = &feature.description {
                constraints_content.push_str(&format!("# {}\n", desc));
            }

            // Generate hierarchical sections for fields with constraints
            for field in &feature.fields {
                if field.min.is_some() || field.max.is_some() {
                    // Use hierarchical path: [tracker.udp] instead of [feature.tracker.udp]
                    constraints_content.push_str(&format!("[{}.{}]\n", feature.name, field.name));

                    if let Some(min) = field.min {
                        constraints_content.push_str(&format!("min = {}\n", min));
                    }
                    if let Some(max) = field.max {
                        constraints_content.push_str(&format!("max = {}\n", max));
                    }

                    constraints_content.push('\n');
                }
            }
        }

        // Add global array constraints with hierarchical paths
        if !spec.constraints.is_empty() {
            constraints_content.push_str("# Array constraints\n\n");

            for constraint in &spec.constraints {
                // Hierarchical path: [tracker.udp_trackers] instead of [constraint."tracker.udp_trackers"]
                constraints_content.push_str(&format!("[{}]\n", constraint.path));

                if let Some(min) = constraint.min_items {
                    constraints_content.push_str(&format!("min_items = {}\n", min));
                }
                if let Some(max) = constraint.max_items {
                    constraints_content.push_str(&format!("max_items = {}\n", max));
                }

                if constraint.unique {
                    constraints_content.push_str("unique = true\n");
                    if let Some(unique_key) = &constraint.unique_key {
                        constraints_content.push_str(&format!("unique_key = \"{}\"\n", unique_key));
                    }
                }

                constraints_content.push('\n');
            }
        }

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

        // Write constraints.toml file
        let constraints_file = output_dir.join("constraints.toml");
        std::fs::write(&constraints_file, constraints_content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write constraints file: {}",
                e
            ))
        })?;

        tracing::info!("Generated constraints file: {}", constraints_file.display());

        // Generate README.md explaining the constraint system
        let readme_content = Self::generate_readme(spec);
        let readme_file = constraints_dir.join("README.md");
        std::fs::write(&readme_file, readme_content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write constraints README: {}",
                e
            ))
        })?;

        tracing::info!("Generated constraints README: {}", readme_file.display());

        Ok(())
    }

    /// Generate README.md documenting the constraint system.
    fn generate_readme(spec: &ProjectSpec) -> String {
        let mut content = String::new();

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

        content.push_str(
            "This directory contains documentation for the constraint system used in this project.\n\n\
             ## Overview\n\n\
             Constraints define validation rules that apply to configuration values. They are centralized \
             in `constraints.toml` and can be referenced throughout the provisioning system.\n\n\
             ## Usage\n\n\
             ### In Form Fragments\n\n\
             Reference constraints using interpolation syntax:\n\n\
             ```toml\n\
             [[elements]]\n\
             type = \"repeatinggroup\"\n\
             max_items = \"${constraint.tracker.udp_trackers.max_items}\"\n\
             unique = true\n\
             ```\n\n\
             ### In Validators\n\n\
             Validators read constraints from `constraints.toml` to enforce rules:\n\n\
             ```nickel\n\
             let constraints = import \"../constraints.toml\" in\n\
             std.contract.from_predicate (fun arr =>\n\
               std.array.length arr <= constraints.tracker.udp_trackers.max_items\n\
             )\n\
             ```\n\n\
             ## Constraint Types\n\n\
             ### Field Constraints\n\n\
             - `min` - Minimum value for numbers\n\
             - `max` - Maximum value for numbers\n\n\
             ### Array Constraints\n\n\
             - `min_items` - Minimum array length\n\
             - `max_items` - Maximum array length\n\
             - `unique` - Items must be unique\n\
             - `unique_key` - Field name to check for uniqueness\n\n\
             ## Example\n\n\
             ```toml\n\
             [tracker.udp_trackers]\n\
             min_items = 1\n\
             max_items = 4\n\
             unique = true\n\
             unique_key = \"bind_address\"\n\
             ```\n\n\
             This ensures:\n\
             - At least 1 UDP tracker is configured\n\
             - No more than 4 UDP trackers\n\
             - Each tracker has a unique bind_address\n\n\
             ## Hierarchical Paths\n\n\
             Constraints use hierarchical TOML paths for clarity:\n\n\
             - ✅ `[tracker.udp]` - Hierarchical\n\
             - ❌ `[feature.tracker.udp]` - Old flat style\n\n\
             This matches the Nickel schema structure and makes interpolation cleaner.\n\n"
        );

        content.push_str("## Constraints in This Project\n\n");

        if !spec.constraints.is_empty() {
            for constraint in &spec.constraints {
                content.push_str(&format!("### `{}`\n\n", constraint.path));

                if let Some(min) = constraint.min_items {
                    content.push_str(&format!("- Min items: {}\n", min));
                }
                if let Some(max) = constraint.max_items {
                    content.push_str(&format!("- Max items: {}\n", max));
                }
                if constraint.unique {
                    content.push_str("- Uniqueness: Required\n");
                    if let Some(key) = &constraint.unique_key {
                        content.push_str(&format!("- Unique key: `{}`\n", key));
                    }
                }

                content.push('\n');
            }
        } else {
            content.push_str("*No global constraints defined.*\n\n");
        }

        content
    }
}
