use sqlparser::ast::{
    CreateFunction, CreateFunctionBody, FunctionBehavior, FunctionSecurity, Statement,
};

use super::error::SqlParseError;
use super::util::{data_type_to_str, extract_string_literal, object_name_parts};
use crate::states::{schema_qualified_key, FunctionDef, Volatility};

pub(super) fn parse_create_function(
    cf: &CreateFunction,
    stmt: &Statement,
) -> Result<(String, FunctionDef), SqlParseError> {
    let (fn_name, schema) = object_name_parts(&cf.name);
    let key = schema_qualified_key(&fn_name, schema.as_deref());

    let arguments = cf
        .args
        .as_deref()
        .unwrap_or(&[])
        .iter()
        .map(|arg| {
            let mode = arg.mode.as_ref().map(|m| format!("{} ", m)).unwrap_or_default();
            let name = arg
                .name
                .as_ref()
                .map(|n| format!("{} ", n.value))
                .unwrap_or_default();
            format!("{}{}{}", mode, name, data_type_to_str(&arg.data_type))
        })
        .collect::<Vec<_>>()
        .join(", ");

    let returns = cf
        .return_type
        .as_ref()
        .map(|dt| data_type_to_str(dt))
        .unwrap_or_else(|| "void".to_string());

    let language = cf
        .language
        .as_ref()
        .map(|l| l.value.to_lowercase())
        .unwrap_or_else(|| "sql".to_string());

    let body = match &cf.function_body {
        Some(CreateFunctionBody::AsBeforeOptions { body, .. }) => {
            extract_string_literal(body).unwrap_or_else(|| body.to_string())
        }
        Some(CreateFunctionBody::AsAfterOptions(expr)) => {
            extract_string_literal(expr).unwrap_or_else(|| expr.to_string())
        }
        Some(CreateFunctionBody::Return(expr)) => expr.to_string(),
        Some(CreateFunctionBody::AsReturnExpr(expr)) => expr.to_string(),
        Some(CreateFunctionBody::AsReturnSelect(query)) => query.to_string(),
        Some(CreateFunctionBody::AsBeginEnd(_)) => stmt.to_string(),
        None => String::new(),
    };

    let volatility = match &cf.behavior {
        Some(FunctionBehavior::Immutable) => Volatility::Immutable,
        Some(FunctionBehavior::Stable) => Volatility::Stable,
        _ => Volatility::Volatile,
    };

    Ok((
        key,
        FunctionDef {
            name: fn_name,
            schema,
            arguments,
            returns,
            language,
            body,
            volatility,
            security_definer: matches!(&cf.security, Some(FunctionSecurity::Definer)),
        },
    ))
}
