do
    local spec_dir = debug.getinfo(1, 'S').source:match('@?(.*/)')
    if spec_dir and not spec_dir:match('^/') then
        spec_dir = require('lfs').currentdir() .. '/' .. spec_dir
    end
    local root = spec_dir:gsub('/spec/?$', '')
    package.path = root .. '/lua/?.lua;' .. root .. '/lua/?/init.lua;' .. package.path
end

for name in pairs(package.loaded) do
    if name == 'zync' or name:match('^zync%.') then
        package.loaded[name] = nil
    end
end

local t = {
    assert = require('luassert'),
    stub = require('luassert.stub'),
    lfs = require('lfs'),
    manager = require('zync.api.manager'),
    helpers = {},
    fixtures = {},
}

local inspect = require('inspect').inspect
function t.inspect(any)
    print(inspect(any))
end

t.fixtures = {
    utils = require('spec.fixtures.utils_fix'),
}

local exit_stub
t.exit_handler = nil
t.og_globals = {}

function t.manager.load()
    t.manager['execute'] = t.stub(t.manager, 'execute', function(_) end)
    package.loaded['zync.api.manager'] = t.manager
end

function t.manager.revert()
    package.loaded['zync.api.manager'] = nil
    t.manager['execute']:revert()
end

t.manager.load()

function t.helpers.setup()
    t.manager.load()
    t.og_dir = t.lfs.currentdir()
    t.tmp = '/tmp/zync_test_' .. os.time()
    os.execute('mkdir -p ' .. t.tmp)
    t.lfs.chdir(t.tmp)
    t.og_globals.ZYNC_TESTING = _G.ZYNC_TESTING
    t.og_globals.ZYNC_LOGS = _G.ZYNC_LOGS
    t.og_globals.ARG = _G.arg
    _G.ZYNC_TESTING = true
    _G.ZYNC_LOGS = false
    _G.arg = {}
    exit_stub = t.stub(_G.os, 'exit', function(...)
        if t.exit_handler then
            return t.exit_handler(...)
        end
    end)
end

function t.helpers.teardown()
    t.exit_handler = nil
    exit_stub:revert()
    t.lfs.chdir(t.og_dir)
    t.manager.revert()
    os.execute('rm -rf ' .. t.tmp)
    _G.ZYNC_TESTING = t.og_globals.ZYNC_TESTING
    _G.ZYNC_LOGS = t.og_globals.ZYNC_LOGS
    _G.arg = t.og_globals.ARG
end

function t.helpers.read_file(path)
    local file = io.open(path, 'r')
    if file then
        local content = file:read('*a')
        file:close()
        return content
    end

    return ''
end

function t.helpers.write_file(path, content)
    local file = io.open(path, 'w')
    if file ~= nil then
        file:write(content)
        file:close()
    end
end

function t.helpers.dir_exists(path, bool)
    local res = t.lfs.attributes(path, 'mode') == 'directory'
    if bool and bool == true then
        t.assert.is_true(res)
    else
        t.assert.is_false(res)
    end
end

function t.helpers.spec_root()
    local path = debug.getinfo(1).source:match('@?(.*/)')
    local test_dir = path:match('(.*/spec/)')
    return test_dir and test_dir:gsub('/spec/$', '')
end

function t.helpers.array_contains(array, value)
    for _, val in ipairs(array) do
        if val == value then
            return true
        end
    end

    return false
end

function t.helpers.tables_equal(t1, t2)
    if t1 == t2 then
        return true
    end

    if type(t1) ~= 'table' or type(t2) ~= 'table' then
        return false
    end

    for k, v in pairs(t1) do
        if not t.helpers.tables_equal(v, t2[k]) then
            return false
        end
    end
    for k in pairs(t2) do
        if t1[k] == nil then
            return false
        end -- Extra keys in t2
    end

    return true
end

function t.helpers.table_contains(tab, key)
    if tab and next(tab) then
        if tab[key] then
            return true
        end
    end

    return false
end

function t.helpers.assert_tables_equal(t1, t2)
    local res = t.helpers.tables_equal(t1, t2)
    if not res then
        print('Passed in:')
        print(require('inspect').inspect(t1))
        print('Expected:')
        print(require('inspect').inspect(t2))
    end

    t.assert.is_true(res)
end

return t
