use crate::ext::register_scalar_function;
use turso_ext::{scalar, ExtensionApi, ResultCode, Value, ValueType};

pub fn register_extension(ext_api: &mut ExtensionApi) {
    // FIXME: Add macro magic to register functions automatically.
    unsafe {
        register_scalar_function(ext_api.ctx, c"uuid4_str".as_ptr(), uuid4_str);
        register_scalar_function(ext_api.ctx, c"gen_random_uuid".as_ptr(), uuid4_str);
        register_scalar_function(ext_api.ctx, c"uuid4".as_ptr(), uuid4_blob);
        register_scalar_function(ext_api.ctx, c"uuid7_str".as_ptr(), uuid7_str);
        register_scalar_function(ext_api.ctx, c"uuid7".as_ptr(), uuid7);
        register_scalar_function(ext_api.ctx, c"uuid7_timestamp_ms".as_ptr(), uuid7_ts);
        register_scalar_function(ext_api.ctx, c"uuid_str".as_ptr(), uuid_str);
        register_scalar_function(ext_api.ctx, c"uuid_blob".as_ptr(), uuid_blob);
    }
}

#[scalar(name = "uuid4_str", alias = "gen_random_uuid")]
fn uuid4_str(_args: &[Value]) -> Value {
    let uuid = uuid::Uuid::new_v4().to_string();
    Value::from_text(uuid)
}

#[scalar(name = "uuid4")]
fn uuid4_blob(_args: &[Value]) -> Value {
    let uuid = uuid::Uuid::new_v4();
    let bytes = uuid.as_bytes();
    Value::from_blob(bytes.to_vec())
}

#[scalar(name = "uuid7_str")]
fn uuid7_str(args: &[Value]) -> Value {
    let timestamp = if args.is_empty() {
        let ctx = uuid::ContextV7::new();
        uuid::Timestamp::now(ctx)
    } else {
        match args[0].value_type() {
            ValueType::Integer => {
                let ctx = uuid::ContextV7::new();
                let Some(int) = args[0].to_integer() else {
                    return Value::error(ResultCode::InvalidArgs);
                };
                uuid::Timestamp::from_unix(ctx, int as u64, 0)
            }
            ValueType::Text => {
                let Some(text) = args[0].to_text() else {
                    return Value::error(ResultCode::InvalidArgs);
                };
                match text.parse::<i64>() {
                    Ok(unix) => {
                        if unix <= 0 {
                            return Value::error_with_message("Invalid timestamp".to_string());
                        }
                        uuid::Timestamp::from_unix(uuid::ContextV7::new(), unix as u64, 0)
                    }
                    Err(_) => return Value::error(ResultCode::InvalidArgs),
                }
            }
            _ => return Value::error(ResultCode::InvalidArgs),
        }
    };
    let uuid = uuid::Uuid::new_v7(timestamp);
    Value::from_text(uuid.to_string())
}

#[scalar(name = "uuid7")]
fn uuid7(&self, args: &[Value]) -> Value {
    let timestamp = if args.is_empty() {
        let ctx = uuid::ContextV7::new();
        uuid::Timestamp::now(ctx)
    } else {
        match args[0].value_type() {
            ValueType::Integer => {
                let ctx = uuid::ContextV7::new();
                let Some(int) = args[0].to_integer() else {
                    return Value::null();
                };
                uuid::Timestamp::from_unix(ctx, int as u64, 0)
            }
            _ => return Value::null(),
        }
    };
    let uuid = uuid::Uuid::new_v7(timestamp);
    let bytes = uuid.as_bytes();
    Value::from_blob(bytes.to_vec())
}

#[scalar(name = "uuid7_timestamp_ms")]
fn uuid7_ts(args: &[Value]) -> Value {
    match args.first().map(|a| a.value_type()) {
        Some(ValueType::Blob) => {
            let Some(blob) = &args[0].to_blob() else {
                return Value::null();
            };
            let Ok(uuid) = uuid::Uuid::from_slice(blob.as_slice()) else {
                return Value::null();
            };
            let unix = uuid_to_unix(uuid.as_bytes());
            Value::from_integer(unix as i64)
        }
        Some(ValueType::Text) => {
            let Some(text) = args[0].to_text() else {
                return Value::null();
            };
            let Ok(uuid) = uuid::Uuid::parse_str(text) else {
                return Value::null();
            };
            let unix = uuid_to_unix(uuid.as_bytes());
            Value::from_integer(unix as i64)
        }
        None => Value::error_with_message(
            "wrong number of arguments to function uuid7_timestamp_ms()".into(),
        ),
        _ => Value::null(),
    }
}

#[scalar(name = "uuid_str")]
fn uuid_str(args: &[Value]) -> Value {
    let Some(blob) = args.first().and_then(|a| a.to_blob()) else {
        return Value::error_with_message(
            "wrong number of arguments to function uuid_str()".into(),
        );
    };
    let parsed = uuid::Uuid::from_slice(blob.as_slice())
        .ok()
        .map(|u| u.to_string());
    match parsed {
        Some(s) => Value::from_text(s),
        None => Value::null(),
    }
}

#[scalar(name = "uuid_blob")]
fn uuid_blob(&self, args: &[Value]) -> Value {
    let Some(text) = args.first().and_then(|a| a.to_text()) else {
        return Value::error_with_message(
            "wrong number of arguments to function uuid_blob()".into(),
        );
    };
    match uuid::Uuid::parse_str(text) {
        Ok(uuid) => Value::from_blob(uuid.as_bytes().to_vec()),
        Err(_) => Value::null(),
    }
}

#[inline(always)]
fn uuid_to_unix(uuid: &[u8; 16]) -> u64 {
    ((uuid[0] as u64) << 40)
        | ((uuid[1] as u64) << 32)
        | ((uuid[2] as u64) << 24)
        | ((uuid[3] as u64) << 16)
        | ((uuid[4] as u64) << 8)
        | (uuid[5] as u64)
}
