//! SQL parser using sqlparser-rs.

use super::types::{SqlError, SqlType, SqlValue};
use sqlparser::ast::{
    BinaryOperator, ColumnOption, DataType, Expr, Query, Select, SelectItem, SetExpr, Statement,
    TableFactor, Value as AstValue,
};
use sqlparser::dialect::SQLiteDialect;
use sqlparser::parser::Parser;

/// Parsed SQL statement.
#[derive(Debug)]
pub enum ParsedStatement {
    /// CREATE TABLE statement.
    CreateTable {
        name: String,
        columns: Vec<ParsedColumn>,
        if_not_exists: bool,
    },
    /// DROP TABLE statement.
    DropTable {
        name: String,
        if_exists: bool,
    },
    /// SELECT statement.
    Select(ParsedSelect),
    /// INSERT statement.
    Insert {
        table: String,
        columns: Vec<String>,
        values: Vec<Vec<SqlValue>>,
    },
    /// UPDATE statement.
    Update {
        table: String,
        assignments: Vec<(String, SqlValue)>,
        where_clause: Option<ParsedExpr>,
    },
    /// DELETE statement.
    Delete {
        table: String,
        where_clause: Option<ParsedExpr>,
    },
    /// CREATE INDEX statement.
    CreateIndex {
        name: String,
        table: String,
        columns: Vec<String>,
        unique: bool,
    },
    /// DROP INDEX statement.
    DropIndex { name: String },
}

/// Parsed column definition.
#[derive(Debug)]
pub struct ParsedColumn {
    pub name: String,
    pub data_type: SqlType,
    pub nullable: bool,
    pub default: Option<SqlValue>,
    pub primary_key: bool,
    pub unique: bool,
}

/// Parsed SELECT statement.
#[derive(Debug)]
pub struct ParsedSelect {
    pub columns: Vec<ParsedSelectItem>,
    pub from: String,
    pub joins: Vec<ParsedJoin>,
    pub where_clause: Option<ParsedExpr>,
    pub group_by: Vec<String>,
    pub having: Option<ParsedExpr>,
    pub order_by: Vec<ParsedOrderBy>,
    pub limit: Option<usize>,
    pub offset: Option<usize>,
}

/// Parsed select item.
#[derive(Debug)]
pub enum ParsedSelectItem {
    /// All columns (*).
    Wildcard,
    /// Single column.
    Column(String),
    /// Column with alias.
    ColumnAlias { column: String, alias: String },
    /// Aggregate function.
    Aggregate {
        function: String,
        column: Option<String>,
        alias: Option<String>,
    },
}

/// Parsed JOIN clause.
#[derive(Debug)]
pub struct ParsedJoin {
    pub table: String,
    pub join_type: JoinType,
    pub on: ParsedExpr,
}

/// Join types.
#[derive(Debug, Clone)]
pub enum JoinType {
    Inner,
    Left,
    Right,
    Full,
}

/// Parsed ORDER BY clause.
#[derive(Debug)]
pub struct ParsedOrderBy {
    pub column: String,
    pub ascending: bool,
}

/// Parsed expression.
#[derive(Debug, Clone)]
pub enum ParsedExpr {
    /// Column reference.
    Column(String),
    /// Literal value.
    Literal(SqlValue),
    /// Binary operation.
    BinaryOp {
        left: Box<ParsedExpr>,
        op: BinaryOp,
        right: Box<ParsedExpr>,
    },
    /// Unary NOT.
    Not(Box<ParsedExpr>),
    /// IS NULL check.
    IsNull(Box<ParsedExpr>),
    /// IS NOT NULL check.
    IsNotNull(Box<ParsedExpr>),
    /// IN list.
    InList {
        expr: Box<ParsedExpr>,
        list: Vec<ParsedExpr>,
        negated: bool,
    },
    /// BETWEEN.
    Between {
        expr: Box<ParsedExpr>,
        low: Box<ParsedExpr>,
        high: Box<ParsedExpr>,
        negated: bool,
    },
    /// Function call.
    Function { name: String, args: Vec<ParsedExpr> },
}

/// Binary operators.
#[derive(Debug, Clone)]
pub enum BinaryOp {
    Eq,
    Ne,
    Lt,
    Le,
    Gt,
    Ge,
    And,
    Or,
    Like,
    Plus,
    Minus,
    Multiply,
    Divide,
}

/// SQL parser.
pub struct SqlParser;

impl SqlParser {
    /// Parses a SQL statement.
    pub fn parse(sql: &str) -> Result<ParsedStatement, SqlError> {
        let dialect = SQLiteDialect {};
        let statements = Parser::parse_sql(&dialect, sql)
            .map_err(|e| SqlError::Parse(e.to_string()))?;

        if statements.is_empty() {
            return Err(SqlError::Parse("Empty SQL statement".to_string()));
        }

        if statements.len() > 1 {
            return Err(SqlError::Parse("Multiple statements not supported".to_string()));
        }

        Self::convert_statement(&statements[0])
    }

    fn convert_statement(stmt: &Statement) -> Result<ParsedStatement, SqlError> {
        match stmt {
            Statement::CreateTable { name, columns, if_not_exists, constraints, .. } => {
                Self::convert_create_table(name, columns, constraints, *if_not_exists)
            }
            Statement::Drop { object_type, names, if_exists, .. } => {
                Self::convert_drop(object_type, names, *if_exists)
            }
            Statement::Query(query) => Self::convert_query(query),
            Statement::Insert { table_name, columns, source, .. } => {
                Self::convert_insert(table_name, columns, source)
            }
            Statement::Update { table, assignments, selection, .. } => {
                Self::convert_update(table, assignments, selection)
            }
            Statement::Delete { from, selection, .. } => {
                Self::convert_delete(from, selection)
            }
            Statement::CreateIndex { name, table_name, columns, unique, .. } => {
                Self::convert_create_index(name, table_name, columns, *unique)
            }
            _ => Err(SqlError::Unsupported(format!("Statement not supported"))),
        }
    }

    fn convert_create_table(
        name: &sqlparser::ast::ObjectName,
        columns: &[sqlparser::ast::ColumnDef],
        constraints: &[sqlparser::ast::TableConstraint],
        if_not_exists: bool,
    ) -> Result<ParsedStatement, SqlError> {
        let table_name = name.to_string();
        let mut parsed_columns = Vec::new();
        let mut primary_keys: Vec<String> = Vec::new();

        // Extract primary keys from table constraints
        for constraint in constraints {
            if let sqlparser::ast::TableConstraint::Unique { columns: pk_cols, is_primary: true, .. } = constraint {
                for col in pk_cols {
                    primary_keys.push(col.value.clone());
                }
            }
        }

        for col in columns {
            let col_name = col.name.value.clone();
            let data_type = Self::convert_data_type(&col.data_type)?;

            let mut nullable = true;
            let mut default = None;
            let mut primary_key = primary_keys.contains(&col_name);
            let mut unique = false;

            for option in &col.options {
                match &option.option {
                    ColumnOption::Null => nullable = true,
                    ColumnOption::NotNull => nullable = false,
                    ColumnOption::Default(expr) => {
                        default = Some(Self::convert_value_expr(expr)?);
                    }
                    ColumnOption::Unique { is_primary, .. } => {
                        if *is_primary {
                            primary_key = true;
                        } else {
                            unique = true;
                        }
                    }
                    _ => {}
                }
            }

            if primary_key {
                nullable = false;
                unique = true;
            }

            parsed_columns.push(ParsedColumn {
                name: col_name,
                data_type,
                nullable,
                default,
                primary_key,
                unique,
            });
        }

        Ok(ParsedStatement::CreateTable {
            name: table_name,
            columns: parsed_columns,
            if_not_exists,
        })
    }

    fn convert_data_type(dt: &DataType) -> Result<SqlType, SqlError> {
        match dt {
            DataType::Int(_)
            | DataType::Integer(_)
            | DataType::BigInt(_)
            | DataType::SmallInt(_)
            | DataType::TinyInt(_) => Ok(SqlType::Integer),
            DataType::Real | DataType::Float(_) | DataType::Double | DataType::DoublePrecision => {
                Ok(SqlType::Real)
            }
            DataType::Varchar(_)
            | DataType::Char(_)
            | DataType::Text
            | DataType::String(_) => Ok(SqlType::Text),
            DataType::Binary(_) | DataType::Varbinary(_) | DataType::Blob(_) => Ok(SqlType::Blob),
            DataType::Boolean => Ok(SqlType::Boolean),
            DataType::Timestamp(_, _) | DataType::Date | DataType::Datetime(_) => {
                Ok(SqlType::Timestamp)
            }
            _ => Err(SqlError::Unsupported(format!("Data type: {:?}", dt))),
        }
    }

    fn convert_drop(
        object_type: &sqlparser::ast::ObjectType,
        names: &[sqlparser::ast::ObjectName],
        if_exists: bool,
    ) -> Result<ParsedStatement, SqlError> {
        let name = names
            .first()
            .map(|n| n.to_string())
            .ok_or_else(|| SqlError::Parse("Missing object name".to_string()))?;

        match object_type {
            sqlparser::ast::ObjectType::Table => Ok(ParsedStatement::DropTable { name, if_exists }),
            sqlparser::ast::ObjectType::Index => Ok(ParsedStatement::DropIndex { name }),
            _ => Err(SqlError::Unsupported(format!("DROP not supported"))),
        }
    }

    fn convert_query(query: &Query) -> Result<ParsedStatement, SqlError> {
        let select = match &*query.body {
            SetExpr::Select(select) => select,
            _ => return Err(SqlError::Unsupported("Non-SELECT query body".to_string())),
        };

        let parsed_select = Self::convert_select(select, query)?;
        Ok(ParsedStatement::Select(parsed_select))
    }

    fn convert_select(select: &Select, query: &Query) -> Result<ParsedSelect, SqlError> {
        // Parse columns
        let columns = select
            .projection
            .iter()
            .map(Self::convert_select_item)
            .collect::<Result<Vec<_>, _>>()?;

        // Parse FROM
        let from = select
            .from
            .first()
            .map(|f| Self::convert_table_factor(&f.relation))
            .transpose()?
            .unwrap_or_default();

        // Parse WHERE
        let where_clause = select
            .selection
            .as_ref()
            .map(Self::convert_expr)
            .transpose()?;

        // Parse ORDER BY - simplified approach
        let order_by: Vec<ParsedOrderBy> = query
            .order_by
            .iter()
            .filter_map(|e| Self::convert_order_by(e).ok())
            .collect();

        // Parse LIMIT/OFFSET
        let limit = query
            .limit
            .as_ref()
            .and_then(|l| Self::expr_to_usize(l));
        let offset = query
            .offset
            .as_ref()
            .and_then(|o| Self::expr_to_usize(&o.value));

        Ok(ParsedSelect {
            columns,
            from,
            joins: Vec::new(),
            where_clause,
            group_by: Vec::new(),
            having: None,
            order_by,
            limit,
            offset,
        })
    }

    fn convert_select_item(item: &SelectItem) -> Result<ParsedSelectItem, SqlError> {
        match item {
            SelectItem::Wildcard(_) => Ok(ParsedSelectItem::Wildcard),
            SelectItem::UnnamedExpr(expr) => Self::convert_select_expr(expr),
            SelectItem::ExprWithAlias { expr, alias } => {
                if let Expr::Identifier(id) = expr {
                    Ok(ParsedSelectItem::ColumnAlias {
                        column: id.value.clone(),
                        alias: alias.value.clone(),
                    })
                } else {
                    Self::convert_select_expr(expr)
                }
            }
            _ => Err(SqlError::Unsupported("Select item not supported".to_string())),
        }
    }

    fn convert_select_expr(expr: &Expr) -> Result<ParsedSelectItem, SqlError> {
        match expr {
            Expr::Identifier(id) => Ok(ParsedSelectItem::Column(id.value.clone())),
            Expr::CompoundIdentifier(ids) => {
                Ok(ParsedSelectItem::Column(ids.last().map(|i| i.value.clone()).unwrap_or_default()))
            }
            Expr::Function(func) => {
                let name = func.name.to_string().to_uppercase();
                // Try to extract column from first arg - simplified for compatibility
                let column = Self::extract_func_column_arg(func);
                Ok(ParsedSelectItem::Aggregate {
                    function: name,
                    column,
                    alias: None,
                })
            }
            _ => Err(SqlError::Unsupported("Select expression not supported".to_string())),
        }
    }

    fn convert_table_factor(factor: &TableFactor) -> Result<String, SqlError> {
        match factor {
            TableFactor::Table { name, .. } => Ok(name.to_string()),
            _ => Err(SqlError::Unsupported("Table factor not supported".to_string())),
        }
    }

    fn extract_func_column_arg(func: &sqlparser::ast::Function) -> Option<String> {
        // Use string representation and parse it - works across sqlparser versions
        let func_str = func.to_string();
        // Parse function like "SUM(age)" or "COUNT(*)"
        if let Some(start) = func_str.find('(') {
            if let Some(end) = func_str.rfind(')') {
                let arg = func_str[start + 1..end].trim();
                if !arg.is_empty() && arg != "*" {
                    return Some(arg.to_string());
                }
            }
        }
        None
    }

    fn convert_order_by(ob: &sqlparser::ast::OrderByExpr) -> Result<ParsedOrderBy, SqlError> {
        let column = match &ob.expr {
            Expr::Identifier(id) => id.value.clone(),
            _ => return Err(SqlError::Unsupported("Order by expression".to_string())),
        };
        let ascending = ob.asc.unwrap_or(true);
        Ok(ParsedOrderBy { column, ascending })
    }

    fn convert_expr(expr: &Expr) -> Result<ParsedExpr, SqlError> {
        match expr {
            Expr::Identifier(id) => Ok(ParsedExpr::Column(id.value.clone())),
            Expr::CompoundIdentifier(ids) => {
                Ok(ParsedExpr::Column(ids.last().map(|i| i.value.clone()).unwrap_or_default()))
            }
            Expr::Value(v) => Ok(ParsedExpr::Literal(Self::convert_value(v)?)),
            Expr::BinaryOp { left, op, right } => {
                let left = Box::new(Self::convert_expr(left)?);
                let right = Box::new(Self::convert_expr(right)?);
                let op = Self::convert_binary_op(op)?;
                Ok(ParsedExpr::BinaryOp { left, op, right })
            }
            Expr::UnaryOp { op: sqlparser::ast::UnaryOperator::Not, expr } => {
                Ok(ParsedExpr::Not(Box::new(Self::convert_expr(expr)?)))
            }
            Expr::IsNull(expr) => Ok(ParsedExpr::IsNull(Box::new(Self::convert_expr(expr)?))),
            Expr::IsNotNull(expr) => Ok(ParsedExpr::IsNotNull(Box::new(Self::convert_expr(expr)?))),
            Expr::InList { expr, list, negated } => Ok(ParsedExpr::InList {
                expr: Box::new(Self::convert_expr(expr)?),
                list: list.iter().map(Self::convert_expr).collect::<Result<_, _>>()?,
                negated: *negated,
            }),
            Expr::Between { expr, low, high, negated } => Ok(ParsedExpr::Between {
                expr: Box::new(Self::convert_expr(expr)?),
                low: Box::new(Self::convert_expr(low)?),
                high: Box::new(Self::convert_expr(high)?),
                negated: *negated,
            }),
            Expr::Like { expr, pattern, .. } => {
                let left = Box::new(Self::convert_expr(expr)?);
                let right = Box::new(Self::convert_expr(pattern)?);
                Ok(ParsedExpr::BinaryOp { left, op: BinaryOp::Like, right })
            }
            Expr::Nested(inner) => Self::convert_expr(inner),
            _ => Err(SqlError::Unsupported("Expression not supported".to_string())),
        }
    }

    fn convert_binary_op(op: &BinaryOperator) -> Result<BinaryOp, SqlError> {
        match op {
            BinaryOperator::Eq => Ok(BinaryOp::Eq),
            BinaryOperator::NotEq => Ok(BinaryOp::Ne),
            BinaryOperator::Lt => Ok(BinaryOp::Lt),
            BinaryOperator::LtEq => Ok(BinaryOp::Le),
            BinaryOperator::Gt => Ok(BinaryOp::Gt),
            BinaryOperator::GtEq => Ok(BinaryOp::Ge),
            BinaryOperator::And => Ok(BinaryOp::And),
            BinaryOperator::Or => Ok(BinaryOp::Or),
            BinaryOperator::Plus => Ok(BinaryOp::Plus),
            BinaryOperator::Minus => Ok(BinaryOp::Minus),
            BinaryOperator::Multiply => Ok(BinaryOp::Multiply),
            BinaryOperator::Divide => Ok(BinaryOp::Divide),
            _ => Err(SqlError::Unsupported("Operator not supported".to_string())),
        }
    }

    fn convert_value(v: &AstValue) -> Result<SqlValue, SqlError> {
        match v {
            AstValue::Null => Ok(SqlValue::Null),
            AstValue::Number(n, _) => {
                if n.contains('.') {
                    n.parse::<f64>()
                        .map(SqlValue::Real)
                        .map_err(|e| SqlError::Parse(e.to_string()))
                } else {
                    n.parse::<i64>()
                        .map(SqlValue::Integer)
                        .map_err(|e| SqlError::Parse(e.to_string()))
                }
            }
            AstValue::SingleQuotedString(s) | AstValue::DoubleQuotedString(s) => {
                Ok(SqlValue::Text(s.clone()))
            }
            AstValue::Boolean(b) => Ok(SqlValue::Boolean(*b)),
            AstValue::HexStringLiteral(h) => hex::decode(h)
                .map(SqlValue::Blob)
                .map_err(|e| SqlError::Parse(e.to_string())),
            _ => Err(SqlError::Unsupported("Value not supported".to_string())),
        }
    }

    fn convert_value_expr(expr: &Expr) -> Result<SqlValue, SqlError> {
        match expr {
            Expr::Value(v) => Self::convert_value(v),
            _ => Err(SqlError::Unsupported("Non-literal default".to_string())),
        }
    }

    fn convert_insert(
        table_name: &sqlparser::ast::ObjectName,
        columns: &[sqlparser::ast::Ident],
        source: &Option<Box<Query>>,
    ) -> Result<ParsedStatement, SqlError> {
        let table = table_name.to_string();
        let col_names: Vec<String> = columns.iter().map(|c| c.value.clone()).collect();

        let values = match source.as_ref().map(|s| s.body.as_ref()) {
            Some(SetExpr::Values(vals)) => {
                let mut result = Vec::new();
                for row in &vals.rows {
                    let row_values: Vec<SqlValue> = row
                        .iter()
                        .map(Self::convert_value_expr)
                        .collect::<Result<_, _>>()?;
                    result.push(row_values);
                }
                result
            }
            _ => return Err(SqlError::Unsupported("INSERT source".to_string())),
        };

        Ok(ParsedStatement::Insert {
            table,
            columns: col_names,
            values,
        })
    }

    fn convert_update(
        table: &sqlparser::ast::TableWithJoins,
        assignments: &[sqlparser::ast::Assignment],
        selection: &Option<Expr>,
    ) -> Result<ParsedStatement, SqlError> {
        let table_name = Self::convert_table_factor(&table.relation)?;

        let parsed_assignments: Vec<(String, SqlValue)> = assignments
            .iter()
            .map(|a| {
                let col = a.id.iter().map(|i| i.value.clone()).collect::<Vec<_>>().join(".");
                let val = Self::convert_value_expr(&a.value)?;
                Ok((col, val))
            })
            .collect::<Result<_, SqlError>>()?;

        let where_clause = selection.as_ref().map(Self::convert_expr).transpose()?;

        Ok(ParsedStatement::Update {
            table: table_name,
            assignments: parsed_assignments,
            where_clause,
        })
    }

    fn convert_delete(
        from: &[sqlparser::ast::TableWithJoins],
        selection: &Option<Expr>,
    ) -> Result<ParsedStatement, SqlError> {
        let table = from
            .first()
            .map(|f| Self::convert_table_factor(&f.relation))
            .transpose()?
            .unwrap_or_default();

        let where_clause = selection.as_ref().map(Self::convert_expr).transpose()?;

        Ok(ParsedStatement::Delete {
            table,
            where_clause,
        })
    }

    fn convert_create_index(
        name: &Option<sqlparser::ast::ObjectName>,
        table_name: &sqlparser::ast::ObjectName,
        columns: &[sqlparser::ast::OrderByExpr],
        unique: bool,
    ) -> Result<ParsedStatement, SqlError> {
        let index_name = name
            .as_ref()
            .map(|n| n.to_string())
            .ok_or_else(|| SqlError::Parse("Index name required".to_string()))?;

        let table = table_name.to_string();

        let cols: Vec<String> = columns
            .iter()
            .map(|c| c.expr.to_string())
            .collect();

        Ok(ParsedStatement::CreateIndex {
            name: index_name,
            table,
            columns: cols,
            unique,
        })
    }

    fn expr_to_usize(expr: &Expr) -> Option<usize> {
        if let Expr::Value(AstValue::Number(n, _)) = expr {
            n.parse().ok()
        } else {
            None
        }
    }
}

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

    #[test]
    fn test_parse_create_table() {
        let sql = "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)";
        let stmt = SqlParser::parse(sql).unwrap();

        if let ParsedStatement::CreateTable { name, columns, .. } = stmt {
            assert_eq!(name, "users");
            assert_eq!(columns.len(), 3);
            assert!(columns[0].primary_key);
            assert!(!columns[1].nullable);
        } else {
            panic!("Expected CreateTable");
        }
    }

    #[test]
    fn test_parse_select() {
        let sql = "SELECT name, age FROM users WHERE age > 18 ORDER BY name LIMIT 10";
        let stmt = SqlParser::parse(sql).unwrap();

        if let ParsedStatement::Select(select) = stmt {
            assert_eq!(select.columns.len(), 2);
            assert_eq!(select.from, "users");
            assert!(select.where_clause.is_some());
            assert_eq!(select.limit, Some(10));
        } else {
            panic!("Expected Select");
        }
    }

    #[test]
    fn test_parse_insert() {
        let sql = "INSERT INTO users (name, age) VALUES ('Alice', 30), ('Bob', 25)";
        let stmt = SqlParser::parse(sql).unwrap();

        if let ParsedStatement::Insert { table, columns, values } = stmt {
            assert_eq!(table, "users");
            assert_eq!(columns, vec!["name", "age"]);
            assert_eq!(values.len(), 2);
        } else {
            panic!("Expected Insert");
        }
    }

    #[test]
    fn test_parse_update() {
        let sql = "UPDATE users SET age = 31 WHERE name = 'Alice'";
        let stmt = SqlParser::parse(sql).unwrap();

        if let ParsedStatement::Update { table, assignments, where_clause } = stmt {
            assert_eq!(table, "users");
            assert_eq!(assignments.len(), 1);
            assert!(where_clause.is_some());
        } else {
            panic!("Expected Update");
        }
    }

    #[test]
    fn test_parse_delete() {
        let sql = "DELETE FROM users WHERE age < 18";
        let stmt = SqlParser::parse(sql).unwrap();

        if let ParsedStatement::Delete { table, where_clause } = stmt {
            assert_eq!(table, "users");
            assert!(where_clause.is_some());
        } else {
            panic!("Expected Delete");
        }
    }
}
