//! Policy Template System
//!
//! Provides templating system for Cedar policies with configuration-driven
//! approach.

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use chrono::{Datelike, Timelike};
use serde::{Deserialize, Serialize};
use tera::{Context, Tera};
use tracing::{debug, error, info, warn};

use crate::config::ControlCenterConfig;
use crate::error::{auth, infrastructure, policy, ControlCenterError, Result};

/// Policy template definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyTemplate {
    pub id: String,
    pub name: String,
    pub description: String,
    pub category: TemplateCategory,
    pub template_content: String,
    pub required_variables: Vec<TemplateVariable>,
    pub optional_variables: Vec<TemplateVariable>,
    pub compliance_frameworks: Vec<String>,
    pub tags: Vec<String>,
    pub version: String,
    pub author: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

/// Template categories for organization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemplateCategory {
    Authentication,
    Authorization,
    Infrastructure,
    Compliance,
    Security,
    Workflow,
    Anomaly,
}

/// Template variable definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateVariable {
    pub name: String,
    pub description: String,
    pub var_type: VariableType,
    pub default_value: Option<serde_json::Value>,
    pub validation_regex: Option<String>,
    pub allowed_values: Option<Vec<String>>,
}

/// Variable types for template parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VariableType {
    String,
    Number,
    Boolean,
    Array,
    Object,
}

/// Template rendering context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateContext {
    pub variables: HashMap<String, serde_json::Value>,
    pub environment: HashMap<String, String>,
    pub metadata: HashMap<String, serde_json::Value>,
}

/// Policy template manager
pub struct PolicyTemplateManager {
    tera: Tera,
    templates: HashMap<String, PolicyTemplate>,
    templates_dir: PathBuf,
}

impl PolicyTemplateManager {
    /// Create new template manager
    pub fn new(config: &ControlCenterConfig) -> Result<Self> {
        let templates_dir = config.policies.templates_dir.clone();

        // Initialize Tera templating engine
        let template_pattern = templates_dir
            .join("**/*.cedar.j2")
            .to_string_lossy()
            .to_string();
        let tera = Tera::new(&template_pattern).map_err(|e| {
            ControlCenterError::Infrastructure(infrastructure::InfrastructureError::Configuration(
                format!("Failed to initialize template engine: {}", e),
            ))
        })?;

        let mut manager = Self {
            tera,
            templates: HashMap::new(),
            templates_dir: templates_dir.clone(),
        };

        // Load built-in templates
        manager.load_builtin_templates()?;

        // Load templates from file system
        if templates_dir.exists() {
            manager.load_templates_from_directory()?;
        }

        Ok(manager)
    }

    /// Load built-in policy templates
    fn load_builtin_templates(&mut self) -> Result<()> {
        // Authentication templates
        self.register_builtin_template(
            "require-mfa",
            "Require Multi-Factor Authentication",
            TemplateCategory::Authentication,
            self.get_builtin_template_content("require-mfa"),
            vec![
                TemplateVariable {
                    name: "resource_types".to_string(),
                    description: "Resource types requiring MFA".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["Server", "Database"])),
                    validation_regex: None,
                    allowed_values: None,
                },
                TemplateVariable {
                    name: "exempt_roles".to_string(),
                    description: "Roles exempt from MFA requirement".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["ServiceAccount"])),
                    validation_regex: None,
                    allowed_values: None,
                },
            ],
        )?;

        // Infrastructure access control
        self.register_builtin_template(
            "production-approval",
            "Production Environment Approval",
            TemplateCategory::Infrastructure,
            self.get_builtin_template_content("production-approval"),
            vec![
                TemplateVariable {
                    name: "approval_roles".to_string(),
                    description: "Roles that can approve production access".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["ProductionAdmin", "SRE"])),
                    validation_regex: None,
                    allowed_values: None,
                },
                TemplateVariable {
                    name: "restricted_actions".to_string(),
                    description: "Actions requiring approval in production".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["Deploy", "Delete", "Modify"])),
                    validation_regex: None,
                    allowed_values: None,
                },
            ],
        )?;

        // Time-based access control
        self.register_builtin_template(
            "maintenance-window",
            "Maintenance Window Access Control",
            TemplateCategory::Infrastructure,
            self.get_builtin_template_content("maintenance-window"),
            vec![
                TemplateVariable {
                    name: "maintenance_start_hour".to_string(),
                    description: "Maintenance window start hour (24h format)".to_string(),
                    var_type: VariableType::Number,
                    default_value: Some(serde_json::json!(2)),
                    validation_regex: Some(r"^([01]?[0-9]|2[0-3])$".to_string()),
                    allowed_values: None,
                },
                TemplateVariable {
                    name: "maintenance_end_hour".to_string(),
                    description: "Maintenance window end hour (24h format)".to_string(),
                    var_type: VariableType::Number,
                    default_value: Some(serde_json::json!(6)),
                    validation_regex: Some(r"^([01]?[0-9]|2[0-3])$".to_string()),
                    allowed_values: None,
                },
            ],
        )?;

        // Geographic restrictions
        self.register_builtin_template(
            "geo-restriction",
            "Geographic Access Restrictions",
            TemplateCategory::Security,
            self.get_builtin_template_content("geo-restriction"),
            vec![
                TemplateVariable {
                    name: "allowed_countries".to_string(),
                    description: "List of allowed country codes".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["US", "CA", "GB"])),
                    validation_regex: None,
                    allowed_values: None,
                },
                TemplateVariable {
                    name: "restricted_resources".to_string(),
                    description: "Resources with geographic restrictions".to_string(),
                    var_type: VariableType::Array,
                    default_value: Some(serde_json::json!(["PHI", "PII"])),
                    validation_regex: None,
                    allowed_values: None,
                },
            ],
        )?;

        info!("Loaded {} built-in templates", self.templates.len());
        Ok(())
    }

    /// Register a built-in template
    fn register_builtin_template(
        &mut self,
        id: &str,
        name: &str,
        category: TemplateCategory,
        content: &str,
        variables: Vec<TemplateVariable>,
    ) -> Result<()> {
        let template = PolicyTemplate {
            id: id.to_string(),
            name: name.to_string(),
            description: format!("Built-in template: {}", name),
            category,
            template_content: content.to_string(),
            required_variables: variables,
            optional_variables: Vec::new(),
            compliance_frameworks: Vec::new(),
            tags: vec!["builtin".to_string()],
            version: "1.0.0".to_string(),
            author: "Control Center".to_string(),
            created_at: chrono::Utc::now(),
        };

        self.templates.insert(id.to_string(), template);
        Ok(())
    }

    /// Load templates from directory
    fn load_templates_from_directory(&mut self) -> Result<()> {
        if !self.templates_dir.exists() {
            return Ok(());
        }

        for entry in std::fs::read_dir(&self.templates_dir)? {
            let entry = entry?;
            let path = entry.path();

            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
                self.load_template_definition(&path)?;
            }
        }

        Ok(())
    }

    /// Load template definition from TOML file
    fn load_template_definition(&mut self, path: &Path) -> Result<()> {
        let content = std::fs::read_to_string(path)?;
        let template: PolicyTemplate = toml::from_str(&content).map_err(|e| {
            ControlCenterError::Infrastructure(infrastructure::InfrastructureError::Configuration(
                format!("Failed to parse template definition {:?}: {}", path, e),
            ))
        })?;

        self.templates.insert(template.id.clone(), template);
        debug!("Loaded template definition from {:?}", path);
        Ok(())
    }

    /// Render policy from template
    pub fn render_policy(
        &mut self,
        template_id: &str,
        context: &TemplateContext,
    ) -> Result<String> {
        let template = self.templates.get(template_id).ok_or_else(|| {
            ControlCenterError::Policy(policy::PolicyError::Parsing(format!(
                "Template not found: {}",
                template_id
            )))
        })?;

        // Validate required variables
        self.validate_context(template, context)?;

        // Build Tera context
        let mut tera_context = Context::new();

        // Add variables
        for (key, value) in &context.variables {
            tera_context.insert(key, value);
        }

        // Add environment variables
        for (key, value) in &context.environment {
            tera_context.insert(format!("env_{}", key), value);
        }

        // Add metadata
        for (key, value) in &context.metadata {
            tera_context.insert(format!("meta_{}", key), value);
        }

        // Add helper functions
        self.add_helper_functions(&mut tera_context);

        // Render template
        let rendered = self
            .tera
            .render_str(&template.template_content, &tera_context)
            .map_err(|e| {
                ControlCenterError::Policy(policy::PolicyError::Parsing(format!(
                    "Template rendering error: {}",
                    e
                )))
            })?;

        debug!("Successfully rendered template: {}", template_id);
        Ok(rendered)
    }

    /// Validate template context
    fn validate_context(&self, template: &PolicyTemplate, context: &TemplateContext) -> Result<()> {
        // Check required variables
        for var in &template.required_variables {
            if !context.variables.contains_key(&var.name) {
                return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                    format!("Required template variable missing: {}", var.name),
                )));
            }

            // Validate variable value
            self.validate_variable_value(var, context.variables.get(&var.name).unwrap())?;
        }

        // Validate optional variables if present
        for var in &template.optional_variables {
            if let Some(value) = context.variables.get(&var.name) {
                self.validate_variable_value(var, value)?;
            }
        }

        Ok(())
    }

    /// Validate individual variable value
    fn validate_variable_value(
        &self,
        var: &TemplateVariable,
        value: &serde_json::Value,
    ) -> Result<()> {
        // Type validation
        match var.var_type {
            VariableType::String => {
                if !value.is_string() {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!("Variable {} must be a string", var.name),
                    )));
                }
            }
            VariableType::Number => {
                if !value.is_number() {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!("Variable {} must be a number", var.name),
                    )));
                }
            }
            VariableType::Boolean => {
                if !value.is_boolean() {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!("Variable {} must be a boolean", var.name),
                    )));
                }
            }
            VariableType::Array => {
                if !value.is_array() {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!("Variable {} must be an array", var.name),
                    )));
                }
            }
            VariableType::Object => {
                if !value.is_object() {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!("Variable {} must be an object", var.name),
                    )));
                }
            }
        }

        // Regex validation
        if let Some(regex_pattern) = &var.validation_regex {
            if let Some(string_value) = value.as_str() {
                let regex = regex::Regex::new(regex_pattern).map_err(|e| {
                    ControlCenterError::Policy(policy::PolicyError::Parsing(format!(
                        "Invalid validation regex for {}: {}",
                        var.name, e
                    )))
                })?;

                if !regex.is_match(string_value) {
                    return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                        format!(
                            "Variable {} does not match pattern: {}",
                            var.name, regex_pattern
                        ),
                    )));
                }
            }
        }

        // Allowed values validation
        if let Some(allowed_values) = &var.allowed_values {
            let string_value = value.as_str().unwrap_or("");
            if !allowed_values.contains(&string_value.to_string()) {
                return Err(ControlCenterError::Policy(policy::PolicyError::Parsing(
                    format!("Variable {} must be one of: {:?}", var.name, allowed_values),
                )));
            }
        }

        Ok(())
    }

    /// Add helper functions to Tera context
    fn add_helper_functions(&self, context: &mut Context) {
        let now = chrono::Utc::now();
        context.insert("now_utc", &now.to_rfc3339());
        context.insert("now_timestamp", &now.timestamp());
        context.insert("now_hour", &now.hour());
        context.insert("now_day_of_week", &now.weekday().num_days_from_monday());
    }

    /// List available templates
    pub fn list_templates(&self) -> Vec<&PolicyTemplate> {
        self.templates.values().collect()
    }

    /// Get template by ID
    pub fn get_template(&self, template_id: &str) -> Option<&PolicyTemplate> {
        self.templates.get(template_id)
    }

    /// Get templates by category
    pub fn get_templates_by_category(&self, category: &TemplateCategory) -> Vec<&PolicyTemplate> {
        self.templates
            .values()
            .filter(|t| std::mem::discriminant(&t.category) == std::mem::discriminant(category))
            .collect()
    }

    /// Create template context from JSON
    pub fn create_context_from_json(&self, json_str: &str) -> Result<TemplateContext> {
        let json_value: serde_json::Value = serde_json::from_str(json_str)?;

        let variables = if let Some(obj) = json_value.as_object() {
            obj.clone().into_iter().collect()
        } else {
            HashMap::new()
        };

        Ok(TemplateContext {
            variables,
            environment: std::env::vars().collect(),
            metadata: HashMap::new(),
        })
    }

    /// Generate template documentation
    pub fn generate_template_docs(&self, template_id: &str) -> Result<String> {
        let template = self.get_template(template_id).ok_or_else(|| {
            ControlCenterError::Policy(policy::PolicyError::Parsing(format!(
                "Template not found: {}",
                template_id
            )))
        })?;

        let mut docs = String::new();
        docs.push_str(&format!("# {} Template\n\n", template.name));
        docs.push_str(&format!("**Description:** {}\n\n", template.description));
        docs.push_str(&format!("**Category:** {:?}\n\n", template.category));
        docs.push_str(&format!("**Version:** {}\n\n", template.version));

        if !template.required_variables.is_empty() {
            docs.push_str("## Required Variables\n\n");
            for var in &template.required_variables {
                docs.push_str(&format!(
                    "- **{}** ({:?}): {}\n",
                    var.name, var.var_type, var.description
                ));
                if let Some(default) = &var.default_value {
                    docs.push_str(&format!("  - Default: `{}`\n", default));
                }
                if let Some(regex) = &var.validation_regex {
                    docs.push_str(&format!("  - Pattern: `{}`\n", regex));
                }
            }
            docs.push('\n');
        }

        if !template.optional_variables.is_empty() {
            docs.push_str("## Optional Variables\n\n");
            for var in &template.optional_variables {
                docs.push_str(&format!(
                    "- **{}** ({:?}): {}\n",
                    var.name, var.var_type, var.description
                ));
            }
            docs.push('\n');
        }

        if !template.compliance_frameworks.is_empty() {
            docs.push_str(&format!(
                "**Compliance Frameworks:** {}\n\n",
                template.compliance_frameworks.join(", ")
            ));
        }

        Ok(docs)
    }
}

/// Built-in template content would be stored as separate files
/// For now, we'll include placeholder content as constants
const REQUIRE_MFA_TEMPLATE: &str = r#"
// Multi-Factor Authentication Policy
// Generated from template: require-mfa
permit(
    principal,
    action == Action::"access",
    resource in {{ resource_types | join(", ") }}
) when {
    principal has mfa_enabled && principal.mfa_enabled == true
    {% if exempt_roles %}
    || principal has role && principal.role in {{ exempt_roles | json_encode }}
    {% endif %}
};

// Log all access attempts for audit
@audit(true)
forbid(
    principal,
    action == Action::"access",
    resource in {{ resource_types | join(", ") }}
) when {
    !(principal has mfa_enabled) || principal.mfa_enabled == false
    {% if exempt_roles %}
    && !(principal has role && principal.role in {{ exempt_roles | json_encode }})
    {% endif %}
};
"#;

const PRODUCTION_APPROVAL_TEMPLATE: &str = r#"
// Production Environment Approval Policy
// Generated from template: production-approval
permit(
    principal,
    action in {{ restricted_actions | json_encode }},
    resource has environment && resource.environment == "production"
) when {
    principal has approval &&
    principal.approval.approved_by in {{ approval_roles | json_encode }} &&
    principal.approval.expires_at > {{ now_timestamp }}
};

forbid(
    principal,
    action in {{ restricted_actions | json_encode }},
    resource has environment && resource.environment == "production"
) when {
    !(principal has approval) ||
    !(principal.approval.approved_by in {{ approval_roles | json_encode }}) ||
    principal.approval.expires_at <= {{ now_timestamp }}
};
"#;

// Add template content as constants for the includes
impl PolicyTemplateManager {
    fn get_builtin_template_content(&self, template_id: &str) -> &'static str {
        match template_id {
            "require-mfa" => REQUIRE_MFA_TEMPLATE,
            "production-approval" => PRODUCTION_APPROVAL_TEMPLATE,
            "maintenance-window" => {
                r#"
// Maintenance Window Access Control
permit(
    principal,
    action == Action::"maintenance",
    resource
) when {
    {{ now_hour }} >= {{ maintenance_start_hour }} &&
    {{ now_hour }} < {{ maintenance_end_hour }}
};

forbid(
    principal,
    action == Action::"maintenance",
    resource
) when {
    {{ now_hour }} < {{ maintenance_start_hour }} ||
    {{ now_hour }} >= {{ maintenance_end_hour }}
};
"#
            }
            "geo-restriction" => {
                r#"
// Geographic Access Restrictions
permit(
    principal,
    action,
    resource in {{ restricted_resources | json_encode }}
) when {
    context has geo && context.geo has country &&
    context.geo.country in {{ allowed_countries | json_encode }}
};

forbid(
    principal,
    action,
    resource in {{ restricted_resources | json_encode }}
) when {
    !(context has geo) ||
    !(context.geo has country) ||
    !(context.geo.country in {{ allowed_countries | json_encode }})
};
"#
            }
            _ => "",
        }
    }
}
