use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString, PyBool, PyFloat, PyInt, PyAny};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use dashmap::DashMap;
use tera::{Tera, Context, Value, ErrorKind};
use serde_json;
use regex::Regex;

/// High-performance Rust-powered template engine for WOPR
#[pyclass]
#[derive(Clone)]
pub struct RustTemplateEngine {
    /// Tera template engine instance (wrapped in RwLock for thread-safe mutation)
    tera: Arc<std::sync::RwLock<Tera>>,
    /// Template cache for compiled templates
    template_cache: Arc<DashMap<String, String>>,
    /// Context cache for frequently used contexts
    context_cache: Arc<DashMap<String, Context>>,
    /// Template directory for file watching
    template_dir: PathBuf,
    /// Cache size limit (in number of entries)
    cache_limit: usize,
}

#[pymethods]
impl RustTemplateEngine {
    #[new]
    #[pyo3(signature = (template_dir, auto_escape=true, cache_limit=1000))]
    pub fn new(template_dir: &str, auto_escape: bool, cache_limit: usize) -> PyResult<Self> {
        let template_path = PathBuf::from(template_dir);
        let glob_pattern = format!("{}/**/*.html", template_dir);
        let mut tera = match Tera::new(&glob_pattern) {
            Ok(t) => t,
            Err(_) => Tera::default(),
        };
        if auto_escape {
            tera.autoescape_on(vec![".html", ".htm", ".xml", ".j2", ".jinja2"]);
        }
        
        // Register built-in filters
        tera.register_filter("currency", currency_filter);
        tera.register_filter("relative_time", relative_time_filter);
        tera.register_filter("tojson", tojson_filter);
        
        Ok(RustTemplateEngine {
            tera: Arc::new(std::sync::RwLock::new(tera)),
            template_cache: Arc::new(DashMap::new()),
            context_cache: Arc::new(DashMap::new()),
            template_dir: template_path,
            cache_limit,
        })
    }
    
    /// Render a template with the given context
    #[pyo3(signature = (template_name, context=None))]
    pub fn render<'py>(&self, _py: Python<'py>, template_name: &str, context: Option<&Bound<'py, PyDict>>) -> PyResult<String> {
        let tera_context = match context {
            Some(ctx) => self.convert_py_context(ctx)?,
            None => Context::new(),
        };

        let tera = self.tera.read().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Lock error: {}", e)))?;
        match tera.render(template_name, &tera_context) {
            Ok(rendered) => Ok(rendered),
            Err(e) => {
                let error_info = self.parse_tera_error(&e, Some(template_name));
                let json_error = serde_json::to_string(&serde_json::json!({
                    "message": error_info.message,
                    "template_name": error_info.template_name,
                    "line": error_info.line,
                    "column": error_info.column
                })).unwrap_or_else(|_| "{\"message\": \"Error\"}".to_string());
                
                let msg = format!("GOBSTOPPER_TEMPLATE_ERROR:{}", json_error);
                eprintln!("DEBUG: Rust raising error: {}", msg);
                Err(pyo3::exceptions::PyRuntimeError::new_err(msg))
            }
        }
    }
    
    /// Render a template from string content
    #[pyo3(signature = (template_content, context=None, name=None))]
    pub fn render_string<'py>(&self, _py: Python<'py>, template_content: &str, context: Option<&Bound<'py, PyDict>>, name: Option<&str>) -> PyResult<String> {
        let tera_context = match context {
            Some(ctx) => self.convert_py_context(ctx)?,
            None => Context::new(),
        };
        let template_name = name.unwrap_or("string_template");

        let mut tera = self.tera.write().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Lock error: {}", e)))?;
        match tera.render_str(template_content, &tera_context) {
            Ok(rendered) => Ok(rendered),
            Err(e) => {
                let error_info = self.parse_tera_error(&e, Some(template_name));
                let json_error = serde_json::to_string(&serde_json::json!({
                    "message": error_info.message,
                    "template_name": error_info.template_name,
                    "line": error_info.line,
                    "column": error_info.column
                })).unwrap_or_else(|_| "{\"message\": \"Error\"}".to_string());
                
                let msg = format!("GOBSTOPPER_TEMPLATE_ERROR:{}", json_error);
                eprintln!("DEBUG: Rust raising error_string: {}", msg);
                Err(pyo3::exceptions::PyRuntimeError::new_err(msg))
            }
        }
    }
    
    pub fn add_template(&self, name: &str, content: &str) -> PyResult<()> {
        self.template_cache.insert(name.to_string(), content.to_string());
        Ok(())
    }
    
    pub fn reload(&self) -> PyResult<()> {
        Ok(())
    }
    
    pub fn clear_cache(&self) {
        self.template_cache.clear();
        self.context_cache.clear();
    }
    
    pub fn cache_stats<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let stats = PyDict::new_bound(py);
        stats.set_item("template_cache_size", self.template_cache.len())?;
        stats.set_item("context_cache_size", self.context_cache.len())?;
        stats.set_item("cache_limit", self.cache_limit)?;
        Ok(stats)
    }
    
    pub fn template_exists(&self, name: &str) -> bool {
        self.tera.read().unwrap().get_template_names().any(|tn| tn == name)
    }
    
    pub fn list_templates<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
        let tera = self.tera.read().unwrap();
        let names: Vec<&str> = tera.get_template_names().collect();
        Ok(PyList::new_bound(py, names))
    }
}

// Internal error info struct
struct TeraErrorInfo {
    message: String,
    template_name: Option<String>,
    line: Option<usize>,
    column: Option<usize>,
}

impl RustTemplateEngine {
    fn parse_tera_error(&self, error: &tera::Error, template_name: Option<&str>) -> TeraErrorInfo {
        use std::error::Error;
        let message = error.to_string();
        let mut line = None;
        let mut column = None;
        let re = Regex::new(r"at line (\d+), column (\d+)").unwrap();
        
        if let Some(caps) = re.captures(&message) {
            line = caps.get(1).and_then(|m| m.as_str().parse().ok());
            column = caps.get(2).and_then(|m| m.as_str().parse().ok());
        }

        if line.is_none() {
            let mut current_source = error.source();
            while let Some(err) = current_source {
                let s = err.to_string();
                if let Some(caps) = re.captures(&s) {
                    line = caps.get(1).and_then(|m| m.as_str().parse().ok());
                    column = caps.get(2).and_then(|m| m.as_str().parse().ok());
                    break;
                }
                current_source = err.source();
            }
        }

        TeraErrorInfo {
            message: self.format_tera_error(error, template_name.unwrap_or("unknown")),
            template_name: template_name.map(|s| s.to_string()),
            line,
            column,
        }
    }

    fn format_tera_error(&self, error: &tera::Error, template_name: &str) -> String {
        let mut error_parts = vec![format!("🚨 Template Rendering Error in '{}'", template_name), "=".repeat(60), String::new()];
        error_parts.push(format!("❌ {}", error));
        error_parts.push(String::new());
        error_parts.push("=".repeat(60));
        error_parts.join("\n")
    }

    fn convert_py_context<'py>(&self, py_dict: &Bound<'py, PyDict>) -> PyResult<Context> {
        let mut context = Context::new();
        for (key, value) in py_dict.iter() {
            let key_str = key.extract::<String>()?;
            let tera_value = self.python_to_tera_value(&value)?;
            context.insert(&key_str, &tera_value);
        }
        Ok(context)
    }
    
    fn python_to_tera_value<'py>(&self, py_value: &Bound<'py, PyAny>) -> PyResult<Value> {
        if py_value.is_none() { return Ok(Value::Null); }
        if let Ok(s) = py_value.downcast::<PyString>() { return Ok(Value::String(s.to_str()?.to_string())); }
        if let Ok(i) = py_value.downcast::<PyInt>() { return Ok(Value::Number(serde_json::Number::from(i.extract::<i64>()?))); }
        if let Ok(f) = py_value.downcast::<PyFloat>() { if let Some(num) = serde_json::Number::from_f64(f.extract::<f64>()?) { return Ok(Value::Number(num)); } }
        if let Ok(b) = py_value.downcast::<PyBool>() { return Ok(Value::Bool(b.extract::<bool>()?)); }
        if let Ok(list) = py_value.downcast::<PyList>() {
            let mut vec = Vec::new();
            for item in list.iter() { vec.push(self.python_to_tera_value(&item)?); }
            return Ok(Value::Array(vec));
        }
        if let Ok(dict) = py_value.downcast::<PyDict>() {
            let mut map = serde_json::Map::new();
            for (key, value) in dict.iter() {
                let key_str = key.extract::<String>()?;
                let tera_value = self.python_to_tera_value(&value)?;
                map.insert(key_str, tera_value);
            }
            return Ok(Value::Object(map));
        }
        Ok(Value::String(py_value.str()?.to_str()?.to_string()))
    }
}

// Built-in filters
fn currency_filter(value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
    Ok(Value::String(format!("${:.2}", value.as_f64().unwrap_or(0.0))))
}

fn relative_time_filter(value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
    Ok(Value::String(format!("{} ago", value.as_str().unwrap_or("unknown"))))
}

fn tojson_filter(value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
    Ok(Value::String(serde_json::to_string(value).unwrap_or_else(|_| "null".to_string())))
}