//! Nickel Template Renderer
//!
//! Renders Nickel output using Tera templates (.ncl.j2).
//! Preserves validators and structure from parsed contracts.

use super::contract_parser::ParsedContracts;
use super::schema_ir::ContractCall;
use serde_json::Value;
use std::collections::HashMap;

/// Context for rendering Nickel templates
#[derive(Debug, Clone)]
pub struct NickelTemplateContext {
    /// Original imports from source
    pub imports: HashMap<String, String>,

    /// Contract calls available for use in output
    pub contracts: HashMap<String, ContractCall>,

    /// Form results (field values)
    pub results: HashMap<String, Value>,

    /// Original source (for fallback/reference)
    pub source: String,
}

impl NickelTemplateContext {
    /// Create context from parsed contracts and form results
    pub fn from_parsed_contracts(parsed: ParsedContracts, results: HashMap<String, Value>) -> Self {
        Self {
            imports: parsed.imports,
            contracts: parsed.field_contracts,
            results,
            source: parsed.source,
        }
    }

    /// Get a contract call for a field
    pub fn get_contract(&self, module: &str, function: &str) -> Option<&ContractCall> {
        let key = format!("{}.{}", module, function);
        self.contracts.get(&key)
    }

    /// Render a value using its contract call if available
    pub fn render_with_contract(&self, value: &Value, contract: &ContractCall) -> String {
        let arg = Self::value_to_nickel_arg(value);
        format!("{}.{} {}", contract.module, contract.function, arg)
    }

    /// Convert a JSON value to Nickel argument representation
    fn value_to_nickel_arg(value: &Value) -> String {
        match value {
            Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Null => "null".to_string(),
            Value::Array(_) | Value::Object(_) => {
                // For complex types, use JSON representation
                value.to_string()
            }
        }
    }

    /// Generate Nickel code from results with preserved contracts
    pub fn generate_user_config(&self) -> String {
        let mut output = String::from("let user_config = {\n");

        for (field_name, value) in &self.results {
            // Try to find a contract for this field
            let line = if let Some(contract) = self.find_contract_for_field(field_name) {
                format!(
                    "  {} = {},\n",
                    field_name,
                    self.render_with_contract(value, contract)
                )
            } else {
                // Fallback to direct value rendering
                format!(
                    "  {} = {},\n",
                    field_name,
                    Self::value_to_nickel_expr(value)
                )
            };

            output.push_str(&line);
        }

        output.push_str("} in\n");
        output
    }

    /// Find a contract that applies to this field
    fn find_contract_for_field(&self, field_name: &str) -> Option<&ContractCall> {
        self.contracts.get(field_name)
    }

    /// Convert a JSON value to full Nickel expression
    fn value_to_nickel_expr(value: &Value) -> String {
        match value {
            Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Null => "null".to_string(),
            Value::Array(arr) => {
                let items: Vec<String> = arr.iter().map(Self::value_to_nickel_expr).collect();
                format!("[{}]", items.join(", "))
            }
            Value::Object(obj) => {
                let items: Vec<String> = obj
                    .iter()
                    .map(|(k, v)| format!("{} = {}", k, Self::value_to_nickel_expr(v)))
                    .collect();
                format!("{{{}}}", items.join(", "))
            }
        }
    }

    /// Render complete Nickel file with imports, user_config, and final expression
    pub fn render_full_nickel(&self) -> String {
        let mut output = String::new();

        // Add imports
        for (name, path) in &self.imports {
            output.push_str(&format!("let {} = import \"{}\" in\n", name, path));
        }
        output.push('\n');

        // Add user_config
        output.push_str(&self.generate_user_config());

        // Add final expression
        output.push_str("user_config\n");

        output
    }
}

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

    #[test]
    fn test_value_to_nickel_arg() {
        assert_eq!(
            NickelTemplateContext::value_to_nickel_arg(&json!("test")),
            "\"test\""
        );
        assert_eq!(NickelTemplateContext::value_to_nickel_arg(&json!(42)), "42");
        assert_eq!(
            NickelTemplateContext::value_to_nickel_arg(&json!(true)),
            "true"
        );
    }

    #[test]
    fn test_value_to_nickel_expr() {
        assert_eq!(
            NickelTemplateContext::value_to_nickel_expr(&json!("test")),
            "\"test\""
        );
        assert_eq!(
            NickelTemplateContext::value_to_nickel_expr(&json!([1, 2, 3])),
            "[1, 2, 3]"
        );
    }

    #[test]
    fn test_render_with_contract() {
        let mut ctx = NickelTemplateContext {
            imports: HashMap::new(),
            contracts: HashMap::new(),
            results: HashMap::new(),
            source: String::new(),
        };

        let contract = ContractCall {
            module: "validators".to_string(),
            function: "ValidPort".to_string(),
            args: None,
            expr: "validators.ValidPort".to_string(),
        };

        // Key by field name, not module.function
        ctx.contracts
            .insert("ssh_port".to_string(), contract.clone());

        let result = ctx.render_with_contract(&json!(8080), &contract);
        assert!(result.contains("validators.ValidPort"));
        assert!(result.contains("8080"));
    }

    #[test]
    fn test_find_contract_by_field_name() {
        let mut ctx = NickelTemplateContext {
            imports: HashMap::new(),
            contracts: HashMap::new(),
            results: HashMap::new(),
            source: String::new(),
        };

        // Add multiple contracts keyed by field name
        let port_contract = ContractCall {
            module: "validators".to_string(),
            function: "ValidPort".to_string(),
            args: None,
            expr: "validators.ValidPort".to_string(),
        };

        let env_contract = ContractCall {
            module: "validators".to_string(),
            function: "ValidEnvironmentName".to_string(),
            args: Some("\"dev\"".to_string()),
            expr: "validators.ValidEnvironmentName \"dev\"".to_string(),
        };

        // Key by field name - this is the fix!
        ctx.contracts.insert("ssh_port".to_string(), port_contract);
        ctx.contracts
            .insert("environment_name".to_string(), env_contract);

        // Verify each field gets its own validator
        let port_result = ctx.find_contract_for_field("ssh_port");
        assert!(port_result.is_some());
        assert_eq!(port_result.unwrap().function, "ValidPort");

        let env_result = ctx.find_contract_for_field("environment_name");
        assert!(env_result.is_some());
        assert_eq!(env_result.unwrap().function, "ValidEnvironmentName");

        // Verify non-existent field returns None
        let missing = ctx.find_contract_for_field("unknown_field");
        assert!(missing.is_none());
    }
}
