//! Tag-based guardrail rules.
//!
//! These rules cross-reference columns read by a query against semantic
//! tags defined in the schema (e.g., "pii", "deprecated", "fiscal").

use crate::explainer::types::QueryExplanation;
use crate::schema::types::SchemaDefinition;

use super::super::policy::{PolicyConfig, TagAction};
use super::super::types::{
    PolicyCategory, PolicyViolation, PolicyWarning, ViolationDetail, ViolationSeverity,
};
use super::{GuardrailRule, RuleResult};

/// Rule: tag-based access control.
///
/// For each column read by the query, looks up its tags in the schema.
/// If a tag matches a configured tag_rule and the column is not exempted,
/// the rule produces a violation (block) or warning (warn).
pub struct TagRule;

impl GuardrailRule for TagRule {
    fn name(&self) -> &str {
        "tag_rules"
    }

    fn category(&self) -> PolicyCategory {
        PolicyCategory::TagRules
    }

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        let mut warnings = Vec::new();

        if config.tag_rules.is_empty() {
            return RuleResult {
                violations,
                warnings,
            };
        }

        for col_ref in &explanation.lineage.columns_read {
            // Look up the column's tags in the schema
            let tags = match schema.tables.get(&col_ref.table) {
                Some(table_def) => {
                    match table_def
                        .columns
                        .iter()
                        .find(|c| c.name.to_lowercase() == col_ref.column.to_lowercase())
                    {
                        Some(col_def) => &col_def.tags,
                        None => continue,
                    }
                }
                None => continue,
            };

            for tag_rule in &config.tag_rules {
                // Check if this column has the tag (case-insensitive)
                let has_tag = tags
                    .iter()
                    .any(|t| t.to_lowercase() == tag_rule.tag.to_lowercase());

                if !has_tag {
                    continue;
                }

                // Check if the column is in the exceptions list
                let column_pattern = format!("{}.{}", col_ref.table, col_ref.column);
                let is_excepted = tag_rule
                    .exceptions
                    .iter()
                    .any(|exc| exc.to_lowercase() == column_pattern.to_lowercase());

                if is_excepted {
                    continue;
                }

                let message = tag_rule.message.clone().unwrap_or_else(|| {
                    format!(
                        "Column '{}.{}' has tag '{}' which is restricted by policy",
                        col_ref.table, col_ref.column, tag_rule.tag
                    )
                });

                match tag_rule.action {
                    TagAction::Block => {
                        violations.push(PolicyViolation {
                            rule: self.name().to_string(),
                            category: self.category(),
                            severity: ViolationSeverity::Error,
                            message,
                            remediation: Some(format!(
                                "Remove column '{}.{}' from the query or request an exception",
                                col_ref.table, col_ref.column
                            )),
                            detail: ViolationDetail::TagViolation {
                                tag: tag_rule.tag.clone(),
                                table: col_ref.table.clone(),
                                column: col_ref.column.clone(),
                            },
                        });
                    }
                    TagAction::Warn => {
                        warnings.push(PolicyWarning {
                            rule: self.name().to_string(),
                            category: self.category(),
                            message,
                        });
                    }
                }
            }
        }

        RuleResult {
            violations,
            warnings,
        }
    }
}
