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

local manager = require('zync.api.manager')

local cmd = { 'Lorem', 'ipsum', 'dolor', 'sit', 'amet' }
local cmd_exp = ' Lorem ipsum dolor sit amet'

    local clean_stub
    local config_stub
    local build_stub
    local list_stub
    local print_stub

describe('Manager lib module', function()
    local function base_config()
        local config = fixture.build_conf(utils.copy_table(fixture.tconf))
        config.project_dir = t.tmp
        return config
    end

    before_each(function()
        t.helpers.setup()
    end)

    after_each(function()
        t.helpers.teardown()
        if clean_stub then
            clean_stub:revert()
        end
        if config_stub then
            config_stub:revert()
        end
        if build_stub then
            build_stub:revert()
        end
        if list_stub then
            list_stub:revert()
        end
        if print_stub then
            print_stub:revert()
        end
        t.lfs.chdir(t.tmp)
        os.execute('rm -rf ./build')
    end)

    it('should make a build dir', function()
        t.helpers.dir_exists('build', false)
        manager.mkbuild({})
        t.lfs.chdir('..')
        t.helpers.dir_exists('build', true)
        os.execute('rm -rf ./build')
        t.helpers.dir_exists('build', false)
        manager.mkbuild({ is_build = true })
        t.helpers.dir_exists('build', false)
    end)

    it('should use profile build dir when configured', function()
        local config = {
            build_dir = 'build/sim',
            project_dir = t.tmp,
        }

        manager.mkbuild(config)
        t.assert.is_truthy(t.lfs.currentdir():match('/build/sim$'))
        t.lfs.chdir(config.project_dir)
        t.helpers.dir_exists('build/sim', true)

        manager.clean(config)
        t.lfs.chdir(config.project_dir)
        t.helpers.dir_exists('build/sim', true)
    end)

    it('should build a cmd', function()
        local res = manager.build_cmd(cmd)
        t.assert.equal(res, cmd_exp)
    end)

    it('should execute a command', function()
        manager.execute(cmd)
        t.assert.stub(manager.execute).was.called_with(cmd)
    end)

    it('should clean build dir', function()
        fixture.gen_build()
        manager.clean({})
        t.lfs.chdir('..')
        fixture.check_build()

        fixture.gen_build()
        t.lfs.chdir('build')
        manager.clean({ is_build = true })
        t.lfs.chdir('..')
        fixture.check_build()
    end)

    it('should create zync config', function()
        for _, test_case in ipairs(fixture.init) do
            local exp =
                string.format(fixture.conf_cont, test_case.board or fixture.board, test_case.runner or fixture.runner)

            manager.init(test_case)
            local res = t.helpers.read_file(fixture.conf_name)
            t.assert.equal(res, exp)
            os.execute('rm ' .. fixture.conf_name)

            manager.mkbuild({})

            manager.init(test_case)
            res = t.helpers.read_file(fixture.conf_name)
            t.assert.equal(res, exp)
            os.execute('rm ' .. fixture.conf_name)
        end
    end)

    it('should run config', function()
        for _, test_case in ipairs(fixture.conf) do
            local config = fixture.build_conf(test_case)
            config.project_dir = t.tmp
            local exp = fixture.build_cmd(config)
            manager.configure(config)
            t.assert.stub(manager.execute).was.called_with(exp)
        end
    end)

    it('should apply global args to phases', function()
        local config = base_config()
        config.args = {
            conf = '-DHG_TEST=ON',
            build = { '-j', '0' },
            flash = { '--hex-file', 'path/to/other.hex' },
            run = { '--flash_mount', '../sd' },
        }

        manager.configure(config)
        t.assert.stub(manager.execute).was.called_with({
            'cmake -GNinja',
            '-DBOARD=' .. config.board,
            '-DBOARD_FLASH_RUNNER=' .. config.runner,
            '-DHG_TEST=ON',
            t.tmp,
        })
        manager.execute:clear()

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.build(config)
        t.assert.stub(manager.execute).was.called_with({ 'ninja', '-j', '0' })
        config_stub:revert()
        manager.execute:clear()

        build_stub = t.stub(manager, 'build', function(_) end)
        manager.flash(config)
        t.assert.stub(manager.execute).was.called_with({ 'west flash', '--hex-file', 'path/to/other.hex' })
        build_stub:revert()
        manager.execute:clear()

        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe', '--flash_mount', '../sd' })
        config_stub:revert()
    end)

    it('should apply profile args to phases', function()
        local config = base_config()
        config.profile = 'sim'
        config.profiles = {
            sim = {
                args = {
                    conf = { '-DTEST=ON' },
                    build = '-j 2',
                    flash = { '--hex-file', 'path/to/profile.hex' },
                    run = '--flash_mount ../profile',
                },
            },
        }

        manager.configure(config)
        t.assert.stub(manager.execute).was.called_with({
            'cmake -GNinja',
            '-DBOARD=' .. config.board,
            '-DBOARD_FLASH_RUNNER=' .. config.runner,
            '-DTEST=ON',
            t.tmp,
        })
        manager.execute:clear()

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.build(config)
        t.assert.stub(manager.execute).was.called_with({ 'ninja', '-j 2' })
        config_stub:revert()
        manager.execute:clear()

        build_stub = t.stub(manager, 'build', function(_) end)
        manager.flash(config)
        t.assert.stub(manager.execute).was.called_with({ 'west flash', '--hex-file', 'path/to/profile.hex' })
        build_stub:revert()
        manager.execute:clear()

        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.mkdir('build/sim')
        t.lfs.chdir('build/sim')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe', '--flash_mount ../profile' })
        config_stub:revert()
    end)

    it('should apply CLI args to phases', function()
        local config = base_config()
        config[''] = { '--cli', 'arg' }

        manager.configure(config)
        t.assert.stub(manager.execute).was.called_with({
            'cmake -GNinja',
            '-DBOARD=' .. config.board,
            '-DBOARD_FLASH_RUNNER=' .. config.runner,
            '--cli',
            'arg',
            t.tmp,
        })
        manager.execute:clear()

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.build(config)
        t.assert.stub(manager.execute).was.called_with({ 'ninja', '--cli', 'arg' })
        config_stub:revert()
        manager.execute:clear()

        build_stub = t.stub(manager, 'build', function(_) end)
        manager.flash(config)
        t.assert.stub(manager.execute).was.called_with({ 'west flash', '--cli', 'arg' })
        build_stub:revert()
        manager.execute:clear()

        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe', '--cli', 'arg' })
        config_stub:revert()
    end)

    it('should append CLI args after global and profile args', function()
        local config = base_config()
        config.profile = 'sim'
        config.args = { run = '--global' }
        config.profiles = {
            sim = {
                args = {
                    run = { '--profile' },
                },
            },
        }
        config[''] = { '--cli1', '--cli2' }

        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.mkdir('build/sim')
        t.lfs.chdir('build/sim')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe', '--global', '--profile', '--cli1', '--cli2' })
        config_stub:revert()
    end)

    it('should execute zephyr binary with run args when available', function()
        local config = base_config()
        config[''] = { '--cli', 'arg' }
        config.args = { run = '--global' }
        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        build_stub = t.stub(manager, 'build', function(_) end)
        manager.run(config)
        t.assert.stub(build_stub).was.called()
        t.assert.stub(manager.execute).was.called_with({
            'zephyr/zephyr.exe',
            '--global',
            '--cli',
            'arg',
        })
        config_stub:revert()
        build_stub:revert()
    end)

    it('should fallback to ninja run without extra args when zephyr binary missing', function()
        local config = base_config()
        config[''] = { '--cli', 'arg' }
        config.args = { run = '--global' }

        build_stub = t.stub(manager, 'build', function(_) end)
        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(build_stub).was_not.called()
        t.assert.stub(manager.execute).was.called_with({ 'ninja run' })
        config_stub:revert()
        build_stub:revert()
    end)

    it('should resolve zephyr binary under sysbuild image dir', function()
        local config = {
            sysbuild = true,
            is_build = true,
            image = 2,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('mcuboot')
        t.lfs.mkdir('app')
        t.lfs.mkdir('mcuboot/zephyr')
        t.helpers.write_file('mcuboot/zephyr/zephyr.exe', '')
        fixture.create_config('test_board')

        build_stub = t.stub(manager, 'build', function(_) end)
        manager.run(config)
        t.assert.equal('mcuboot', config.image)
        t.assert.stub(build_stub).was.called()
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe' })
        t.assert.is_true(t.lfs.currentdir():match('/build/mcuboot$') ~= nil)
        build_stub:revert()
    end)

    it('should skip rebuild when norebuild set for zephyr binary', function()
        local config = base_config()
        config.norebuild = true
        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        build_stub = t.stub(manager, 'build', function(_) end)
        manager.run(config)
        t.assert.stub(build_stub).was_not.called()
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe' })
        config_stub:revert()
        build_stub:revert()
    end)

    it('should append profile args after global args', function()
        local config = base_config()
        config.profile = 'sim'
        config.args = { run = '--global' }
        config.profiles = {
            sim = {
                args = {
                    run = { '--profile' },
                },
            },
        }

        t.lfs.chdir(t.tmp)
        t.lfs.mkdir('build')
        t.lfs.mkdir('build/sim')
        t.lfs.chdir('build/sim')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        config.is_build = true

        config_stub = t.stub(manager, 'configure', function(_) end)
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with({ 'zephyr/zephyr.exe', '--global', '--profile' })
        config_stub:revert()
    end)

    it('should clean build on board config change', function()
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        local config = fixture.build_conf(fixture.tconf)
        config.is_build = true

        clean_stub = t.stub(manager, 'clean', function(_) end)
        manager.configure(config)
        t.assert.stub(clean_stub).was_not.called()

        fixture.gen_cache(config.board)
        manager.configure(config)
        t.assert.stub(clean_stub).was_not.called()

        fixture.gen_cache(fixture.board)
        manager.configure(config)
        t.assert.stub(clean_stub).was.called()

        clean_stub:revert()
    end)

    it('should run build', function()
        local config = fixture.build_conf(fixture.tconf)
        config.project_dir = t.tmp
        config_stub = t.stub(manager, 'configure', function(_) end)
        local exp = { 'ninja' }

        manager.build(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was.called()
        config_stub:clear()

        fixture.create_config(config.board)

        manager.build(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was_not.called()

        config.board = fixture.board
        manager.build(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was.called()

        config_stub:revert()
    end)

    it('should run flash', function()
        local config = fixture.build_conf(fixture.tconf)
        config.project_dir = t.tmp
        config.board = 'test_board'
        build_stub = t.stub(manager, 'build', function(_) end)
        local exp = { 'west flash' }

        manager.flash(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(build_stub).was.called()
        build_stub:revert()
    end)

    it('should reject flash list on non-sysbuild projects', function()
        local config = {
            board = fixture.tconf.board,
            runner = fixture.tconf.runner,
            zephyr_base = fixture.zephyr_base,
            project_dir = t.tmp,
        }
        config.list = true
        build_stub = t.stub(manager, 'build', function(_) end)
        local print_stub = t.stub(_G, 'print')
        local og_logs = _G.ZYNC_LOGS
        _G.ZYNC_LOGS = true

        local exit_code = manager.flash(config)

        t.assert.equal(1, exit_code)
        t.assert.stub(build_stub).was_not.called()
        t.assert.stub(print_stub).was_called_with('zync: Option --list is only supported for sysbuild projects')

        _G.ZYNC_LOGS = og_logs
        print_stub:revert()
        build_stub:revert()
    end)

    it('should reject flash image on non-sysbuild projects', function()
        local config = {
            board = fixture.tconf.board,
            runner = fixture.tconf.runner,
            zephyr_base = fixture.zephyr_base,
            project_dir = t.tmp,
        }
        config.image = 1
        build_stub = t.stub(manager, 'build', function(_) end)
        local print_stub = t.stub(_G, 'print')
        local og_logs = _G.ZYNC_LOGS
        _G.ZYNC_LOGS = true

        local exit_code = manager.flash(config)

        t.assert.equal(1, exit_code)
        t.assert.stub(build_stub).was_not.called()
        t.assert.stub(print_stub).was_called_with('zync: Option --image is only supported for sysbuild projects')

        _G.ZYNC_LOGS = og_logs
        print_stub:revert()
        build_stub:revert()
    end)

    it('should run sim', function()
        local config = fixture.build_conf(fixture.tconf)
        config.project_dir = t.tmp
        config.board = 'test_board'
        config_stub = t.stub(manager, 'configure', function(_) end)
        local exp = { 'ninja run' }

        manager.run(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was.called()
        config_stub:clear()

        fixture.create_config(config.board)

        manager.run(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was_not.called()

        config.board = fixture.board
        manager.run(config)
        t.assert.stub(manager.execute).was.called_with(exp)
        t.assert.stub(config_stub).was.called()

        config_stub:revert()
    end)

    it('should reject run list on non-sysbuild projects', function()
        local config = {
            board = fixture.tconf.board,
            runner = fixture.tconf.runner,
            zephyr_base = fixture.zephyr_base,
            project_dir = t.tmp,
        }
        config.list = true
        config_stub = t.stub(manager, 'configure', function(_) end)
        local print_stub = t.stub(_G, 'print')
        local og_logs = _G.ZYNC_LOGS
        _G.ZYNC_LOGS = true

        local exit_code = manager.run(config)

        t.assert.equal(1, exit_code)
        t.assert.stub(config_stub).was_not.called()
        t.assert.stub(print_stub).was_called_with('zync: Option --list is only supported for sysbuild projects')

        _G.ZYNC_LOGS = og_logs
        print_stub:revert()
        config_stub:revert()
    end)

    it('should reject run image on non-sysbuild projects', function()
        local config = {
            board = fixture.tconf.board,
            runner = fixture.tconf.runner,
            zephyr_base = fixture.zephyr_base,
            project_dir = t.tmp,
        }
        config.image = 1
        config_stub = t.stub(manager, 'configure', function(_) end)
        local print_stub = t.stub(_G, 'print')
        local og_logs = _G.ZYNC_LOGS
        _G.ZYNC_LOGS = true

        local exit_code = manager.run(config)

        t.assert.equal(1, exit_code)
        t.assert.stub(config_stub).was_not.called()
        t.assert.stub(print_stub).was_called_with('zync: Option --image is only supported for sysbuild projects')

        _G.ZYNC_LOGS = og_logs
        print_stub:revert()
        config_stub:revert()
    end)

    it('should list configured profiles in order', function()
        local config = {
            profiles = {
                prod = {},
                sim = {},
            },
            profile_order = { 'prod', 'sim' },
        }

        local print_stub = t.stub(_G, 'print')
        manager.profiles(config)

        t.assert.stub(print_stub).was_called_with('prod')
        t.assert.stub(print_stub).was_called_with('sim')

        print_stub:revert()
    end)

    it('should list configured profiles alphabetically without profile_order', function()
        local config = {
            profiles = {
                zeta = {},
                alpha = {},
            },
        }

        local print_stub = t.stub(_G, 'print')
        manager.profiles(config)

        t.assert.stub(print_stub).was_called_with('alpha')
        t.assert.stub(print_stub).was_called_with('zeta')

        print_stub:revert()
    end)

    it('should log profile-less message when profiles are not configured', function()
        local config = {}
        local print_stub = t.stub(_G, 'print')

        manager.profiles(config)

        t.assert.stub(print_stub).was_called_with('\nNo profiles configured - project configured in profile-less mode\n')

        print_stub:revert()
    end)

    it('should list sysbuild images when requested', function()
        local config = {
            sysbuild = true,
            list = true,
            is_build = true,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('app')
        t.lfs.mkdir('mcuboot')

        build_stub = t.stub(manager, 'build', function(_) end)
        list_stub = t.stub(manager, 'list_images', function() end)

        manager.flash(config)
        t.assert.stub(build_stub).was.called()
        t.assert.stub(list_stub).was.called()
        t.assert.stub(manager.execute).was_not.called()

        build_stub:revert()
        list_stub:revert()
    end)

    it('should resolve sysbuild image before flashing', function()
        local config = {
            sysbuild = true,
            is_build = true,
            board = 'test_board',
            runner = 'test_runner',
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('mcuboot')
        t.lfs.mkdir('app')

        build_stub = t.stub(manager, 'build', function(_) end)
        manager.flash(config)

        t.assert.equal('app', config.image)
        t.assert.stub(manager.execute).was.called_with({
            'west flash',
            '--board test_board',
            '--domain app',
            '--runner test_runner',
        })

        build_stub:revert()
    end)

    it('should run selected sysbuild image', function()
        local config = {
            sysbuild = true,
            is_build = true,
            image = 2,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('mcuboot')
        t.lfs.mkdir('app')
        fixture.create_config('test_board')

        manager.run(config)

        t.assert.equal('mcuboot', config.image)
        t.assert.stub(manager.execute).was.called_with({ 'ninja run' })
        t.assert.is_true(t.lfs.currentdir():match('/build/mcuboot$') ~= nil)
    end)

    it('should list boards and return execute exit code', function()
        local execute_stub = t.stub(manager, 'execute', function(_)
            return 7
        end)
        print_stub = t.stub(_G, 'print')

        local exit_code = manager.boards({})

        t.assert.equal(7, exit_code)
        t.assert.stub(execute_stub).was.called_with({ 'west boards' })
        t.assert.stub(print_stub).was.called_with('\nAvailable boards:\n')
        execute_stub:revert()
    end)

    it('should list runners when zephyr_base is set', function()
        local config = {
            zephyr_base = t.tmp .. '/zephyr',
        }
        os.execute('mkdir -p ' .. config.zephyr_base .. '/scripts/west_commands/runners')
        t.helpers.write_file(config.zephyr_base .. '/scripts/west_commands/runners/foo.py', '')
        t.helpers.write_file(config.zephyr_base .. '/scripts/west_commands/runners/bar.py', '')
        t.helpers.write_file(config.zephyr_base .. '/scripts/west_commands/runners/__init__.py', '')
        print_stub = t.stub(_G, 'print')

        local exit_code = manager.runners(config)

        t.assert.equal(0, exit_code)
        t.assert.stub(print_stub).was.called_with('\nAvailable runners:\n')
        t.assert.stub(print_stub).was.called_with('bar')
        t.assert.stub(print_stub).was.called_with('foo')
    end)

    it('should fail listing runners when zephyr_base missing', function()
        local config = {
            zephyr_base = '',
        }

        local exit_code = manager.runners(config)

        t.assert.equal(1, exit_code)
    end)

    it('should list sysbuild images in sorted order', function()
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zeta')
        t.lfs.mkdir('alpha')
        t.lfs.mkdir('mcuboot')
        print_stub = t.stub(_G, 'print')

        manager.list_images()

        t.assert.stub(print_stub).was.called_with('\nAvailable sysbuild images:\n')
        t.assert.stub(print_stub).was.called_with('1: alpha')
        t.assert.stub(print_stub).was.called_with('2: mcuboot')
        t.assert.stub(print_stub).was.called_with('3: zeta')
    end)

    it('should return error when sysbuild image resolution fails for run', function()
        local config = {
            sysbuild = true,
            is_build = true,
            image = 3,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('app')
        t.lfs.mkdir('mcuboot')
        fixture.create_config('test_board')

        local exit_code = manager.run(config)

        t.assert.equal(1, exit_code)
    end)

    it('should return error when sysbuild image resolution fails for flash', function()
        local config = {
            sysbuild = true,
            is_build = true,
            image = 3,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('app')
        t.lfs.mkdir('mcuboot')
        fixture.create_config('test_board')
        build_stub = t.stub(manager, 'build', function(_) end)

        local exit_code = manager.flash(config)

        t.assert.equal(1, exit_code)
        build_stub:revert()
    end)

    it('should execute commands and return exit code', function()
        t.manager.revert()
        local real_manager = require('zync.api.manager')
        local exec_stub = t.stub(_G.os, 'execute', function(_)
            return false, 'exit', 5
        end)

        local exit_code = real_manager.execute({ 'echo', 'ok' })

        t.assert.equal(5, exit_code)

        exec_stub:revert()
        t.manager.load()
    end)

    it('should resolve project dir from build/profile path in init', function()
        local app_dir = t.tmp .. '/app'
        os.execute('mkdir -p ' .. app_dir .. '/build/sim')
        t.lfs.chdir(app_dir .. '/build/sim')

        manager.init({ is_build = true, board = 'test_board', runner = 'test_runner' })

        t.assert.is_truthy(t.lfs.currentdir():match('/app$') ~= nil)
        local content = t.helpers.read_file('zconf.lua')
        t.assert.is_truthy(content ~= '')

        os.execute('rm -f zconf.lua')
        t.lfs.chdir(t.tmp)
    end)

    it('should return early when build reconfigure fails', function()
        local config = {
            project_dir = t.tmp,
            board = 'board',
            runner = 'runner',
        }
        local call_count = 0
        local exec_stub = t.stub(manager, 'execute', function(_)
            call_count = call_count + 1
            return 7
        end)

        local exit_code = manager.build(config)

        t.assert.equal(7, exit_code)
        t.assert.equal(1, call_count)

        exec_stub:revert()
    end)

    it('should return early when flash build fails', function()
        local config = {
            board = 'board',
            runner = 'runner',
        }
        local exec_count = 0
        local build_stub = t.stub(manager, 'build', function(_)
            return 9
        end)
        local exec_stub = t.stub(manager, 'execute', function(_)
            exec_count = exec_count + 1
            return 0
        end)

        local exit_code = manager.flash(config)

        t.assert.equal(9, exit_code)
        t.assert.equal(0, exec_count)

        exec_stub:revert()
        build_stub:revert()
    end)

    it('should return early when run reconfigure fails', function()
        local config = {
            project_dir = t.tmp,
        }
        local call_count = 0
        local exec_stub = t.stub(manager, 'execute', function(_)
            call_count = call_count + 1
            return 6
        end)

        local exit_code = manager.run(config)

        t.assert.equal(6, exit_code)
        t.assert.equal(1, call_count)

        exec_stub:revert()
    end)

    it('should list sysbuild images when running with list flag', function()
        local config = {
            sysbuild = true,
            list = true,
            is_build = true,
        }
        local exec_count = 0
        config_stub = t.stub(manager, 'configure', function(_)
            return 0
        end)
        list_stub = t.stub(manager, 'list_images', function()
            return 0
        end)
        local exec_stub = t.stub(manager, 'execute', function(_)
            exec_count = exec_count + 1
            return 0
        end)

        local exit_code = manager.run(config)

        t.assert.equal(0, exit_code)
        t.assert.stub(list_stub).was.called()
        t.assert.equal(0, exec_count)

        exec_stub:revert()
        list_stub:revert()
        config_stub:revert()
    end)

    it('should return early when run build fails with existing binary', function()
        local config = {
            is_build = true,
        }
        t.lfs.mkdir('build')
        t.lfs.chdir('build')
        t.lfs.mkdir('zephyr')
        t.helpers.write_file('zephyr/zephyr.exe', '')
        fixture.create_config('test_board')

        local build_stub = t.stub(manager, 'build', function(_)
            return 8
        end)

        local exit_code = manager.run(config)

        t.assert.equal(8, exit_code)
        build_stub:revert()
    end)
end)
