#!/usr/bin/env lua
-- Unit tests for un.lua - tests internal functions without API calls

local passed = 0
local failed = 0

local function test(name, fn)
    local ok, err = pcall(fn)
    if ok then
        print("  ✓ " .. name)
        passed = passed + 1
    else
        print("  ✗ " .. name)
        print("    " .. tostring(err))
        failed = failed + 1
    end
end

local function assert_equal(actual, expected, msg)
    if actual ~= expected then
        error(string.format("Expected '%s' but got '%s' %s", tostring(expected), tostring(actual), msg or ""))
    end
end

local function assert_not_equal(a, b)
    if a == b then
        error(string.format("Expected values to be different but both were '%s'", tostring(a)))
    end
end

local function assert_contains(str, substr)
    if not string.find(str, substr, 1, true) then
        error(string.format("Expected '%s' to contain '%s'", str, substr))
    end
end

local function assert_true(val)
    if not val then
        error("Expected true but got false")
    end
end

-- Extension mapping (from un.lua)
local ext_map = {
    [".py"] = "python", [".js"] = "javascript", [".ts"] = "typescript",
    [".rb"] = "ruby", [".php"] = "php", [".pl"] = "perl", [".lua"] = "lua",
    [".sh"] = "bash", [".go"] = "go", [".rs"] = "rust", [".c"] = "c",
    [".cpp"] = "cpp", [".cc"] = "cpp", [".cxx"] = "cpp",
    [".java"] = "java", [".kt"] = "kotlin", [".cs"] = "csharp", [".fs"] = "fsharp",
    [".hs"] = "haskell", [".ml"] = "ocaml", [".clj"] = "clojure", [".scm"] = "scheme",
    [".lisp"] = "commonlisp", [".erl"] = "erlang", [".ex"] = "elixir", [".exs"] = "elixir",
    [".jl"] = "julia", [".r"] = "r", [".R"] = "r", [".cr"] = "crystal",
    [".d"] = "d", [".nim"] = "nim", [".zig"] = "zig", [".v"] = "v",
    [".dart"] = "dart", [".groovy"] = "groovy", [".scala"] = "scala",
    [".f90"] = "fortran", [".f95"] = "fortran", [".cob"] = "cobol",
    [".pro"] = "prolog", [".forth"] = "forth", [".4th"] = "forth",
    [".tcl"] = "tcl", [".raku"] = "raku", [".m"] = "objc",
}

-- ============================================================================
-- Extension Mapping Tests
-- ============================================================================

print("\n=== Extension Mapping Tests ===")

test("Python extension maps correctly", function()
    assert_equal(ext_map[".py"], "python")
end)

test("JavaScript extensions map correctly", function()
    assert_equal(ext_map[".js"], "javascript")
    assert_equal(ext_map[".ts"], "typescript")
end)

test("Ruby extension maps correctly", function()
    assert_equal(ext_map[".rb"], "ruby")
end)

test("Go extension maps correctly", function()
    assert_equal(ext_map[".go"], "go")
end)

test("Rust extension maps correctly", function()
    assert_equal(ext_map[".rs"], "rust")
end)

test("C/C++ extensions map correctly", function()
    assert_equal(ext_map[".c"], "c")
    assert_equal(ext_map[".cpp"], "cpp")
    assert_equal(ext_map[".cc"], "cpp")
    assert_equal(ext_map[".cxx"], "cpp")
end)

test("Lua extension maps correctly", function()
    assert_equal(ext_map[".lua"], "lua")
end)

test("JVM extensions map correctly", function()
    assert_equal(ext_map[".java"], "java")
    assert_equal(ext_map[".kt"], "kotlin")
    assert_equal(ext_map[".groovy"], "groovy")
end)

test("Functional language extensions map correctly", function()
    assert_equal(ext_map[".hs"], "haskell")
    assert_equal(ext_map[".ml"], "ocaml")
    assert_equal(ext_map[".clj"], "clojure")
    assert_equal(ext_map[".erl"], "erlang")
end)

-- ============================================================================
-- HMAC Signature Tests (using openssl via shell)
-- ============================================================================

print("\n=== HMAC Signature Tests ===")

local function hmac_sha256(secret, message)
    -- Use printf for better portability and awk to extract just the hash
    local cmd = string.format("printf '%%s' '%s' | openssl dgst -sha256 -hmac '%s' | awk '{print $NF}'",
        message:gsub("'", "'\\''"), secret:gsub("'", "'\\''"))
    local handle = io.popen(cmd)
    if handle then
        local result = handle:read("*a"):gsub("%s+$", "")
        handle:close()
        return result
    end
    return nil
end

test("HMAC-SHA256 generates 64 character hex string", function()
    local sig = hmac_sha256("test-secret", "test-message")
    if sig then
        assert_equal(#sig, 64)
    end
end)

test("Same input produces same signature", function()
    local sig1 = hmac_sha256("key", "message")
    local sig2 = hmac_sha256("key", "message")
    if sig1 and sig2 then
        assert_equal(sig1, sig2)
    end
end)

test("Different secrets produce different signatures", function()
    local sig1 = hmac_sha256("key1", "message")
    local sig2 = hmac_sha256("key2", "message")
    if sig1 and sig2 then
        assert_not_equal(sig1, sig2)
    end
end)

test("Different messages produce different signatures", function()
    local sig1 = hmac_sha256("key", "message1")
    local sig2 = hmac_sha256("key", "message2")
    if sig1 and sig2 then
        assert_not_equal(sig1, sig2)
    end
end)

test("Signature format is timestamp:METHOD:path:body", function()
    local timestamp = "1704067200"
    local method = "POST"
    local endpoint = "/execute"
    local body = '{"language":"python"}'

    local message = timestamp .. ":" .. method .. ":" .. endpoint .. ":" .. body

    -- Verify format by checking structure, not counting colons (body may contain colons)
    assert_true(message:sub(1, #timestamp) == timestamp)
    assert_contains(message, ":POST:")
    assert_contains(message, ":/execute:")
end)

-- ============================================================================
-- Language Detection Tests
-- ============================================================================

print("\n=== Language Detection Tests ===")

local function get_extension(filename)
    return filename:match("%.([^%.]+)$")
end

local function detect_from_shebang(first_line)
    if first_line:sub(1, 2) == "#!" then
        if first_line:find("python") then return "python" end
        if first_line:find("node") then return "javascript" end
        if first_line:find("ruby") then return "ruby" end
        if first_line:find("perl") then return "perl" end
        if first_line:find("bash") or first_line:find("/sh") then return "bash" end
        if first_line:find("lua") then return "lua" end
        if first_line:find("php") then return "php" end
    end
    return nil
end

test("Detect language from .py extension", function()
    local ext = "." .. get_extension("script.py")
    assert_equal(ext_map[ext], "python")
end)

test("Detect language from .lua extension", function()
    local ext = "." .. get_extension("script.lua")
    assert_equal(ext_map[ext], "lua")
end)

test("Python shebang detection", function()
    assert_equal(detect_from_shebang("#!/usr/bin/env python3"), "python")
end)

test("Node shebang detection", function()
    assert_equal(detect_from_shebang("#!/usr/bin/env node"), "javascript")
end)

test("Lua shebang detection", function()
    assert_equal(detect_from_shebang("#!/usr/bin/env lua"), "lua")
end)

test("Bash shebang detection", function()
    assert_equal(detect_from_shebang("#!/bin/bash"), "bash")
end)

-- ============================================================================
-- Argument Parsing Tests
-- ============================================================================

print("\n=== Argument Parsing Tests ===")

local function parse_env_var(arg)
    local key, value = arg:match("^([^=]+)=(.*)$")
    return key, value
end

test("Parse -e KEY=VALUE format", function()
    local key, value = parse_env_var("DEBUG=1")
    assert_equal(key, "DEBUG")
    assert_equal(value, "1")
end)

test("Parse -e KEY=VALUE with equals in value", function()
    local key, value = parse_env_var("URL=https://example.com?foo=bar")
    assert_equal(key, "URL")
    assert_equal(value, "https://example.com?foo=bar")
end)

test("Valid network modes", function()
    local valid_modes = { zerotrust = true, semitrusted = true }
    assert_true(valid_modes["zerotrust"])
    assert_true(valid_modes["semitrusted"])
    assert_true(not valid_modes["invalid"])
end)

test("Subcommand detection", function()
    local subcommands = { session = true, service = true, key = true, restore = true }
    assert_true(subcommands["session"])
    assert_true(subcommands["service"])
    assert_true(not subcommands["script.py"])
end)

-- ============================================================================
-- File Operations Tests
-- ============================================================================

print("\n=== File Operations Tests ===")

test("Read text file", function()
    local tmpname = os.tmpname() .. ".py"
    local f = io.open(tmpname, "w")
    f:write('print("hello world")')
    f:close()

    f = io.open(tmpname, "r")
    local content = f:read("*a")
    f:close()
    os.remove(tmpname)

    assert_equal(content, 'print("hello world")')
end)

test("Extract file basename", function()
    local path = "/home/user/project/script.py"
    local basename = path:match("([^/]+)$")
    assert_equal(basename, "script.py")
end)

test("Extract file extension", function()
    local path = "/home/user/project/script.py"
    local ext = path:match("%.([^%.]+)$")
    assert_equal(ext, "py")
end)

-- ============================================================================
-- API Constants Tests
-- ============================================================================

print("\n=== API Constants Tests ===")

test("API base URL format", function()
    local api_base = "https://api.unsandbox.com"
    assert_true(api_base:sub(1, 8) == "https://")
    assert_contains(api_base, "unsandbox.com")
end)

test("Portal base URL format", function()
    local portal_base = "https://unsandbox.com"
    assert_true(portal_base:sub(1, 8) == "https://")
end)

-- ============================================================================
-- Summary
-- ============================================================================

print("\n=== Summary ===")
print("Passed: " .. passed)
print("Failed: " .. failed)
print("Total:  " .. (passed + failed))

os.exit(failed > 0 and 1 or 0)
