//! SQL tag analysis engine.
//!
//! Parses SQL once using polyglot-sql and runs all tag analyses in Rust.
//! Uses a 32MB stack thread to handle deep AST recursion.

use super::rules;
use super::types::{AnalysisConfig, TagResult};
use polyglot_sql::dialects::DialectType;

/// Stack size for the tag analysis thread (32MB to handle deep AST recursion).
const TAG_THREAD_STACK_SIZE: usize = 32 * 1024 * 1024;

/// Run all tag analyses on a single SQL string.
///
/// 1. Runs string-only tags (create_or_replace_table) without parsing.
/// 2. Parses SQL once with polyglot-sql.
/// 3. Runs all AST-based tags on each parsed statement.
/// 4. On parse failure, returns not_triggered for all AST tags.
pub fn analyze_tags(sql: &str, dialect: DialectType, config: &AnalysisConfig) -> Vec<TagResult> {
    let sql_owned = sql.to_string();
    let config_owned = config.clone();

    std::thread::Builder::new()
        .name("tag-analysis".to_string())
        .stack_size(TAG_THREAD_STACK_SIZE)
        .spawn(move || analyze_tags_inner(&sql_owned, dialect, &config_owned))
        .map(|handle| handle.join().unwrap_or_default())
        .unwrap_or_default()
}

fn analyze_tags_inner(sql: &str, dialect: DialectType, config: &AnalysisConfig) -> Vec<TagResult> {
    let skip = &config.skip_tags;
    let mut results = Vec::new();

    // String-only tag — no parsing needed
    if !skip
        .iter()
        .any(|s| s == rules::create_or_replace_table::TAG_NAME)
    {
        results.push(rules::create_or_replace_table::check(sql));
    }

    // Parse SQL — on failure, return not_triggered for all AST tags
    let statements = match polyglot_sql::parse(sql, dialect) {
        Ok(stmts) => stmts,
        Err(_) => {
            push_not_triggered_ast_tags(&mut results, skip);
            return results;
        }
    };

    if statements.is_empty() {
        push_not_triggered_ast_tags(&mut results, skip);
        return results;
    }

    // Run AST tags on each statement, aggregating: triggered if ANY statement triggers
    let mut select_star = TagResult::not_triggered(rules::select_star::TAG_NAME);
    let mut filter_has_func = TagResult::not_triggered(rules::filter_has_func::TAG_NAME);
    let mut join_has_func = TagResult::not_triggered(rules::join_has_func::TAG_NAME);
    let mut agg_before_join = TagResult::not_triggered(rules::agg_before_join::TAG_NAME);
    let mut select_without_limit = TagResult::not_triggered(rules::select_without_limit::TAG_NAME);

    for stmt in &statements {
        if !skip.iter().any(|s| s == rules::select_star::TAG_NAME) {
            let r = rules::select_star::check(stmt, dialect);
            if r.triggered {
                select_star = r;
            }
        }
        if !skip.iter().any(|s| s == rules::filter_has_func::TAG_NAME) {
            let r = rules::filter_has_func::check(stmt, dialect);
            if r.triggered {
                filter_has_func = r;
            }
        }
        if !skip.iter().any(|s| s == rules::join_has_func::TAG_NAME) {
            let r = rules::join_has_func::check(stmt, dialect);
            if r.triggered {
                join_has_func = r;
            }
        }
        if !skip.iter().any(|s| s == rules::agg_before_join::TAG_NAME) {
            let r = rules::agg_before_join::check(stmt);
            if r.triggered {
                agg_before_join = r;
            }
        }
        if !skip
            .iter()
            .any(|s| s == rules::select_without_limit::TAG_NAME)
        {
            let r = rules::select_without_limit::check(stmt);
            if r.triggered {
                select_without_limit = r;
            }
        }
    }

    if !skip.iter().any(|s| s == rules::select_star::TAG_NAME) {
        results.push(select_star);
    }
    if !skip.iter().any(|s| s == rules::filter_has_func::TAG_NAME) {
        results.push(filter_has_func);
    }
    if !skip.iter().any(|s| s == rules::join_has_func::TAG_NAME) {
        results.push(join_has_func);
    }
    if !skip.iter().any(|s| s == rules::agg_before_join::TAG_NAME) {
        results.push(agg_before_join);
    }
    if !skip
        .iter()
        .any(|s| s == rules::select_without_limit::TAG_NAME)
    {
        results.push(select_without_limit);
    }

    results
}

fn push_not_triggered_ast_tags(results: &mut Vec<TagResult>, skip: &[String]) {
    if !skip.iter().any(|s| s == rules::select_star::TAG_NAME) {
        results.push(TagResult::not_triggered(rules::select_star::TAG_NAME));
    }
    if !skip.iter().any(|s| s == rules::filter_has_func::TAG_NAME) {
        results.push(TagResult::not_triggered(rules::filter_has_func::TAG_NAME));
    }
    if !skip.iter().any(|s| s == rules::join_has_func::TAG_NAME) {
        results.push(TagResult::not_triggered(rules::join_has_func::TAG_NAME));
    }
    if !skip.iter().any(|s| s == rules::agg_before_join::TAG_NAME) {
        results.push(TagResult::not_triggered(rules::agg_before_join::TAG_NAME));
    }
    if !skip
        .iter()
        .any(|s| s == rules::select_without_limit::TAG_NAME)
    {
        results.push(TagResult::not_triggered(
            rules::select_without_limit::TAG_NAME,
        ));
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn analyze_tags_select_star() {
        let results = analyze_tags(
            "SELECT * FROM t",
            DialectType::Snowflake,
            &AnalysisConfig::default(),
        );
        let star = results
            .iter()
            .find(|r| r.tag_name == "select_star")
            .unwrap();
        assert!(star.triggered);
        let limit = results
            .iter()
            .find(|r| r.tag_name == "select_without_limit")
            .unwrap();
        assert!(limit.triggered);
    }

    #[test]
    fn analyze_tags_parse_failure() {
        let results = analyze_tags(
            "NOT VALID SQL !!@#$",
            DialectType::Snowflake,
            &AnalysisConfig::default(),
        );
        assert_eq!(results.len(), 6);
        for r in &results {
            assert!(!r.triggered);
        }
    }

    #[test]
    fn analyze_tags_with_skip() {
        let config = AnalysisConfig {
            skip_tags: vec!["select_star".to_string()],
        };
        let results = analyze_tags("SELECT * FROM t", DialectType::Snowflake, &config);
        assert!(results.iter().all(|r| r.tag_name != "select_star"));
        assert_eq!(results.len(), 5);
    }

    #[test]
    fn analyze_tags_filter_has_func() {
        let results = analyze_tags(
            "SELECT x FROM t WHERE UPPER(name) = 'FOO'",
            DialectType::Snowflake,
            &AnalysisConfig::default(),
        );
        let ff = results
            .iter()
            .find(|r| r.tag_name == "filter_has_func")
            .unwrap();
        assert!(ff.triggered);
    }

    #[test]
    fn analyze_tags_join_has_func() {
        let results = analyze_tags(
            "SELECT * FROM t1 JOIN t2 ON LOWER(t1.a) = t2.a",
            DialectType::Snowflake,
            &AnalysisConfig::default(),
        );
        let jf = results
            .iter()
            .find(|r| r.tag_name == "join_has_func")
            .unwrap();
        assert!(jf.triggered);
    }

    #[test]
    fn analyze_tags_create_or_replace() {
        let results = analyze_tags(
            "CREATE OR REPLACE TABLE x AS SELECT 1",
            DialectType::Snowflake,
            &AnalysisConfig::default(),
        );
        let cr = results
            .iter()
            .find(|r| r.tag_name == "create_or_replace_table")
            .unwrap();
        assert!(cr.triggered);
    }
}
