//! Type definitions for the query equivalence engine.
//!
//! All types derive `Debug, Clone, Serialize, Deserialize` for
//! consistent JSON output and programmatic consumption.

use serde::{Deserialize, Serialize};

/// The result of checking equivalence between two SQL queries.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EquivalenceResult {
    /// Our best guess whether the queries are equivalent
    pub equivalent: bool,

    /// Confidence in the equivalence assessment: 0.0 (definitely different) to 1.0 (very likely equivalent)
    pub confidence: f64,

    /// Specific differences found between the two queries
    pub differences: Vec<EquivalenceDiff>,

    /// Whether the output columns are compatible (same names and types)
    pub output_compatible: bool,

    /// Validation errors if either query could not be planned
    pub validation_errors: Vec<String>,
}

/// A specific difference between two queries.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EquivalenceDiff {
    /// The aspect being compared (e.g., "tables", "columns", "filters", "joins", "aggregation")
    pub aspect: String,

    /// Human-readable description of the difference
    pub description: String,

    /// Whether this is a structural or semantic difference
    pub severity: DiffSeverity,
}

/// Severity of a difference between two queries.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DiffSeverity {
    /// Structural difference (different tables, joins, etc.)
    Structural,
    /// Semantic difference (different filters, predicates, etc.)
    Semantic,
    /// Minor difference (aliases, formatting, ordering)
    Minor,
}

impl std::fmt::Display for DiffSeverity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DiffSeverity::Structural => write!(f, "structural"),
            DiffSeverity::Semantic => write!(f, "semantic"),
            DiffSeverity::Minor => write!(f, "minor"),
        }
    }
}

/// The result of checking a query against old and new schema versions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegressionResult {
    /// Whether the query still passes validation against the new schema
    pub passes: bool,

    /// Breaking changes that affect the query
    pub breaking_changes: Vec<BreakingChange>,

    /// Non-breaking warnings
    pub warnings: Vec<String>,

    /// Validation errors if the query failed against the old schema too
    pub validation_errors: Vec<String>,
}

/// A schema change that breaks an existing query.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakingChange {
    /// Type of breaking change
    pub change_type: String,

    /// The schema element that changed
    pub affected_element: String,

    /// Detailed description of the change
    pub detail: String,

    /// Suggested fix
    #[serde(skip_serializing_if = "Option::is_none")]
    pub suggestion: Option<String>,
}

impl EquivalenceResult {
    /// Create a result for queries that could not be compared (validation failure).
    pub fn invalid(errors: Vec<String>) -> Self {
        Self {
            equivalent: false,
            confidence: 0.0,
            differences: vec![],
            output_compatible: false,
            validation_errors: errors,
        }
    }

    /// Create a result with the given differences and compute equivalence.
    pub fn with_diffs(differences: Vec<EquivalenceDiff>, output_compatible: bool) -> Self {
        let (equivalent, confidence) = compute_equivalence(&differences, output_compatible);
        Self {
            equivalent,
            confidence,
            differences,
            output_compatible,
            validation_errors: vec![],
        }
    }
}

impl RegressionResult {
    /// Create a result for a query that was already invalid against the old schema.
    pub fn already_invalid(errors: Vec<String>) -> Self {
        Self {
            passes: false,
            breaking_changes: vec![],
            warnings: vec!["Query was already invalid against the old schema.".to_string()],
            validation_errors: errors,
        }
    }
}

/// Compute equivalence and confidence from differences.
fn compute_equivalence(diffs: &[EquivalenceDiff], output_compatible: bool) -> (bool, f64) {
    if diffs.is_empty() && output_compatible {
        return (true, 0.95); // High confidence but not 100% (heuristic)
    }

    let mut score = 1.0_f64;

    for diff in diffs {
        let penalty = match diff.severity {
            DiffSeverity::Structural => 0.4,
            DiffSeverity::Semantic => 0.25,
            DiffSeverity::Minor => 0.05,
        };
        score -= penalty;
    }

    if !output_compatible {
        score -= 0.3;
    }

    let score = score.max(0.0);
    let equivalent = score >= 0.7 && output_compatible;

    (equivalent, score)
}
