local t = require('spec.zync_test')
local fixture = t.fixtures.utils

local utils = require('zync.lib.utils')

describe('Utils lib module', function()
    before_each(function()
        t.helpers.setup()
    end)

    after_each(function()
        t.helpers.teardown()
    end)

    it('should find a file', function()
        for _, test_case in ipairs(fixture.find_file) do
            if test_case.create == true and test_case.name and test_case.name ~= '' then
                os.execute('install -D /dev/null ' .. test_case.name)
            end
            local res = utils.find_file(test_case.name)
            if test_case.pass and test_case.pass == true then
                t.assert.equal(res, t.tmp .. '/' .. test_case.name)
            else
                t.assert.equal(res, '')
            end
        end
    end)

    it('should generate a file', function()
        for _, test_case in ipairs(fixture.gen_file) do
            utils.gen_file(test_case.name, test_case.content)
            t.assert.equal(t.helpers.read_file(test_case.name), test_case.content)
        end
    end)

    it('should read a file', function()
        for _, test_case in ipairs(fixture.read_file) do
            utils.gen_file(test_case.name, test_case.content)
            local res = utils.read_file(test_case.name)
            t.assert.equal(res, test_case.content)
        end
    end)

    it('should normalize exit codes', function()
        t.assert.equal(0, utils.normalize_exit_code(nil))
        t.assert.equal(0, utils.normalize_exit_code(true))
        t.assert.equal(1, utils.normalize_exit_code(false))
        t.assert.equal(7, utils.normalize_exit_code(7))
        t.assert.equal(1, utils.normalize_exit_code('nope'))
    end)

    it('should map os.execute results to exit codes', function()
        t.assert.equal(0, utils.exit_code_from_execute(true))
        t.assert.equal(3, utils.exit_code_from_execute(3))
        t.assert.equal(2, utils.exit_code_from_execute(false, 'exit', 2))
        t.assert.equal(137, utils.exit_code_from_execute(false, 'signal', 9))
    end)

    it('should handle copy_table cycles', function()
        local original = {}
        original.self = original
        original.child = { parent = original }

        local copy = utils.copy_table(original)

        t.assert.is_truthy(copy)
        t.assert.is_not_equal(copy, original)
        t.assert.equal(copy, copy.self)
        t.assert.equal(copy, copy.child.parent)
    end)

    it('should convert strings to numbers when possible', function()
        t.assert.equal(123, utils.tonumber_if_possible('123'))
        t.assert.equal('nope', utils.tonumber_if_possible('nope'))
    end)

    it('should handle exit_code_from_execute fallback cases', function()
        t.assert.equal(1, utils.exit_code_from_execute(false))
        t.assert.equal(1, utils.exit_code_from_execute('nope'))
    end)

    it('should interpolate strings with placeholders', function()
        local res = utils.interpolate('hello ${name}', { name = 'world' })
        t.assert.equal('hello world', res)

        local missing = utils.interpolate('keep ${missing}', { name = 'world' })
        t.assert.equal('keep ${missing}', missing)

        local mixed = utils.interpolate('${greet}, ${name}!', { greet = 'hi', name = 'lua' })
        t.assert.equal('hi, lua!', mixed)
    end)

    it('should interpolate recursively across nested tables and arrays', function()
        local source = {
            board = 'native_sim',
            count = 2,
            enabled = true,
        }
        local value = {
            title = 'board=${board}',
            flags = { 'one', '${board}', 3 },
            nested = {
                count = '${count}',
                active = '${enabled}',
                list = { { name = '${board}' }, false },
            },
        }

        local res = utils.interpolate_recursive(value, source)

        t.assert.equal('board=native_sim', res.title)
        t.helpers.assert_tables_equal(res.flags, { 'one', 'native_sim', 3 })
        t.helpers.assert_tables_equal(res.nested, {
            count = '2',
            active = 'true',
            list = { { name = 'native_sim' }, false },
        })
    end)

    it('should resolve chained placeholders from the source table', function()
        local source = {
            base = '/tmp',
            root = '${base}/app',
            artifact = '${root}/build',
        }
        local value = {
            path = '${artifact}/zephyr.elf',
            nested = { dir = '${root}' },
        }

        local res = utils.interpolate_recursive(value, source)

        t.assert.equal('/tmp/app/build/zephyr.elf', res.path)
        t.helpers.assert_tables_equal(res.nested, { dir = '/tmp/app' })
    end)

    it('should keep placeholders for cyclic or unsupported source values', function()
        local source = {
            root = '${root}',
            data = {},
        }
        local value = {
            root = '${root}',
            data = '${data}',
        }

        local res = utils.interpolate_recursive(value, source)

        t.assert.equal('${root}', res.root)
        t.assert.equal('${data}', res.data)
    end)

    it('should avoid cycles during recursive interpolation', function()
        local value = {}
        value.self = value

        local res = utils.interpolate_recursive(value, { base = 'x' })

        t.assert.equal(res, res.self)
    end)
end)
