use crate::ast::Expr;
use crate::catalog::Catalog;
use crate::error::DbError;
use crate::storage::Storage;
use crate::types::Value;

use super::common::eval_expr_to_value;
use crate::executor::clock::Clock;
use crate::executor::context::ExecutionContext;
use crate::executor::model::ContextTable;

pub(crate) fn eval_databasepropertyex(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 2 {
        return Err(DbError::Execution(
            "DATABASEPROPERTYEX expects 2 arguments".into(),
        ));
    }
    let db_val = eval_expr_to_value(&args[0], row, ctx, catalog, storage, clock)?;
    let prop_val = eval_expr_to_value(&args[1], row, ctx, catalog, storage, clock)?;
    if db_val.is_null() || prop_val.is_null() {
        return Ok(Value::Null);
    }
    let db_name = match db_val {
        Value::Int(1) | Value::BigInt(1) | Value::SmallInt(1) | Value::TinyInt(1) => {
            "master".to_string()
        }
        Value::Int(2) | Value::BigInt(2) | Value::SmallInt(2) | Value::TinyInt(2) => {
            "tempdb".to_string()
        }
        Value::Int(3) | Value::BigInt(3) | Value::SmallInt(3) | Value::TinyInt(3) => {
            "model".to_string()
        }
        Value::Int(4) | Value::BigInt(4) | Value::SmallInt(4) | Value::TinyInt(4) => {
            "msdb".to_string()
        }
        Value::Int(5) | Value::BigInt(5) | Value::SmallInt(5) | Value::TinyInt(5) => {
            "iridium_sql".to_string()
        }
        _ => db_val.to_string_value(),
    };
    let is_known_db = matches!(
        db_name.to_ascii_lowercase().as_str(),
        "master" | "tempdb" | "model" | "msdb" | "iridium_sql"
    );
    let active_db = ctx
        .metadata
        .database
        .as_ref()
        .unwrap_or(&ctx.metadata.original_database)
        .to_string();
    if !is_known_db && !active_db.eq_ignore_ascii_case(&db_name) {
        return Ok(Value::Null);
    }
    let prop = prop_val.to_string_value().to_ascii_uppercase();
    Ok(match prop.as_str() {
        "COLLATION" => Value::NVarChar("SQL_Latin1_General_CP1_CI_AS".to_string()),
        "STATUS" => Value::NVarChar("ONLINE".to_string()),
        "UPDATEABILITY" => Value::NVarChar("READ_WRITE".to_string()),
        "USERACCESS" => Value::NVarChar("MULTI_USER".to_string()),
        "ISREADONLY" => Value::Int(0),
        "ISANSINULLDEFAULT" | "ISANSI_NULL_DEFAULT" => {
            Value::Int(if ctx.metadata.ansi_nulls { 1 } else { 0 })
        }
        "COMPATIBILITYLEVEL" => Value::Int(160),
        "RECOVERY" | "RECOVERYMODEL" => {
            Value::NVarChar(if db_name.eq_ignore_ascii_case("tempdb") {
                "SIMPLE".to_string()
            } else {
                "FULL".to_string()
            })
        }
        "ISAUTOSHRINK" | "ISAUTO_SHRINK_ON" => Value::Int(0),
        "ISAUTOCLOSE" | "ISAUTO_CLOSE_ON" => Value::Int(0),
        "ISFULLTEXTENABLED" => Value::Int(0),
        "ISBROKERPRIORITYHONORED" => Value::Int(0),
        "ISBROKERENABLED" | "SERVICEBROKERENABLED" => Value::Int(0),
        "ISARITHMETICABORT" | "ISARITHMETIC_ABORT" => Value::Int(0),
        "ISCONCATNULLYIELDSNULL" | "ISCONCAT_NULL_YIELDS_NULL" => Value::Int(1),
        "ISNUMERICROUNDABORT" | "ISNUMERIC_ROUNDABORT" => Value::Int(0),
        "ISQUOTEDIDENTIFIER" | "ISQUOTED_IDENTIFIER" => Value::Int(1),
        "ISRECURSIVETRIGENABLED" | "ISRECURSIVE_TRIGGERS_ENABLED" => Value::Int(0),
        "ISDATEPRIORITYYMD" | "ISDATE_CORRELATION_OPTIMIZATION" => Value::Int(0),
        "ISPARAMETERIZATIONFORCED" | "ISPARAMETERIZATION_FORCED" => Value::Int(0),
        "ISSUPPLEMENTALLOGGINGENABLED" | "ISSUPPLEMENTAL_LOGGING" => Value::Int(0),
        "ISTRANSFORMNOISEWORDSENABLED" | "ISTRANSFORM_NOISE_WORDS" => Value::Int(0),
        "ISTRUSTWORTHY" | "ISTRUSTWORTHY_ON" => Value::Int(0),
        "ISCURSORDEFAULT" | "ISCURSORCLOSE_ON_COMMIT" => Value::Int(0),
        "ISINSTANDBY" => Value::Int(0),
        "ISAUTOCREATESTATISTICS" | "ISAUTOCREATESTATISTICS_ON" => Value::Int(1),
        "ISAUTOUPDATESTATISTICS" | "ISAUTOUPDATESTATISTICS_ON" => Value::Int(1),
        "ISAUTOUPDATESTATISTICSASYNC" | "ISAUTOUPDATESTATISTICSASYNC_ON" => Value::Int(0),
        "ISENCRYPTED" | "ISENCRYPTION_ON" => Value::Int(0),
        "ISPUBLISHED" | "ISSUBSCRIBED" | "ISMERGEPUBLISHED" | "ISDISTRIBUTOR" => Value::Int(0),
        "VERSION" => Value::Int(957),
        _ => Value::Null,
    })
}

pub(crate) fn eval_original_db_name(
    args: &[Expr],
    ctx: &ExecutionContext,
) -> Result<Value, DbError> {
    if !args.is_empty() {
        return Err(DbError::Execution(
            "ORIGINAL_DB_NAME expects no arguments".into(),
        ));
    }
    Ok(Value::NVarChar(ctx.metadata.original_database.clone()))
}

pub(crate) fn eval_db_name(args: &[Expr], ctx: &ExecutionContext) -> Result<Value, DbError> {
    if args.len() > 1 {
        return Err(DbError::Execution(
            "DB_NAME expects 0 or 1 arguments".into(),
        ));
    }
    if let Some(arg) = args.first() {
        let name = match arg {
            Expr::Integer(1) => "master".to_string(),
            Expr::Integer(2) => "tempdb".to_string(),
            Expr::Integer(3) => "model".to_string(),
            Expr::Integer(4) => "msdb".to_string(),
            Expr::Integer(5) => "iridium_sql".to_string(),
            Expr::String(s) | Expr::UnicodeString(s) => {
                if s.eq_ignore_ascii_case("master")
                    || s.eq_ignore_ascii_case("tempdb")
                    || s.eq_ignore_ascii_case("model")
                    || s.eq_ignore_ascii_case("msdb")
                    || s.eq_ignore_ascii_case("iridium_sql")
                {
                    s.clone()
                } else {
                    return Ok(Value::Null);
                }
            }
            _ => return Ok(Value::Null),
        };
        return Ok(Value::NVarChar(name));
    }
    Ok(Value::NVarChar(
        ctx.metadata
            .database
            .clone()
            .unwrap_or_else(|| "master".to_string()),
    ))
}

pub(crate) fn eval_db_id(args: &[Expr], _ctx: &ExecutionContext) -> Result<Value, DbError> {
    if args.len() > 1 {
        return Err(DbError::Execution("DB_ID expects 0 or 1 arguments".into()));
    }
    if let Some(arg) = args.first() {
        return Ok(match arg {
            Expr::Integer(1) => Value::Int(1),
            Expr::Integer(2) => Value::Int(2),
            Expr::Integer(3) => Value::Int(3),
            Expr::Integer(4) => Value::Int(4),
            Expr::Integer(5) => Value::Int(5),
            Expr::String(s) | Expr::UnicodeString(s) if s.eq_ignore_ascii_case("master") => {
                Value::Int(1)
            }
            Expr::String(s) | Expr::UnicodeString(s) if s.eq_ignore_ascii_case("tempdb") => {
                Value::Int(2)
            }
            Expr::String(s) | Expr::UnicodeString(s) if s.eq_ignore_ascii_case("model") => {
                Value::Int(3)
            }
            Expr::String(s) | Expr::UnicodeString(s) if s.eq_ignore_ascii_case("msdb") => {
                Value::Int(4)
            }
            Expr::String(s) | Expr::UnicodeString(s) if s.eq_ignore_ascii_case("iridium_sql") => {
                Value::Int(5)
            }
            _ => Value::Null,
        });
    }
    Ok(Value::Int(1))
}


