//! Cost signal analysis for SQL queries.
//!
//! Analyzes plan steps and lineage to compute complexity scores,
//! identify risk flags, and detect unfiltered table scans.

use super::types::*;
use std::collections::HashSet;

/// Analyze cost signals from plan steps and lineage.
///
/// Computes a complexity score and generates risk flags based on
/// the query structure. This does NOT estimate actual runtime cost
/// (we have no data), but identifies structural patterns that typically
/// correlate with expensive queries.
pub fn analyze_cost(steps: &[PlanStep], lineage: &QueryLineage) -> CostSignals {
    let mut join_count = 0;
    let mut has_aggregation = false;
    let mut has_subquery = false;
    let mut has_window_function = false;
    let mut has_limit = false;
    let mut has_cross_join = false;
    let mut uses_select_star = false;
    let mut has_distinct = false;
    let mut has_union = false;
    let mut filter_count = 0;
    let mut has_group_by = false;

    // Tables that have at least one filter applied
    let mut filtered_tables: HashSet<String> = HashSet::new();
    // All tables from scans
    let mut scan_tables: Vec<String> = Vec::new();

    for step in all_steps(steps) {
        match &step.operation {
            PlanOperation::TableScan {
                table,
                columns_read,
                filters,
            } => {
                scan_tables.push(table.clone());
                if !filters.is_empty() {
                    filtered_tables.insert(table.clone());
                }
                // Check for SELECT * pattern: if columns_read matches a large number
                // or if we see all columns being read, flag it
                // Heuristic: if reading 5+ columns, likely SELECT *
                if columns_read.len() >= 5 {
                    // This is a rough heuristic; will be refined
                }
                let _ = columns_read; // used above
            }
            PlanOperation::Join { join_type, .. } => {
                join_count += 1;
                if *join_type == JoinKind::Cross {
                    has_cross_join = true;
                }
            }
            PlanOperation::Filter { .. } => {
                filter_count += 1;
                // Try to associate this filter with tables
                // Filters generally apply to the child scan tables
            }
            PlanOperation::Aggregate { group_by, .. } => {
                has_aggregation = true;
                if !group_by.is_empty() {
                    has_group_by = true;
                }
            }
            PlanOperation::Sort { .. } => {}
            PlanOperation::Limit { .. } => {
                has_limit = true;
            }
            PlanOperation::Projection { columns } => {
                // Check for SELECT * pattern
                for col in columns {
                    if col.contains('*') {
                        uses_select_star = true;
                    }
                }
            }
            PlanOperation::Window { .. } => {
                has_window_function = true;
            }
            PlanOperation::Subquery { .. } => {
                has_subquery = true;
            }
            PlanOperation::Union { .. } => {
                has_union = true;
            }
            PlanOperation::Distinct => {
                has_distinct = true;
            }
            PlanOperation::EmptyRelation => {}
        }
    }

    // Associate filters with tables based on lineage
    // If there's a filter in the plan, mark all tables in the filter scope
    // For simplicity, if filter_count > 0, assume at least one table is filtered
    // More precise tracking would require analyzing filter predicates

    let table_count = lineage.tables.len();

    // Find unfiltered scans
    let unfiltered_scans: Vec<String> = scan_tables
        .iter()
        .filter(|t| !filtered_tables.contains(t.as_str()))
        .cloned()
        .collect();

    // If there are filters but no pushdown filters on scans,
    // the tables are still effectively filtered
    let effective_unfiltered = if filter_count > 0 && unfiltered_scans.len() == scan_tables.len() {
        // Filters exist but none are pushed down — still counts as filtered for risk
        Vec::new()
    } else {
        unfiltered_scans
    };

    // Complexity score formula
    let mut score: usize = 0;
    score += join_count * 2;
    if has_aggregation {
        score += 1;
    }
    if has_subquery {
        score += 3;
    }
    if has_window_function {
        score += 2;
    }
    if has_cross_join {
        score += 5;
    }
    if has_distinct {
        score += 1;
    }
    if has_union {
        score += 2;
    }
    if !has_limit && table_count > 1 {
        score += 1;
    }
    score += effective_unfiltered.len();

    let complexity = match score {
        0..=2 => QueryComplexity::Low,
        3..=5 => QueryComplexity::Moderate,
        6..=9 => QueryComplexity::High,
        _ => QueryComplexity::Extreme,
    };

    // Build risk flags
    let mut risk_flags = Vec::new();

    if has_cross_join {
        risk_flags.push(RiskFlag {
            severity: RiskSeverity::Critical,
            flag: "cross_join".to_string(),
            detail: "CROSS JOIN produces a cartesian product which can be extremely expensive"
                .to_string(),
        });
    }

    if effective_unfiltered.len() > 1 {
        risk_flags.push(RiskFlag {
            severity: RiskSeverity::Warning,
            flag: "multiple_unfiltered_scans".to_string(),
            detail: format!(
                "Multiple tables scanned without filters: {}",
                effective_unfiltered.join(", ")
            ),
        });
    }

    if !has_limit && table_count > 1 {
        risk_flags.push(RiskFlag {
            severity: RiskSeverity::Info,
            flag: "no_limit_multi_table".to_string(),
            detail: "Multi-table query without LIMIT may return large result sets".to_string(),
        });
    }

    if has_distinct && has_group_by {
        risk_flags.push(RiskFlag {
            severity: RiskSeverity::Info,
            flag: "redundant_distinct".to_string(),
            detail: "DISTINCT with GROUP BY may be redundant since GROUP BY already deduplicates"
                .to_string(),
        });
    }

    CostSignals {
        complexity,
        complexity_score: score,
        join_count,
        has_aggregation,
        has_subquery,
        has_window_function,
        has_limit,
        has_cross_join,
        uses_select_star,
        has_distinct,
        has_union,
        table_count,
        filter_count,
        unfiltered_scans: effective_unfiltered,
        risk_flags,
        cost_estimate: None,
        complexity_score_v2: None,
    }
}

/// Flatten all steps including those nested in subqueries for analysis.
fn all_steps(steps: &[PlanStep]) -> Vec<&PlanStep> {
    let mut result = Vec::new();
    for step in steps {
        result.push(step);
        if let PlanOperation::Subquery { inner_steps, .. } = &step.operation {
            result.extend(all_steps(inner_steps));
        }
    }
    result
}
