use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;

/// Implementation of the `sql!()` proc macro.
///
/// Pipeline:
///  1. Preprocess: extract `{expr}` → replace with `$N` placeholders, `c.field` → column name
///  2. Parse with sqlparser-rs to validate SQL expression syntax
///  3. Generate code that creates a `Condition::Raw` or `SqlFragment`
pub fn sql_macro_impl(input: TokenStream) -> TokenStream {
    let input2: TokenStream2 = input.into();
    let input_str = input2.to_string();

    // Phase 1: Preprocess - extract {expr} placeholders and c.field references
    let (sql_template, rust_exprs, column_refs) = preprocess(&input_str);

    // Phase 2: Validate with sqlparser-rs
    if let Err(err) = validate_sql_expr(&sql_template) {
        let msg = format!("sql!() syntax error: {}", err);
        return quote! { compile_error!(#msg) }.into();
    }

    // Phase 3: Generate code
    // Replace $N back to ? for runtime
    let runtime_sql = sql_template_to_runtime(&sql_template);

    let param_exprs: Vec<TokenStream2> = rust_exprs
        .iter()
        .map(|expr| {
            let expr_ts: TokenStream2 = expr.parse().unwrap_or_else(|_| {
                quote! { compile_error!("Invalid expression in sql!()") }
            });
            quote! { litedbmodel::values::Value::from(#expr_ts) }
        })
        .collect();

    let expanded = if param_exprs.is_empty() && column_refs.is_empty() {
        // No params, no column refs - pure SQL fragment
        quote! {
            litedbmodel::condition::Condition::Raw {
                sql: #runtime_sql.to_string(),
                params: vec![],
            }
        }
    } else {
        quote! {
            litedbmodel::condition::Condition::Raw {
                sql: #runtime_sql.to_string(),
                params: vec![#(#param_exprs),*],
            }
        }
    };

    expanded.into()
}

/// Preprocess the macro input:
/// - Extract `{expr}` → replace with `$N`, collect Rust expressions
/// - Recognize `ident . ident` patterns (proc-macro inserts spaces) → replace with just field name
fn preprocess(input: &str) -> (String, Vec<String>, Vec<String>) {
    let mut sql = String::new();
    let mut exprs = Vec::new();
    let mut column_refs = Vec::new();
    let mut param_idx = 0u32;

    // Tokenize into words while preserving operators and braces
    let tokens = tokenize(input);
    let mut i = 0;

    while i < tokens.len() {
        let tok = &tokens[i];

        if tok == "{" {
            // Extract Rust expression inside braces
            let mut depth = 1;
            let mut expr_parts = Vec::new();
            i += 1;
            while i < tokens.len() && depth > 0 {
                if tokens[i] == "{" {
                    depth += 1;
                } else if tokens[i] == "}" {
                    depth -= 1;
                    if depth == 0 {
                        break;
                    }
                }
                expr_parts.push(tokens[i].as_str());
                i += 1;
            }
            i += 1; // skip closing '}'

            let expr = expr_parts.join(" ").trim().to_string();
            param_idx += 1;
            if !sql.is_empty() && !sql.ends_with(' ') {
                sql.push(' ');
            }
            sql.push_str(&format!("${}", param_idx));
            exprs.push(expr);
        } else if is_identifier(tok)
            && i + 2 < tokens.len()
            && tokens[i + 1] == "."
            && is_identifier(&tokens[i + 2])
        {
            // `ident . field` → extract just field name
            if !sql.is_empty() && !sql.ends_with(' ') {
                sql.push(' ');
            }
            let field_name = tokens[i + 2].clone();
            column_refs.push(field_name.clone());
            sql.push_str(&field_name);
            i += 3;
        } else {
            if !sql.is_empty() && !sql.ends_with(' ') && tok != "," && tok != ")" && tok != "." {
                sql.push(' ');
            }
            sql.push_str(tok);
            i += 1;
        }
    }

    (sql, exprs, column_refs)
}

fn is_identifier(s: &str) -> bool {
    let mut chars = s.chars();
    match chars.next() {
        Some(c) if c.is_alphabetic() || c == '_' => {}
        _ => return false,
    }
    chars.all(|c| c.is_alphanumeric() || c == '_')
}

/// Split input into tokens: identifiers, operators, braces, etc.
fn tokenize(input: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let chars: Vec<char> = input.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        if chars[i].is_whitespace() {
            i += 1;
            continue;
        }

        // Braces
        if chars[i] == '{' || chars[i] == '}' {
            tokens.push(chars[i].to_string());
            i += 1;
            continue;
        }

        // String literals inside braces (quoted)
        if chars[i] == '"' {
            let mut s = String::new();
            s.push(chars[i]);
            i += 1;
            while i < chars.len() && chars[i] != '"' {
                if chars[i] == '\\' && i + 1 < chars.len() {
                    s.push(chars[i]);
                    i += 1;
                    s.push(chars[i]);
                    i += 1;
                } else {
                    s.push(chars[i]);
                    i += 1;
                }
            }
            if i < chars.len() {
                s.push(chars[i]);
                i += 1;
            }
            tokens.push(s);
            continue;
        }

        // Identifiers
        if chars[i].is_alphabetic() || chars[i] == '_' {
            let start = i;
            while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
                i += 1;
            }
            tokens.push(chars[start..i].iter().collect());
            continue;
        }

        // Numbers
        if chars[i].is_ascii_digit() {
            let start = i;
            while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
                i += 1;
            }
            tokens.push(chars[start..i].iter().collect());
            continue;
        }

        // Multi-char operators
        if i + 1 < chars.len() {
            let two: String = chars[i..i + 2].iter().collect();
            if matches!(two.as_str(), ">=" | "<=" | "!=" | "<>") {
                tokens.push(two);
                i += 2;
                continue;
            }
        }

        // Single char operators / punctuation
        tokens.push(chars[i].to_string());
        i += 1;
    }

    tokens
}

/// Validate SQL expression using sqlparser-rs.
fn validate_sql_expr(sql_template: &str) -> std::result::Result<(), String> {
    use sqlparser::dialect::GenericDialect;
    use sqlparser::parser::Parser;

    let dialect = GenericDialect {};

    // Wrap in SELECT WHERE to make it a complete statement for the parser
    let wrapped = format!("SELECT 1 WHERE {}", sql_template);

    match Parser::parse_sql(&dialect, &wrapped) {
        Ok(_) => Ok(()),
        Err(e) => {
            // Try parsing as a standalone expression (for fragments like COUNT(*))
            let wrapped_expr = format!("SELECT {}", sql_template);
            match Parser::parse_sql(&dialect, &wrapped_expr) {
                Ok(_) => Ok(()),
                Err(_) => Err(format!("{}", e)),
            }
        }
    }
}

/// Convert `$N` placeholders in the template back to `?` for runtime.
fn sql_template_to_runtime(template: &str) -> String {
    let mut result = String::with_capacity(template.len());
    let chars: Vec<char> = template.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        if chars[i] == '$' && i + 1 < chars.len() && chars[i + 1].is_ascii_digit() {
            result.push('?');
            i += 1;
            while i < chars.len() && chars[i].is_ascii_digit() {
                i += 1;
            }
        } else {
            result.push(chars[i]);
            i += 1;
        }
    }

    result
}

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

    #[test]
    fn test_preprocess_simple() {
        let (sql, exprs, _) = preprocess("c . age > { 18 }");
        assert_eq!(sql.trim(), "age > $1");
        assert_eq!(exprs, vec!["18"]);
    }

    #[test]
    fn test_preprocess_like() {
        let (sql, exprs, _) = preprocess("c . name LIKE { \"%test%\" }");
        assert_eq!(sql.trim(), "name LIKE $1");
        assert_eq!(exprs, vec!["\"%test%\""]);
    }

    #[test]
    fn test_preprocess_between() {
        let (sql, exprs, _) = preprocess("c . score BETWEEN { 60 } AND { 100 }");
        assert_eq!(sql.trim(), "score BETWEEN $1 AND $2");
        assert_eq!(exprs, vec!["60", "100"]);
    }

    #[test]
    fn test_preprocess_is_null() {
        let (sql, exprs, _) = preprocess("c . deleted_at IS NULL");
        assert_eq!(sql.trim(), "deleted_at IS NULL");
        assert!(exprs.is_empty());
    }

    #[test]
    fn test_validate_sql_expr() {
        assert!(validate_sql_expr("age > $1").is_ok());
        assert!(validate_sql_expr("name LIKE $1").is_ok());
        assert!(validate_sql_expr("score BETWEEN $1 AND $2").is_ok());
        assert!(validate_sql_expr("deleted_at IS NULL").is_ok());
        assert!(validate_sql_expr("age > $1 AND is_active = $2").is_ok());
    }

    #[test]
    fn test_sql_template_to_runtime() {
        assert_eq!(sql_template_to_runtime("age > $1"), "age > ?");
        assert_eq!(
            sql_template_to_runtime("score BETWEEN $1 AND $2"),
            "score BETWEEN ? AND ?"
        );
        assert_eq!(
            sql_template_to_runtime("deleted_at IS NULL"),
            "deleted_at IS NULL"
        );
    }
}
