use mlua::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;

/// Lua-side environment variable overrides.
///
/// Values set via `senl.env.set()` are stored here instead of the OS
/// environment, avoiding `unsafe` calls to `std::env::set_var`.
/// `senl.env.get()` checks this overlay first, falling back to the
/// real OS environment.
struct EnvOverrides(RefCell<HashMap<String, String>>);

pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
    if lua.app_data_ref::<EnvOverrides>().is_none() {
        lua.set_app_data(EnvOverrides(RefCell::new(HashMap::new())));
    }

    let t = lua.create_table()?;

    t.set(
        "get",
        lua.create_function(|lua, key: String| {
            if let Some(ov) = lua.app_data_ref::<EnvOverrides>() {
                if let Some(val) = ov.0.borrow().get(&key) {
                    return Ok(Some(val.clone()));
                }
            }
            Ok(std::env::var(&key).ok())
        })?,
    )?;

    t.set(
        "get_or",
        lua.create_function(|lua, (key, default): (String, String)| {
            if let Some(ov) = lua.app_data_ref::<EnvOverrides>() {
                if let Some(val) = ov.0.borrow().get(&key) {
                    return Ok(val.clone());
                }
            }
            Ok(std::env::var(&key).unwrap_or(default))
        })?,
    )?;

    t.set(
        "set",
        lua.create_function(|lua, (key, value): (String, String)| {
            let ov = lua
                .app_data_ref::<EnvOverrides>()
                .ok_or_else(|| LuaError::external("env overlay not initialized"))?;
            ov.0.borrow_mut().insert(key, value);
            Ok(())
        })?,
    )?;

    t.set(
        "home",
        lua.create_function(|_, _: ()| {
            Ok(std::env::var("HOME")
                .or_else(|_| std::env::var("USERPROFILE"))
                .ok())
        })?,
    )?;

    Ok(t)
}

#[cfg(test)]
mod tests {
    use crate::runtime;

    fn eval<T: mlua::FromLua>(code: &str) -> T {
        let lua = runtime::create_lua(&[], &[]).unwrap();
        lua.load(code).eval().unwrap()
    }

    #[test]
    fn get_existing_var() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            return type(env.get("PATH"))
        "#,
        );
        assert_eq!(s, "string");
    }

    #[test]
    fn get_missing_var_returns_nil() {
        let b: bool = eval(
            r#"
            local env = require("senl.env")
            return env.get("__SENL_DOES_NOT_EXIST__") == nil
        "#,
        );
        assert!(b);
    }

    #[test]
    fn get_or_returns_default() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            return env.get_or("__SENL_MISSING__", "fallback")
        "#,
        );
        assert_eq!(s, "fallback");
    }

    #[test]
    fn home_returns_string_or_nil() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            local h = env.home()
            return h ~= nil and "ok" or "nil"
        "#,
        );
        assert_eq!(s, "ok");
    }

    #[test]
    fn set_and_get_roundtrip() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            env.set("__SENL_TEST_KEY__", "test_value")
            return env.get("__SENL_TEST_KEY__")
        "#,
        );
        assert_eq!(s, "test_value");
    }

    #[test]
    fn set_overrides_os_var() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            env.set("PATH", "overridden")
            return env.get("PATH")
        "#,
        );
        assert_eq!(s, "overridden");
    }

    #[test]
    fn get_or_uses_set_value() {
        let s: String = eval(
            r#"
            local env = require("senl.env")
            env.set("__SENL_GOR__", "real")
            return env.get_or("__SENL_GOR__", "default")
        "#,
        );
        assert_eq!(s, "real");
    }
}
