use senl::{Runtime, SenlApp};

// ============================================================
// Proposal #1: Preload API
// ============================================================

#[test]
fn with_preload_single_module() {
    let lua_app = r#"
        local sen = require("lua_sen")
        local mylib = require("mylib")

        local function info(ctx)
            return sen.ok(mylib.version)
        end

        local app = sen.app("test", "test app")
            :command("info", "Show info"):done()
        app:route("info", info)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .with_preload("mylib", "return { version = '1.2.3' }")
        .run_with_args(["info"])
        .unwrap();
    assert_eq!(exit, 0);
}

#[test]
fn with_preload_dir_maps_paths() {
    let lua_app = r#"
        local sen = require("lua_sen")
        local pkg = require("mypkg")
        local util = require("mypkg.util")

        local function check(ctx)
            return sen.ok(pkg.name .. "+" .. util.helper)
        end

        local app = sen.app("test", "test app")
            :command("check", "Check modules"):done()
        app:route("check", check)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .with_preload_dir(
            "mypkg",
            &[
                ("init.lua", "return { name = 'mypkg' }"),
                ("util.lua", "return { helper = 'ok' }"),
            ],
        )
        .run_with_args(["check"])
        .unwrap();
    assert_eq!(exit, 0);
}

#[test]
fn with_preload_dir_nested_init() {
    let lua_app = r#"
        local sen = require("lua_sen")
        local sub = require("pkg.sub")

        local function run(ctx)
            return sen.ok(sub.val)
        end

        local app = sen.app("test", "test app")
            :command("run", "Run"):done()
        app:route("run", run)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .with_preload_dir(
            "pkg",
            &[
                ("init.lua", "return { name = 'pkg' }"),
                ("sub/init.lua", "return { val = 'nested' }"),
            ],
        )
        .run_with_args(["run"])
        .unwrap();
    assert_eq!(exit, 0);
}

// ============================================================
// Proposal #2: CLI Option Arguments
// ============================================================

#[test]
fn option_with_default_value() {
    let lua_app = r#"
        local sen = require("lua_sen")

        local function connect(ctx)
            return sen.ok(ctx.args.endpoint)
        end

        local app = sen.app("test", "test app")
            :command("connect", "Connect to endpoint")
                :option("e", "endpoint", "Service endpoint", { default = "localhost:8000" })
                :done()
        app:route("connect", connect)
        return app:build()
    "#;

    // Uses default
    let exit = SenlApp::from_source("test", lua_app)
        .run_with_args(["connect"])
        .unwrap();
    assert_eq!(exit, 0);
}

#[test]
fn option_with_explicit_value() {
    let lua_app = r#"
        local sen = require("lua_sen")

        local function connect(ctx)
            if ctx.args.endpoint == "remote:9000" then
                return sen.ok("connected")
            end
            return sen.err("wrong endpoint: " .. tostring(ctx.args.endpoint))
        end

        local app = sen.app("test", "test app")
            :command("connect", "Connect")
                :option("e", "endpoint", "Service endpoint", { default = "localhost:8000" })
                :done()
        app:route("connect", connect)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .run_with_args(["connect", "--endpoint", "remote:9000"])
        .unwrap();
    assert_eq!(exit, 0);
}

#[test]
fn option_with_short_flag() {
    let lua_app = r#"
        local sen = require("lua_sen")

        local function connect(ctx)
            if ctx.args.endpoint == "short:1234" then
                return sen.ok("ok")
            end
            return sen.err("wrong")
        end

        local app = sen.app("test", "test app")
            :command("connect", "Connect")
                :option("e", "endpoint", "Endpoint", { default = "localhost:8000" })
                :done()
        app:route("connect", connect)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .run_with_args(["connect", "-e", "short:1234"])
        .unwrap();
    assert_eq!(exit, 0);
}

#[test]
fn option_mixed_with_flags_and_args() {
    let lua_app = r#"
        local sen = require("lua_sen")

        local function deploy(ctx)
            local parts = {
                ctx.args.target,
                ctx.args.env or "default",
            }
            if ctx.args.verbose then
                parts[#parts + 1] = "verbose"
            end
            return sen.ok(table.concat(parts, ","))
        end

        local app = sen.app("test", "test app")
            :command("deploy", "Deploy to target")
                :arg("target", "Deploy target")
                :option("e", "env", "Environment", { default = "staging" })
                :flag("v", "verbose", "Verbose output")
                :done()
        app:route("deploy", deploy)
        return app:build()
    "#;

    let exit = SenlApp::from_source("test", lua_app)
        .run_with_args(["deploy", "prod-server", "--env", "production", "--verbose"])
        .unwrap();
    assert_eq!(exit, 0);
}

// ============================================================
// Proposal #3: Headless Runtime
// ============================================================

#[test]
fn runtime_new_default() {
    let rt = Runtime::new().unwrap();
    let val: i32 = rt.eval("return 1 + 2").unwrap();
    assert_eq!(val, 3);
}

#[test]
fn runtime_lua_sen_available() {
    let rt = Runtime::new().unwrap();
    let result: String = rt
        .eval(r#"local sen = require("lua_sen"); return type(sen.app)"#)
        .unwrap();
    assert_eq!(result, "function");
}

#[test]
fn runtime_builder_with_preload() {
    let rt = Runtime::builder()
        .with_preload("testmod", "return { answer = 42 }")
        .build()
        .unwrap();
    let val: i32 = rt.eval("return require('testmod').answer").unwrap();
    assert_eq!(val, 42);
}

#[test]
fn runtime_builder_with_preload_dir() {
    let rt = Runtime::builder()
        .with_preload_dir(
            "mylib",
            &[
                ("init.lua", "return { name = 'mylib' }"),
                ("math.lua", "return { pi = 3.14 }"),
            ],
        )
        .build()
        .unwrap();

    let name: String = rt.eval("return require('mylib').name").unwrap();
    assert_eq!(name, "mylib");

    let pi: f64 = rt.eval("return require('mylib.math').pi").unwrap();
    assert!((pi - 3.14).abs() < f64::EPSILON);
}

#[test]
fn runtime_helpers_available() {
    let rt = Runtime::new().unwrap();
    let result: String = rt
        .eval(r#"local env = require("senl.env"); return type(env.get)"#)
        .unwrap();
    assert_eq!(result, "function");
}

#[test]
fn runtime_lua_access() {
    let rt = Runtime::new().unwrap();
    let lua = rt.lua();
    let globals = lua.globals();
    assert!(globals.contains_key("require").unwrap());
}

// ============================================================
// Error cases
// ============================================================

#[test]
fn preload_invalid_lua_source_errors() {
    let lua_app = r#"
        local sen = require("lua_sen")
        local bad = require("badmod")
        return sen.app("test", "test"):command("x", "x"):done():build()
    "#;

    let result = SenlApp::from_source("test", lua_app)
        .with_preload("badmod", "this is not valid lua {{{{")
        .run_with_args(["x"]);
    assert!(result.is_err());
}
