//! Defaults generator: produces default configuration values in Nickel with type contracts.

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

/// Generates Nickel defaults from domain features.
pub struct DefaultsGenerator;

impl DefaultsGenerator {
    /// Generate defaults 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 defaults for project: {}", spec.name);

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

        // Generate defaults for each feature
        for feature in &spec.domain_features {
            let mut defaults_content = String::new();

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

            if let Some(desc) = &feature.description {
                defaults_content.push_str(&format!("# {}\n", desc));
            }

            defaults_content.push('\n');

            // Import schema if using type contracts
            if feature.use_type_contracts {
                defaults_content.push_str(&format!(
                    "let schemas = import \"../schemas/{}.ncl\" in\n\n",
                    feature.name
                ));
            }

            // Define defaults record
            if feature.use_type_contracts {
                let type_name = Self::capitalize_first(&feature.name);
                defaults_content.push_str(&format!(
                    "# Default values with type contract\n{{\n  {} | schemas.{} = {{\n",
                    feature.name, type_name
                ));
            } else {
                defaults_content.push_str(&format!("{{\n  {} = {{\n", feature.name));
            }

            // Generate field defaults
            for field in &feature.fields {
                defaults_content.push_str(&format!("    # {}\n", field.prompt));

                if let Some(default) = &field.default {
                    // Use provided default (same format regardless of type contracts)
                    defaults_content.push_str(&format!("    {} = {},\n", field.name, default));
                } else {
                    // Generate sensible defaults based on field type
                    let default_val =
                        Self::generate_default_value(field, feature.use_type_contracts);
                    defaults_content.push_str(&format!(
                        "    {} = {},  # Auto-generated default\n",
                        field.name, default_val
                    ));
                }
            }

            // Same closing format regardless of type contracts
            defaults_content.push_str("  },\n}\n");

            // Write defaults file
            let defaults_file = defaults_dir.join(format!("{}.ncl", feature.name));
            std::fs::write(&defaults_file, defaults_content).map_err(|e| {
                crate::error::ProvisioningGenError::Other(format!(
                    "Failed to write defaults file: {}",
                    e
                ))
            })?;

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

        // Generate main defaults.ncl that imports all features
        Self::generate_main_defaults(spec, &defaults_dir)?;

        // Generate README
        Self::generate_defaults_readme(spec, &defaults_dir)?;

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

    /// Generate main defaults.ncl that imports all feature defaults.
    fn generate_main_defaults(spec: &ProjectSpec, defaults_dir: &Path) -> Result<()> {
        let mut content = String::new();

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

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

        content.push_str("\n# Merge all defaults\nstd.record.merge_all [\n");
        for feature in &spec.domain_features {
            content.push_str(&format!("  {},\n", feature.name));
        }
        content.push_str("]\n");

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

        Ok(())
    }

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

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

        content.push_str(
            "This directory contains default configuration values in Nickel format.\n\n\
             ## Overview\n\n\
             Defaults provide sensible starting values for all configuration fields. \
             Users can override these by merging their own values.\n\n\
             ## Files\n\n",
        );

        for feature in &spec.domain_features {
            content.push_str(&format!("### `{}.ncl`\n\n", feature.name));
            if let Some(desc) = &feature.description {
                content.push_str(&format!("{}\n\n", desc));
            }
            if feature.use_type_contracts {
                content.push_str("*Uses type contracts for validation.*\n\n");
            }
        }

        content.push_str(
            "## Usage\n\n\
             ### Merging with User Values\n\n\
             ```nickel\n\
             let defaults = import \"./defaults/config.ncl\" in\n\
             let user_values = {\n\
               server.port = 9090,\n\
             } in\n\
             std.record.merge defaults user_values\n\
             ```\n\n\
             ### Type Contract Application\n\n\
             When features use type contracts, defaults automatically enforce schemas:\n\n\
             ```nickel\n\
             {\n\
               tracker | schemas.Tracker = {\n\
                 port = 8080,  # Type-checked\n\
               },\n\
             }\n\
             ```\n\n",
        );

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

        Ok(())
    }

    /// Generate a sensible default value for a field type.
    fn generate_default_value(
        field: &crate::models::ConfigField,
        use_type_contract: bool,
    ) -> String {
        match field.field_type {
            FieldType::Text => "\"\"".to_string(),
            FieldType::Number => {
                if let Some(min) = field.min {
                    min.to_string()
                } else {
                    "0".to_string()
                }
            }
            FieldType::Password => "\"\"".to_string(),
            FieldType::Confirm => "false".to_string(),
            FieldType::Select => {
                if let Some(first_option) = field.options.first() {
                    format!("\"{}\"", first_option)
                } else {
                    "\"\"".to_string()
                }
            }
            FieldType::MultiSelect => "[]".to_string(),
            FieldType::Editor => "\"\"".to_string(),
            FieldType::Date => "\"\"".to_string(),
            FieldType::RepeatingGroup => {
                if use_type_contract {
                    // Generate array with one default item and type contract
                    "[\n      # Add default items here\n    ]".to_string()
                } else {
                    "[]".to_string()
                }
            }
        }
    }

    /// Capitalize first letter of a string.
    fn capitalize_first(s: &str) -> String {
        let mut chars = s.chars();
        match chars.next() {
            None => String::new(),
            Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
        }
    }
}
