//! Type definitions for the SQL safety scanner.
//!
//! All types derive `Debug, Clone, Serialize, Deserialize` for structured
//! JSON output and programmatic consumption.

use serde::{Deserialize, Serialize};

/// Configuration for the safety scanner.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SafetyConfig {
    /// Which rules to enable (default: all)
    pub rules: Vec<SafetyRule>,

    /// Severity threshold: block only Critical, or also High/Medium
    pub min_severity: SafetySeverity,

    /// Allow multi-statement SQL (default: false)
    pub allow_multi_statement: bool,

    /// Allowed statement types (default: SELECT only)
    pub allowed_statements: Vec<String>,
}

impl Default for SafetyConfig {
    fn default() -> Self {
        Self {
            rules: SafetyRule::all(),
            min_severity: SafetySeverity::Medium,
            allow_multi_statement: false,
            allowed_statements: vec!["SELECT".to_string(), "WITH".to_string()],
        }
    }
}

/// Result of scanning a SQL query for safety threats.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SafetyScanResult {
    /// Whether the query is considered safe
    pub safe: bool,

    /// List of detected threats
    pub threats: Vec<ThreatFinding>,

    /// Overall risk score from 0.0 (safe) to 1.0 (critical)
    pub risk_score: f64,

    /// Number of SQL statements detected
    pub statement_count: usize,

    /// Types of statements detected (e.g., SELECT, DROP, DELETE)
    pub statement_types: Vec<String>,
}

/// A single threat finding from the safety scan.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThreatFinding {
    /// The rule that detected this threat
    pub rule: SafetyRule,

    /// Severity of the threat
    pub severity: SafetySeverity,

    /// Human-readable summary of the threat
    pub message: String,

    /// Detailed explanation of why this is a threat
    pub detail: String,

    /// Location in the SQL string: (byte offset, byte length)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub location: Option<(usize, usize)>,

    /// The pattern or text that was matched
    pub matched_pattern: String,
}

/// Safety detection rules.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SafetyRule {
    /// Detects `;` followed by dangerous statements (DROP, DELETE, etc.)
    MultiStatement,
    /// Detects UNION SELECT injection patterns
    UnionInjection,
    /// Detects `--` or `/* */` used to truncate/alter query intent
    CommentInjection,
    /// Detects COMMIT, ROLLBACK that break read-only transactions
    TransactionEscape,
    /// Detects multiple complete SQL statements in one input
    StackedQueries,
    /// Detects OR 1=1, OR 'a'='a', OR TRUE tautology patterns
    TautologyAttack,
    /// Detects SLEEP(), BENCHMARK(), WAITFOR DELAY, pg_sleep()
    SleepAttack,
    /// Detects queries probing information_schema, pg_catalog, sys. tables
    InfoSchemaProbe,
    /// Detects string concatenation in WHERE predicates
    StringConcatPredicate,
    /// Detects CHAR(), CHR(), 0x hex encoding bypass attempts
    EncodingBypass,
}

impl SafetyRule {
    /// Returns all available safety rules.
    pub fn all() -> Vec<SafetyRule> {
        vec![
            SafetyRule::MultiStatement,
            SafetyRule::UnionInjection,
            SafetyRule::CommentInjection,
            SafetyRule::TransactionEscape,
            SafetyRule::StackedQueries,
            SafetyRule::TautologyAttack,
            SafetyRule::SleepAttack,
            SafetyRule::InfoSchemaProbe,
            SafetyRule::StringConcatPredicate,
            SafetyRule::EncodingBypass,
        ]
    }

    /// Human-readable name for the rule.
    pub fn name(&self) -> &'static str {
        match self {
            SafetyRule::MultiStatement => "multi_statement_injection",
            SafetyRule::UnionInjection => "union_injection",
            SafetyRule::CommentInjection => "comment_injection",
            SafetyRule::TransactionEscape => "transaction_escape",
            SafetyRule::StackedQueries => "stacked_queries",
            SafetyRule::TautologyAttack => "tautology_attack",
            SafetyRule::SleepAttack => "sleep_attack",
            SafetyRule::InfoSchemaProbe => "info_schema_probe",
            SafetyRule::StringConcatPredicate => "string_concat_predicate",
            SafetyRule::EncodingBypass => "encoding_bypass",
        }
    }
}

impl std::fmt::Display for SafetyRule {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name())
    }
}

/// Severity levels for safety threats.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SafetySeverity {
    /// Informational, unlikely to be harmful
    Low = 0,
    /// Suspicious pattern, might be intentional
    Medium = 1,
    /// Likely injection attempt
    High = 2,
    /// Definite injection / destructive operation
    Critical = 3,
}

impl std::fmt::Display for SafetySeverity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SafetySeverity::Low => write!(f, "low"),
            SafetySeverity::Medium => write!(f, "medium"),
            SafetySeverity::High => write!(f, "high"),
            SafetySeverity::Critical => write!(f, "critical"),
        }
    }
}
