//! LINT_AL_004: Unique table alias.
//!
//! Table aliases should be unique within a query scope.

use crate::linter::config::LintConfig;
use crate::linter::rule::{LintContext, LintRule};
use crate::types::{issue_codes, Issue};
use sqlparser::ast::{
    CreateView, Expr, FunctionArg, FunctionArgExpr, FunctionArguments, Query, Select, SetExpr,
    Statement, TableFactor, TableWithJoins, WindowType,
};
use std::collections::HashSet;

use super::semantic_helpers::visit_select_expressions;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum AliasCaseCheck {
    Dialect,
    CaseInsensitive,
    QuotedCsNakedUpper,
    QuotedCsNakedLower,
    CaseSensitive,
}

impl AliasCaseCheck {
    fn from_config(config: &LintConfig) -> Self {
        match config
            .rule_option_str(issue_codes::LINT_AL_004, "alias_case_check")
            .unwrap_or("dialect")
            .to_ascii_lowercase()
            .as_str()
        {
            "case_insensitive" => Self::CaseInsensitive,
            "quoted_cs_naked_upper" => Self::QuotedCsNakedUpper,
            "quoted_cs_naked_lower" => Self::QuotedCsNakedLower,
            "case_sensitive" => Self::CaseSensitive,
            _ => Self::Dialect,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct AliasRef {
    name: String,
    quoted: bool,
}

pub struct AliasingUniqueTable {
    alias_case_check: AliasCaseCheck,
}

impl AliasingUniqueTable {
    pub fn from_config(config: &LintConfig) -> Self {
        Self {
            alias_case_check: AliasCaseCheck::from_config(config),
        }
    }
}

impl Default for AliasingUniqueTable {
    fn default() -> Self {
        Self {
            alias_case_check: AliasCaseCheck::Dialect,
        }
    }
}

impl LintRule for AliasingUniqueTable {
    fn code(&self) -> &'static str {
        issue_codes::LINT_AL_004
    }

    fn name(&self) -> &'static str {
        "Unique table alias"
    }

    fn description(&self) -> &'static str {
        "Table aliases should be unique within each clause."
    }

    fn check(&self, statement: &Statement, ctx: &LintContext) -> Vec<Issue> {
        if first_duplicate_table_alias_in_statement(statement, self.alias_case_check).is_none() {
            return Vec::new();
        }

        vec![Issue::warning(
            issue_codes::LINT_AL_004,
            "Table aliases should be unique within a statement.",
        )
        .with_statement(ctx.statement_index)]
    }
}

fn first_duplicate_table_alias_in_statement(
    statement: &Statement,
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    match statement {
        Statement::Query(query) => {
            first_duplicate_table_alias_in_query_with_parent(query, &[], alias_case_check)
        }
        Statement::Insert(insert) => insert.source.as_deref().and_then(|query| {
            first_duplicate_table_alias_in_query_with_parent(query, &[], alias_case_check)
        }),
        Statement::CreateView(CreateView { query, .. }) => {
            first_duplicate_table_alias_in_query_with_parent(query, &[], alias_case_check)
        }
        Statement::CreateTable(create) => create.query.as_deref().and_then(|query| {
            first_duplicate_table_alias_in_query_with_parent(query, &[], alias_case_check)
        }),
        _ => None,
    }
}

fn first_duplicate_table_alias_in_query_with_parent(
    query: &Query,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    if let Some(with) = &query.with {
        for cte in &with.cte_tables {
            if let Some(duplicate) =
                first_duplicate_table_alias_in_query_with_parent(&cte.query, &[], alias_case_check)
            {
                return Some(duplicate);
            }
        }
    }

    first_duplicate_table_alias_in_set_expr_with_parent(
        &query.body,
        parent_aliases,
        alias_case_check,
    )
}

fn first_duplicate_table_alias_in_set_expr_with_parent(
    set_expr: &SetExpr,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    match set_expr {
        SetExpr::Select(select) => first_duplicate_table_alias_in_select_with_parent(
            select,
            parent_aliases,
            alias_case_check,
        ),
        SetExpr::Query(query) => first_duplicate_table_alias_in_query_with_parent(
            query,
            parent_aliases,
            alias_case_check,
        ),
        SetExpr::SetOperation { left, right, .. } => {
            first_duplicate_table_alias_in_set_expr_with_parent(
                left,
                parent_aliases,
                alias_case_check,
            )
            .or_else(|| {
                first_duplicate_table_alias_in_set_expr_with_parent(
                    right,
                    parent_aliases,
                    alias_case_check,
                )
            })
        }
        SetExpr::Insert(statement)
        | SetExpr::Update(statement)
        | SetExpr::Delete(statement)
        | SetExpr::Merge(statement) => {
            first_duplicate_table_alias_in_statement(statement, alias_case_check)
        }
        _ => None,
    }
}

fn first_duplicate_table_alias_in_select_with_parent(
    select: &Select,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    let mut aliases = Vec::new();
    for table_with_joins in &select.from {
        collect_scope_table_aliases(table_with_joins, &mut aliases);
    }

    let mut aliases_with_parent = parent_aliases.to_vec();
    aliases_with_parent.extend(aliases);

    if let Some(duplicate) = first_duplicate_alias(&aliases_with_parent, alias_case_check) {
        return Some(duplicate);
    }

    if let Some(duplicate) = first_duplicate_table_alias_in_select_expression_subqueries(
        select,
        &aliases_with_parent,
        alias_case_check,
    ) {
        return Some(duplicate);
    }

    for table_with_joins in &select.from {
        if let Some(duplicate) = first_duplicate_table_alias_in_table_with_joins_children(
            table_with_joins,
            &aliases_with_parent,
            alias_case_check,
        ) {
            return Some(duplicate);
        }
    }

    None
}

fn first_duplicate_table_alias_in_select_expression_subqueries(
    select: &Select,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    let mut duplicate = None;
    visit_select_expressions(select, &mut |expr| {
        if duplicate.is_none() {
            duplicate = first_duplicate_table_alias_in_expr_with_parent(
                expr,
                parent_aliases,
                alias_case_check,
            );
        }
    });
    duplicate
}

fn first_duplicate_table_alias_in_expr_with_parent(
    expr: &Expr,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    match expr {
        Expr::Subquery(query)
        | Expr::Exists {
            subquery: query, ..
        } => first_duplicate_table_alias_in_query_with_parent(
            query,
            parent_aliases,
            alias_case_check,
        ),
        Expr::InSubquery {
            expr: inner,
            subquery,
            ..
        } => {
            first_duplicate_table_alias_in_expr_with_parent(inner, parent_aliases, alias_case_check)
                .or_else(|| {
                    first_duplicate_table_alias_in_query_with_parent(
                        subquery,
                        parent_aliases,
                        alias_case_check,
                    )
                })
        }
        Expr::BinaryOp { left, right, .. }
        | Expr::AnyOp { left, right, .. }
        | Expr::AllOp { left, right, .. } => {
            first_duplicate_table_alias_in_expr_with_parent(left, parent_aliases, alias_case_check)
                .or_else(|| {
                    first_duplicate_table_alias_in_expr_with_parent(
                        right,
                        parent_aliases,
                        alias_case_check,
                    )
                })
        }
        Expr::UnaryOp { expr: inner, .. }
        | Expr::Nested(inner)
        | Expr::IsNull(inner)
        | Expr::IsNotNull(inner)
        | Expr::Cast { expr: inner, .. } => {
            first_duplicate_table_alias_in_expr_with_parent(inner, parent_aliases, alias_case_check)
        }
        Expr::InList { expr, list, .. } => {
            first_duplicate_table_alias_in_expr_with_parent(expr, parent_aliases, alias_case_check)
                .or_else(|| {
                    for item in list {
                        if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                            item,
                            parent_aliases,
                            alias_case_check,
                        ) {
                            return Some(duplicate);
                        }
                    }
                    None
                })
        }
        Expr::Between {
            expr, low, high, ..
        } => {
            first_duplicate_table_alias_in_expr_with_parent(expr, parent_aliases, alias_case_check)
                .or_else(|| {
                    first_duplicate_table_alias_in_expr_with_parent(
                        low,
                        parent_aliases,
                        alias_case_check,
                    )
                })
                .or_else(|| {
                    first_duplicate_table_alias_in_expr_with_parent(
                        high,
                        parent_aliases,
                        alias_case_check,
                    )
                })
        }
        Expr::Case {
            operand,
            conditions,
            else_result,
            ..
        } => {
            if let Some(operand) = operand {
                if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                    operand,
                    parent_aliases,
                    alias_case_check,
                ) {
                    return Some(duplicate);
                }
            }
            for when in conditions {
                if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                    &when.condition,
                    parent_aliases,
                    alias_case_check,
                ) {
                    return Some(duplicate);
                }
                if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                    &when.result,
                    parent_aliases,
                    alias_case_check,
                ) {
                    return Some(duplicate);
                }
            }
            if let Some(otherwise) = else_result {
                return first_duplicate_table_alias_in_expr_with_parent(
                    otherwise,
                    parent_aliases,
                    alias_case_check,
                );
            }
            None
        }
        Expr::Function(function) => {
            if let FunctionArguments::List(arguments) = &function.args {
                for arg in &arguments.args {
                    match arg {
                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr))
                        | FunctionArg::Named {
                            arg: FunctionArgExpr::Expr(expr),
                            ..
                        } => {
                            if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                                expr,
                                parent_aliases,
                                alias_case_check,
                            ) {
                                return Some(duplicate);
                            }
                        }
                        _ => {}
                    }
                }
            }

            if let Some(filter) = &function.filter {
                if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                    filter,
                    parent_aliases,
                    alias_case_check,
                ) {
                    return Some(duplicate);
                }
            }

            for order_expr in &function.within_group {
                if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                    &order_expr.expr,
                    parent_aliases,
                    alias_case_check,
                ) {
                    return Some(duplicate);
                }
            }

            if let Some(WindowType::WindowSpec(spec)) = &function.over {
                for expr in &spec.partition_by {
                    if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                        expr,
                        parent_aliases,
                        alias_case_check,
                    ) {
                        return Some(duplicate);
                    }
                }
                for order_expr in &spec.order_by {
                    if let Some(duplicate) = first_duplicate_table_alias_in_expr_with_parent(
                        &order_expr.expr,
                        parent_aliases,
                        alias_case_check,
                    ) {
                        return Some(duplicate);
                    }
                }
            }

            None
        }
        _ => None,
    }
}

fn collect_scope_table_aliases(table_with_joins: &TableWithJoins, aliases: &mut Vec<AliasRef>) {
    collect_scope_table_aliases_from_factor(&table_with_joins.relation, aliases);
    for join in &table_with_joins.joins {
        collect_scope_table_aliases_from_factor(&join.relation, aliases);
    }
}

fn collect_scope_table_aliases_from_factor(
    table_factor: &TableFactor,
    aliases: &mut Vec<AliasRef>,
) {
    if let Some(alias) = inferred_alias_name(table_factor) {
        aliases.push(alias);
    }

    match table_factor {
        TableFactor::NestedJoin {
            table_with_joins, ..
        } => collect_scope_table_aliases(table_with_joins, aliases),
        TableFactor::Pivot { table, .. }
        | TableFactor::Unpivot { table, .. }
        | TableFactor::MatchRecognize { table, .. } => {
            collect_scope_table_aliases_from_factor(table, aliases)
        }
        _ => {}
    }
}

fn inferred_alias_name(table_factor: &TableFactor) -> Option<AliasRef> {
    if let Some(alias) = explicit_alias_name(table_factor) {
        return Some(alias);
    }

    match table_factor {
        TableFactor::Table { name, .. } => name.0.last().map(|part| {
            if let Some(ident) = part.as_ident() {
                AliasRef {
                    name: ident.value.clone(),
                    quoted: ident.quote_style.is_some(),
                }
            } else {
                AliasRef {
                    name: part.to_string(),
                    quoted: false,
                }
            }
        }),
        _ => None,
    }
}

fn explicit_alias_name(table_factor: &TableFactor) -> Option<AliasRef> {
    let alias = match table_factor {
        TableFactor::Table { alias, .. }
        | TableFactor::Derived { alias, .. }
        | TableFactor::TableFunction { alias, .. }
        | TableFactor::Function { alias, .. }
        | TableFactor::UNNEST { alias, .. }
        | TableFactor::JsonTable { alias, .. }
        | TableFactor::OpenJsonTable { alias, .. }
        | TableFactor::NestedJoin { alias, .. }
        | TableFactor::Pivot { alias, .. }
        | TableFactor::Unpivot { alias, .. }
        | TableFactor::MatchRecognize { alias, .. }
        | TableFactor::XmlTable { alias, .. }
        | TableFactor::SemanticView { alias, .. } => alias.as_ref(),
    }?;

    Some(AliasRef {
        name: alias.name.value.clone(),
        quoted: alias.name.quote_style.is_some(),
    })
}

fn first_duplicate_table_alias_in_table_with_joins_children(
    table_with_joins: &TableWithJoins,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    first_duplicate_table_alias_in_table_factor_children(
        &table_with_joins.relation,
        parent_aliases,
        alias_case_check,
    )
    .or_else(|| {
        for join in &table_with_joins.joins {
            if let Some(duplicate) = first_duplicate_table_alias_in_table_factor_children(
                &join.relation,
                parent_aliases,
                alias_case_check,
            ) {
                return Some(duplicate);
            }
        }
        None
    })
}

fn child_parent_aliases(
    parent_aliases: &[AliasRef],
    table_factor: &TableFactor,
    alias_case_check: AliasCaseCheck,
) -> Vec<AliasRef> {
    let mut next = parent_aliases.to_vec();
    if let Some(alias) = inferred_alias_name(table_factor) {
        if let Some(index) = next
            .iter()
            .position(|existing| aliases_match(existing, &alias, alias_case_check))
        {
            next.remove(index);
        }
    }
    next
}

fn first_duplicate_table_alias_in_table_factor_children(
    table_factor: &TableFactor,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    let child_parent_aliases = child_parent_aliases(parent_aliases, table_factor, alias_case_check);

    match table_factor {
        TableFactor::Derived { subquery, .. } => first_duplicate_table_alias_in_query_with_parent(
            subquery,
            &child_parent_aliases,
            alias_case_check,
        ),
        TableFactor::NestedJoin {
            table_with_joins, ..
        } => first_duplicate_table_alias_in_nested_scope(
            table_with_joins,
            &child_parent_aliases,
            alias_case_check,
        ),
        TableFactor::Pivot { table, .. }
        | TableFactor::Unpivot { table, .. }
        | TableFactor::MatchRecognize { table, .. } => {
            first_duplicate_table_alias_in_table_factor_children(
                table,
                &child_parent_aliases,
                alias_case_check,
            )
        }
        _ => None,
    }
}

fn first_duplicate_table_alias_in_nested_scope(
    table_with_joins: &TableWithJoins,
    parent_aliases: &[AliasRef],
    alias_case_check: AliasCaseCheck,
) -> Option<String> {
    let mut aliases = Vec::new();
    collect_scope_table_aliases(table_with_joins, &mut aliases);
    let mut aliases_with_parent = parent_aliases.to_vec();
    aliases_with_parent.extend(aliases);

    if let Some(duplicate) = first_duplicate_alias(&aliases_with_parent, alias_case_check) {
        return Some(duplicate);
    }

    first_duplicate_table_alias_in_table_with_joins_children(
        table_with_joins,
        &aliases_with_parent,
        alias_case_check,
    )
}

fn first_duplicate_alias(values: &[AliasRef], alias_case_check: AliasCaseCheck) -> Option<String> {
    let mut seen_case_insensitive = HashSet::new();
    let mut seen: Vec<&AliasRef> = Vec::new();

    for value in values {
        if matches!(alias_case_check, AliasCaseCheck::CaseInsensitive) {
            let key = value.name.to_ascii_uppercase();
            if !seen_case_insensitive.insert(key) {
                return Some(value.name.clone());
            }
            continue;
        }

        let is_duplicate = seen
            .iter()
            .any(|existing| aliases_match(existing, value, alias_case_check));
        if is_duplicate {
            return Some(value.name.clone());
        }
        seen.push(value);
    }

    None
}

fn aliases_match(left: &AliasRef, right: &AliasRef, alias_case_check: AliasCaseCheck) -> bool {
    match alias_case_check {
        AliasCaseCheck::CaseInsensitive => left.name.eq_ignore_ascii_case(&right.name),
        AliasCaseCheck::CaseSensitive => left.name == right.name,
        AliasCaseCheck::Dialect => {
            if left.quoted || right.quoted {
                left.name == right.name
            } else {
                left.name.eq_ignore_ascii_case(&right.name)
            }
        }
        AliasCaseCheck::QuotedCsNakedUpper | AliasCaseCheck::QuotedCsNakedLower => {
            normalize_alias_for_mode(left, alias_case_check)
                == normalize_alias_for_mode(right, alias_case_check)
        }
    }
}

fn normalize_alias_for_mode(alias: &AliasRef, mode: AliasCaseCheck) -> String {
    match mode {
        AliasCaseCheck::QuotedCsNakedUpper => {
            if alias.quoted {
                alias.name.clone()
            } else {
                alias.name.to_ascii_uppercase()
            }
        }
        AliasCaseCheck::QuotedCsNakedLower => {
            if alias.quoted {
                alias.name.clone()
            } else {
                alias.name.to_ascii_lowercase()
            }
        }
        _ => alias.name.clone(),
    }
}

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

    fn run(sql: &str) -> Vec<Issue> {
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::default();
        statements
            .iter()
            .enumerate()
            .flat_map(|(index, statement)| {
                rule.check(
                    statement,
                    &LintContext {
                        sql,
                        statement_range: 0..sql.len(),
                        statement_index: index,
                    },
                )
            })
            .collect()
    }

    #[test]
    fn flags_duplicate_alias_in_same_scope() {
        let issues = run("select * from users u join orders u on u.id = u.user_id");
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn allows_unique_aliases() {
        let issues = run("select * from users u join orders o on u.id = o.user_id");
        assert!(issues.is_empty());
    }

    #[test]
    fn allows_same_alias_in_separate_cte_scopes() {
        let sql = "with a as (select * from users u), b as (select * from orders u) select * from a join b on a.id = b.id";
        let issues = run(sql);
        assert!(issues.is_empty());
    }

    #[test]
    fn flags_duplicate_alias_in_nested_subquery() {
        let sql = "select * from (select * from users u join orders u on u.id = u.user_id) t";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
    }

    #[test]
    fn flags_duplicate_implicit_table_name_aliases() {
        let sql =
            "select * from analytics.foo join reporting.foo on analytics.foo.id = reporting.foo.id";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn flags_duplicate_alias_between_parent_and_subquery_scope() {
        let sql = "select * from (select * from users a) s join orders a on s.id = a.user_id";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn flags_duplicate_alias_between_parent_and_where_subquery_scope() {
        let sql = "select * from tbl as t where t.val in (select t.val from tbl2 as t)";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn flags_implicit_table_name_alias_collision_in_where_subquery() {
        let sql = "select * from tbl where val in (select tbl.val from tbl2 as tbl)";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn flags_implicit_table_name_collision_in_where_subquery() {
        let sql = "select * from tbl where val in (select tbl.val from tbl)";
        let issues = run(sql);
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn does_not_treat_subquery_alias_as_parent_alias_conflict() {
        let sql = "select * from (select * from users s) s";
        let issues = run(sql);
        assert!(issues.is_empty());
    }

    #[test]
    fn default_dialect_mode_does_not_flag_quoted_case_mismatch() {
        let sql = "select * from users \"A\" join orders a on \"A\".id = a.user_id";
        let issues = run(sql);
        assert!(issues.is_empty());
    }

    #[test]
    fn alias_case_check_case_sensitive_allows_case_mismatch() {
        let sql = "select * from users a join orders A on a.id = A.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "aliasing.unique.table".to_string(),
                serde_json::json!({"alias_case_check": "case_sensitive"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert!(issues.is_empty());
    }

    #[test]
    fn alias_case_check_case_sensitive_flags_exact_duplicates() {
        let sql = "select * from users a join orders a on a.id = a.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "LINT_AL_004".to_string(),
                serde_json::json!({"alias_case_check": "case_sensitive"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn alias_case_check_quoted_cs_naked_upper_flags_upper_fold_match() {
        let sql = "select * from users \"FOO\" join orders foo on \"FOO\".id = foo.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "aliasing.unique.table".to_string(),
                serde_json::json!({"alias_case_check": "quoted_cs_naked_upper"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn alias_case_check_quoted_cs_naked_upper_allows_nonmatching_quoted_case() {
        let sql = "select * from users \"foo\" join orders foo on \"foo\".id = foo.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "aliasing.unique.table".to_string(),
                serde_json::json!({"alias_case_check": "quoted_cs_naked_upper"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert!(issues.is_empty());
    }

    #[test]
    fn alias_case_check_quoted_cs_naked_lower_flags_lower_fold_match() {
        let sql = "select * from users \"foo\" join orders FOO on \"foo\".id = FOO.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "aliasing.unique.table".to_string(),
                serde_json::json!({"alias_case_check": "quoted_cs_naked_lower"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert_eq!(issues.len(), 1);
        assert_eq!(issues[0].code, issue_codes::LINT_AL_004);
    }

    #[test]
    fn alias_case_check_quoted_cs_naked_lower_allows_nonmatching_quoted_case() {
        let sql = "select * from users \"FOO\" join orders FOO on \"FOO\".id = FOO.user_id";
        let statements = parse_sql(sql).expect("parse");
        let rule = AliasingUniqueTable::from_config(&LintConfig {
            enabled: true,
            disabled_rules: vec![],
            rule_configs: std::collections::BTreeMap::from([(
                "aliasing.unique.table".to_string(),
                serde_json::json!({"alias_case_check": "quoted_cs_naked_lower"}),
            )]),
        });
        let issues = rule.check(
            &statements[0],
            &LintContext {
                sql,
                statement_range: 0..sql.len(),
                statement_index: 0,
            },
        );
        assert!(issues.is_empty());
    }
}
