//! Cost control guardrail rules.
//!
//! These rules enforce cost and performance limits on queries by checking
//! the cost signals from the query explanation against configured thresholds.

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

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

/// Rule: max_joins — limit the number of joins in a query.
pub struct MaxJoinsRule;

impl GuardrailRule for MaxJoinsRule {
    fn name(&self) -> &str {
        "max_joins"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && let Some(max) = cc.max_joins
        {
            let actual = explanation.cost_signals.join_count;
            if actual > max {
                violations.push(PolicyViolation {
                    rule: self.name().to_string(),
                    category: self.category(),
                    severity: ViolationSeverity::Error,
                    message: format!("Query has {actual} joins, exceeding the maximum of {max}"),
                    remediation: Some(
                        "Reduce the number of joins or split into multiple queries".to_string(),
                    ),
                    detail: ViolationDetail::ExceededLimit {
                        metric: "join_count".to_string(),
                        limit: max,
                        actual,
                    },
                });
            }
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: max_tables — limit the number of tables referenced.
pub struct MaxTablesRule;

impl GuardrailRule for MaxTablesRule {
    fn name(&self) -> &str {
        "max_tables"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && let Some(max) = cc.max_tables
        {
            let actual = explanation.cost_signals.table_count;
            if actual > max {
                violations.push(PolicyViolation {
                    rule: self.name().to_string(),
                    category: self.category(),
                    severity: ViolationSeverity::Error,
                    message: format!(
                        "Query references {actual} tables, exceeding the maximum of {max}"
                    ),
                    remediation: Some(
                        "Reduce the number of tables referenced in the query".to_string(),
                    ),
                    detail: ViolationDetail::ExceededLimit {
                        metric: "table_count".to_string(),
                        limit: max,
                        actual,
                    },
                });
            }
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: block_select_star — disallow SELECT * queries.
pub struct BlockSelectStarRule;

impl GuardrailRule for BlockSelectStarRule {
    fn name(&self) -> &str {
        "block_select_star"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && cc.block_select_star == Some(true)
            && explanation.cost_signals.uses_select_star
        {
            violations.push(PolicyViolation {
                rule: self.name().to_string(),
                category: self.category(),
                severity: ViolationSeverity::Error,
                message: "SELECT * is not allowed by policy".to_string(),
                remediation: Some(
                    "Specify explicit column names instead of using SELECT *".to_string(),
                ),
                detail: ViolationDetail::BlockedPattern {
                    pattern: "SELECT *".to_string(),
                },
            });
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: require_limit — require a LIMIT clause on queries.
pub struct RequireLimitRule;

impl GuardrailRule for RequireLimitRule {
    fn name(&self) -> &str {
        "require_limit"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && cc.require_limit == Some(true)
            && !explanation.cost_signals.has_limit
        {
            violations.push(PolicyViolation {
                rule: self.name().to_string(),
                category: self.category(),
                severity: ViolationSeverity::Error,
                message: "Query must include a LIMIT clause".to_string(),
                remediation: Some("Add a LIMIT clause to restrict result set size".to_string()),
                detail: ViolationDetail::BlockedPattern {
                    pattern: "missing LIMIT".to_string(),
                },
            });
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: block_cross_join — disallow CROSS JOIN queries.
pub struct BlockCrossJoinRule;

impl GuardrailRule for BlockCrossJoinRule {
    fn name(&self) -> &str {
        "block_cross_join"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && cc.block_cross_join == Some(true)
            && explanation.cost_signals.has_cross_join
        {
            violations.push(PolicyViolation {
                rule: self.name().to_string(),
                category: self.category(),
                severity: ViolationSeverity::Error,
                message: "CROSS JOIN is not allowed by policy".to_string(),
                remediation: Some(
                    "Use an explicit JOIN with an ON condition instead of CROSS JOIN".to_string(),
                ),
                detail: ViolationDetail::BlockedPattern {
                    pattern: "CROSS JOIN".to_string(),
                },
            });
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}

/// Rule: max_complexity_score — limit overall query complexity.
pub struct MaxComplexityScoreRule;

impl GuardrailRule for MaxComplexityScoreRule {
    fn name(&self) -> &str {
        "max_complexity_score"
    }

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

    fn evaluate(
        &self,
        _sql: &str,
        explanation: &QueryExplanation,
        _schema: &SchemaDefinition,
        config: &PolicyConfig,
    ) -> RuleResult {
        let mut violations = Vec::new();
        if let Some(cc) = &config.cost_control
            && let Some(max) = cc.max_complexity_score
        {
            let actual = explanation.cost_signals.complexity_score;
            if actual > max {
                violations.push(PolicyViolation {
                    rule: self.name().to_string(),
                    category: self.category(),
                    severity: ViolationSeverity::Error,
                    message: format!(
                        "Query complexity score is {actual}, exceeding the maximum of {max}"
                    ),
                    remediation: Some(
                        "Simplify the query by reducing joins, subqueries, or aggregations"
                            .to_string(),
                    ),
                    detail: ViolationDetail::ExceededLimit {
                        metric: "complexity_score".to_string(),
                        limit: max,
                        actual,
                    },
                });
            }
        }
        RuleResult {
            violations,
            warnings: Vec::new(),
        }
    }
}
