//! Data protection guardrail rules.
//!
//! These rules enforce data access controls by checking which columns
//! and tables are accessed against blocklists in the policy configuration.

use crate::explainer::types::{PlanOperation, QueryExplanation};
use crate::schema::types::SchemaDefinition;

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

/// Rule: blocked_columns — prevent access to specific columns.
pub struct BlockedColumnsRule;

impl GuardrailRule for BlockedColumnsRule {
    fn name(&self) -> &str {
        "blocked_columns"
    }

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

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

        let dp = match &config.data_protection {
            Some(dp) => dp,
            None => {
                return RuleResult {
                    violations,
                    warnings: Vec::new(),
                };
            }
        };

        for blocked in &dp.blocked_columns {
            let blocked_table = blocked.table.to_lowercase();
            for col_ref in &explanation.lineage.columns_read {
                if col_ref.table.to_lowercase() == blocked_table {
                    let col_lower = col_ref.column.to_lowercase();
                    if blocked
                        .columns
                        .iter()
                        .any(|c| c.to_lowercase() == col_lower)
                    {
                        let message = blocked.message.clone().unwrap_or_else(|| {
                            format!(
                                "Access to column '{}.{}' is blocked by policy",
                                col_ref.table, col_ref.column
                            )
                        });
                        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 access",
                                col_ref.column
                            )),
                            detail: ViolationDetail::BlockedColumn {
                                table: col_ref.table.clone(),
                                column: col_ref.column.clone(),
                            },
                        });
                    }
                }
            }
        }

        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: blocked_tables — prevent access to specific tables.
pub struct BlockedTablesRule;

impl GuardrailRule for BlockedTablesRule {
    fn name(&self) -> &str {
        "blocked_tables"
    }

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

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

        let dp = match &config.data_protection {
            Some(dp) => dp,
            None => {
                return RuleResult {
                    violations,
                    warnings: Vec::new(),
                };
            }
        };

        for blocked in &dp.blocked_tables {
            let blocked_table = blocked.table.to_lowercase();
            for table in &explanation.lineage.tables {
                if table.to_lowercase() == blocked_table {
                    let message = blocked.message.clone().unwrap_or_else(|| {
                        format!("Access to table '{table}' is blocked by policy")
                    });
                    violations.push(PolicyViolation {
                        rule: self.name().to_string(),
                        category: self.category(),
                        severity: ViolationSeverity::Error,
                        message,
                        remediation: Some(format!(
                            "Remove table '{table}' from the query or request access"
                        )),
                        detail: ViolationDetail::BlockedTable {
                            table: table.clone(),
                        },
                    });
                }
            }
        }

        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: require_where_clause — require a WHERE clause on queries.
pub struct RequireWhereClauseRule;

impl GuardrailRule for RequireWhereClauseRule {
    fn name(&self) -> &str {
        "require_where_clause"
    }

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

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

        let dp = match &config.data_protection {
            Some(dp) => dp,
            None => {
                return RuleResult {
                    violations,
                    warnings: Vec::new(),
                };
            }
        };

        if dp.require_where_clause == Some(true) {
            let has_filter = explanation
                .plan_steps
                .iter()
                .any(|step| matches!(step.operation, PlanOperation::Filter { .. }));

            if !has_filter {
                violations.push(PolicyViolation {
                    rule: self.name().to_string(),
                    category: self.category(),
                    severity: ViolationSeverity::Error,
                    message: "Query must include a WHERE clause".to_string(),
                    remediation: Some("Add a WHERE clause to filter the result set".to_string()),
                    detail: ViolationDetail::BlockedPattern {
                        pattern: "missing WHERE clause".to_string(),
                    },
                });
            }
        }

        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}
