//! Nickel evaluation and validation
//!
//! Evaluates Nickel code to produce AgentDefinition with type checking

use crate::error::{Error, Result};
use nickel_lang_core::program::Program;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Agent configuration from Nickel evaluation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
    pub role: String,
    pub llm: String,
    #[serde(default)]
    pub tools: Vec<String>,
    #[serde(default = "default_max_tokens")]
    pub max_tokens: usize,
    #[serde(default = "default_temperature")]
    pub temperature: f64,
}

fn default_max_tokens() -> usize {
    4096
}

fn default_temperature() -> f64 {
    0.7
}

/// Complete agent definition after Nickel evaluation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentDefinition {
    pub config: AgentConfig,
    #[serde(default)]
    pub inputs: HashMap<String, serde_json::Value>,
    #[serde(default)]
    pub context: Option<ContextSources>,
    #[serde(default)]
    pub validation: Option<ValidationRules>,
    #[serde(default)]
    pub template: String,
}

/// Context sources (imports, shell commands)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextSources {
    #[serde(default)]
    pub imports: Vec<String>,
    #[serde(default)]
    pub shell_commands: Vec<String>,
}

/// Output validation rules
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationRules {
    #[serde(default)]
    pub must_contain: Vec<String>,
    #[serde(default)]
    pub must_not_contain: Vec<String>,
    #[serde(default = "default_format")]
    pub format: String,
    pub min_length: Option<usize>,
    pub max_length: Option<usize>,
}

fn default_format() -> String {
    "markdown".to_string()
}

/// Nickel evaluator
pub struct NickelEvaluator;

impl NickelEvaluator {
    /// Create a new Nickel evaluator
    pub fn new() -> Self {
        Self
    }

    /// Evaluate Nickel code to AgentDefinition
    pub fn evaluate(&self, nickel_code: &str) -> Result<AgentDefinition> {
        use nickel_lang_core::error::NullReporter;
        use nickel_lang_core::eval::value::lazy::CBNCache;
        use nickel_lang_core::serialize::ExportFormat;
        use std::io::Cursor;

        // Parse and create program with null reporter (we handle errors ourselves)
        let mut program: Program<CBNCache> = Program::new_from_source(
            Cursor::new(nickel_code),
            "<agent>",
            std::io::sink(),
            NullReporter {},
        )
        .map_err(|e| Error::nickel_eval("Failed to parse Nickel code", format!("{:?}", e)))?;

        // Evaluate to get the term
        let term = program.eval_full_for_export().map_err(|e| {
            Error::nickel_eval("Failed to evaluate Nickel code", format!("{:?}", e))
        })?;

        // Serialize to JSON
        let mut output = Vec::new();
        nickel_lang_core::serialize::to_writer(&mut output, ExportFormat::Json, &term)
            .map_err(|e| Error::nickel_eval("Failed to serialize to JSON", format!("{:?}", e)))?;

        // Parse JSON and deserialize
        let json_value: serde_json::Value = serde_json::from_slice(&output)
            .map_err(|e| Error::nickel_eval("Failed to parse JSON output", e.to_string()))?;

        serde_json::from_value(json_value)
            .map_err(|e| Error::nickel_eval("Failed to deserialize AgentDefinition", e.to_string()))
    }

    /// Type check Nickel code without evaluation
    pub fn typecheck(&self, nickel_code: &str) -> Result<()> {
        use nickel_lang_core::error::NullReporter;
        use nickel_lang_core::eval::value::lazy::CBNCache;
        use nickel_lang_core::typecheck::TypecheckMode;
        use std::io::Cursor;

        // Parse Nickel code
        let mut program: Program<CBNCache> = Program::new_from_source(
            Cursor::new(nickel_code),
            "<agent>",
            std::io::sink(),
            NullReporter {},
        )
        .map_err(|e| {
            Error::nickel_eval(
                "Failed to parse Nickel code for type checking",
                format!("{:?}", e),
            )
        })?;

        // Type check with strict mode
        program
            .typecheck(TypecheckMode::Enforce)
            .map_err(|e| Error::nickel_eval("Type checking failed", format!("{:?}", e)))?;

        Ok(())
    }
}

impl Default for NickelEvaluator {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_evaluate_simple_nickel() {
        let nickel_code = r#"{
  config = {
    role = "architect",
    llm = "claude-opus-4",
    tools = ["analyze"],
  },
  inputs = {},
  template = "test",
}"#;

        let evaluator = NickelEvaluator::new();
        let result = evaluator.evaluate(nickel_code);

        assert!(result.is_ok());
        let agent_def = result.unwrap();
        assert_eq!(agent_def.config.role, "architect");
        assert_eq!(agent_def.config.llm, "claude-opus-4");
        assert_eq!(agent_def.config.tools, vec!["analyze"]);
    }

    #[test]
    fn test_typecheck_valid_nickel() {
        let nickel_code = r#"{
  config = {
    role = "architect",
    llm = "claude-opus-4",
  },
}"#;

        let evaluator = NickelEvaluator::new();
        let result = evaluator.typecheck(nickel_code);

        assert!(result.is_ok());
    }
}
