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

use super::super::super::clock::Clock;
use super::super::super::context::ExecutionContext;
use super::super::super::evaluator::eval_expr;
use super::super::super::model::ContextTable;

pub(crate) fn eval_serverproperty(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution(
            "SERVERPROPERTY expects 1 argument".into(),
        ));
    }

    let property = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if property.is_null() {
        return Ok(Value::Null);
    }

    let name = property.to_string_value().to_uppercase();
    Ok(match name.as_str() {
        "EDITION" => Value::NVarChar("Developer Edition (64-bit)".to_string()),
        "ENGINEEDITION" => Value::Int(3),
        "PRODUCTVERSION" => Value::NVarChar("16.0.1000.6".to_string()),
        "PRODUCTLEVEL" => Value::NVarChar("RTM".to_string()),
        "PRODUCTUPDATELEVEL" => Value::NVarChar("".to_string()),
        "PRODUCTMAJORVERSION" => Value::NVarChar("16".to_string()),
        "PRODUCTMINORVERSION" => Value::NVarChar("0".to_string()),
        "PRODUCTBUILD" => Value::NVarChar("1000".to_string()),
        "PRODUCTBUILDTYPE" => Value::NVarChar("RTM".to_string()),
        "MACHINENAME" | "COMPUTERNAMEPHYSICALNETBIOS" => Value::NVarChar("localhost".to_string()),
        "SERVERNAME" => Value::NVarChar("localhost".to_string()),
        "INSTANCENAME"
        | "INSTANCEDEFAULTDATAPATH"
        | "INSTANCEDEFAULTLOGPATH"
        | "INSTANCEDEFAULTBACKUPPATH" => Value::Null,
        "COLLATION" | "SQLCOLLATION" => Value::NVarChar("SQL_Latin1_General_CP1_CI_AS".to_string()),
        "SQLCHARSETNAME" => Value::NVarChar("iso_1".to_string()),
        "SQLSORTORDERNAME" => Value::NVarChar("nocase_iso".to_string()),
        "ISINTEGRATEDSECURITYONLY" => Value::Int(0),
        "ISSINGLEUSER" => Value::Int(0),
        "ISXTPSUPPORTED" => Value::Int(0),
        "ISPOLYBASEINSTALLED" => Value::Int(0),
        "ISHADRENABLED" | "HADRMANAGERSTATUS" => Value::Int(0),
        "ISCLUSTERED" => Value::Int(0),
        "ISFULLTEXTINSTALLED" => Value::Int(0),
        "ISADVANCEDANALYTICSINSTALLED" => Value::Int(0),
        "ISTEMPDBMETADATAMEMORYOPTIMIZED" => Value::Int(0),
        "ISLOCALDB" | "ISLOCALDBENABLED" => Value::Int(0),
        "LCID" => Value::Int(1033),
        "BUILDCLRVERSION" => Value::NVarChar("v4.0.30319".to_string()),
        "RESOURCELASTMODIFIEDTIME" | "RESOURCELASTUPDATEDATETIME" => {
            Value::NVarChar("2024-01-01 00:00:00.000".to_string())
        }
        "RESOURCEVERSION" => Value::NVarChar("16.0.1000.6".to_string()),
        "FILESTREAMCONFIGUREDLEVEL" | "FILESTREAMLEVEL" => Value::Int(0),
        "FILESTREAMSHAREDFOLDER" | "FILESTREAMEFFECTIVELEVEL" | "FILESTREAMSHARENAME" => {
            Value::Null
        }
        "PROCESSORUSAGE" => Value::Int(0),
        "PATHSEPARATOR" => Value::NVarChar("\\".to_string()),
        "SERVERMANAGEMENTISINSTALLED" => Value::Int(0),
        "ISSTATEDSESSION" => Value::Int(0),
        "PROCESSID" => Value::Int(std::process::id() as i32),
        _ => Value::Null,
    })
}

pub(crate) fn eval_sessionproperty(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution(
            "SESSIONPROPERTY expects 1 argument".into(),
        ));
    }

    let property = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if property.is_null() {
        return Ok(Value::Null);
    }

    let name = property.to_string_value().to_uppercase();
    Ok(match name.as_str() {
        "ANSI_NULLS" => Value::Int(if ctx.options.ansi_nulls { 1 } else { 0 }),
        "ANSI_PADDING" => Value::Int(if ctx.options.ansi_padding { 1 } else { 0 }),
        "ANSI_WARNINGS" => Value::Int(if ctx.options.ansi_warnings { 1 } else { 0 }),
        "ARITHABORT" => Value::Int(if ctx.options.arithabort { 1 } else { 0 }),
        "CONCAT_NULL_YIELDS_NULL" => Value::Int(if ctx.options.concat_null_yields_null {
            1
        } else {
            0
        }),
        "NUMERIC_ROUNDABORT" => Value::Int(0),
        "QUOTED_IDENTIFIER" => Value::Int(if ctx.options.quoted_identifier { 1 } else { 0 }),
        _ => Value::Null,
    })
}

pub(crate) fn eval_fulltextserviceproperty(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution(
            "FULLTEXTSERVICEPROPERTY expects 1 argument".into(),
        ));
    }

    let property = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if property.is_null() {
        return Ok(Value::Null);
    }

    let name = property.to_string_value().to_uppercase();
    Ok(match name.as_str() {
        "ISFULLTEXTINSTALLED" => Value::Int(0),
        _ => Value::Null,
    })
}

pub(crate) fn eval_connectionproperty(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution(
            "CONNECTIONPROPERTY expects 1 argument".into(),
        ));
    }

    let property = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if property.is_null() {
        return Ok(Value::Null);
    }

    let name = property.to_string_value().to_uppercase();
    Ok(match name.as_str() {
        "NET_TRANSPORT" | "PHYSICAL_NET_TRANSPORT" => Value::NVarChar("TCP".to_string()),
        "PROTOCOL_TYPE" => Value::NVarChar("TSQL".to_string()),
        "AUTH_SCHEME" => Value::NVarChar("SQL".to_string()),
        "LOCAL_NET_ADDRESS" | "CLIENT_NET_ADDRESS" => Value::NVarChar("127.0.0.1".to_string()),
        "LOCAL_TCP_PORT" => Value::NVarChar("1433".to_string()),
        _ => Value::Null,
    })
}

pub(crate) fn eval_collationproperty(
    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(
            "COLLATIONPROPERTY expects 2 arguments".into(),
        ));
    }

    let collation = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    let property = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;
    if collation.is_null() || property.is_null() {
        return Ok(Value::Null);
    }

    let collation_name = collation.to_string_value().to_ascii_uppercase();
    let property_name = property.to_string_value().to_ascii_uppercase();

    match property_name.as_str() {
        "LCID" => Ok(match collation_name.as_str() {
            "SQL_LATIN1_GENERAL_CP1_CI_AS" | "LATIN1_GENERAL_CI_AS" => Value::Int(1033),
            _ => Value::Null,
        }),
        "COMPARISONSTYLE" => Ok(match collation_name.as_str() {
            "SQL_LATIN1_GENERAL_CP1_CI_AS" | "LATIN1_GENERAL_CI_AS" => Value::Int(196609),
            _ => Value::Null,
        }),
        _ => Ok(Value::Null),
    }
}

pub(crate) fn eval_microsoft_version() -> Value {
    Value::Int(0x1000_1009)
}

pub(crate) fn eval_context_info(
    args: &[Expr],
    _row: &[ContextTable],
    ctx: &mut ExecutionContext,
    _catalog: &dyn Catalog,
    _storage: &dyn Storage,
    _clock: &dyn Clock,
) -> Result<Value, DbError> {
    if !args.is_empty() {
        return Err(DbError::Execution(
            "CONTEXT_INFO expects no arguments".into(),
        ));
    }
    Ok(Value::VarBinary(ctx.session.context_info.clone()))
}

pub(crate) fn eval_session_context(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution(
            "SESSION_CONTEXT expects 1 argument".into(),
        ));
    }
    let key_val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if key_val.is_null() {
        return Ok(Value::Null);
    }
    let key = key_val.to_string_value();
    Ok(ctx
        .session
        .session_context
        .get(&key)
        .map(|(v, _)| v.clone())
        .unwrap_or(Value::Null))
}
