local createLibTest = require('/apis/libtest');

local testlib = createLibTest({ ... });

local function parentDir(path)
  return path:match('^(.*)/[^/]+$');
end

local function scopedPath(root, path)
  if path:sub(1, 1) == '/' then
    return root .. path;
  end
  return root .. '/' .. path;
end

local function fakeFs(root)
  return {
    exists = function(path)
      return fs.exists(scopedPath(root, path));
    end,
    makeDir = function(path)
      fs.makeDir(scopedPath(root, path));
    end,
    open = function(path, mode)
      return fs.open(scopedPath(root, path), mode);
    end,
    delete = function(path)
      fs.delete(scopedPath(root, path));
    end,
  };
end

local function fakeHttp(routes)
  return {
    get = function(url)
      local body = routes[url];
      if not body then return nil; end
      return {
        readAll = function() return body; end,
        close = function() end,
      };
    end,
  };
end

local INSTALL_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/';

local function installRoutes()
  return {
    [INSTALL_BASE .. 'manifest.json'] = textutils.serializeJSON({ name = 'TrapOS', version = 'test' }),
    [INSTALL_BASE .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({
      name = 'trapos-core',
      version = '1',
      dependencies = {},
      files = { 'apis/libccpm.lua', 'programs/ccpm.lua' },
    }),
    [INSTALL_BASE .. 'packages/trapos-core/apis/libccpm.lua'] = 'libccpm-body',
    [INSTALL_BASE .. 'packages/trapos-core/programs/ccpm.lua'] = 'ccpm-body',
  };
end

-- Run install-trapos.lua inside a isolated env. `args` is a list of CLI args.
-- Returns { calls, root } for assertions.
local function runInstall(root, args)
  fs.delete(root);
  fs.makeDir(root);

  local routes = installRoutes();
  local calls = {
    executes = {},
    path = '/rom/programs',
    dir = '/',
    rebooted = false,
  };
  local fsApi = fakeFs(root);
  local env = setmetatable({
    fs = fsApi,
    http = fakeHttp(routes),
    os = {
      reboot = function()
        calls.rebooted = true;
        calls.executes[#calls.executes + 1] = { program = '__reboot__', args = {} };
      end,
    },
    print = function() end,
    read = function() return 'yes'; end,
    write = function() end,
    shell = {
      dir = function() return calls.dir; end,
      setDir = function(path) calls.dir = path; end,
      path = function() return calls.path; end,
      setPath = function(path) calls.path = path; end,
      execute = function(program, ...)
        local rest = { ... };
        calls.executes[#calls.executes + 1] = { program = program, args = rest };
        if program ~= 'wget' then return true; end
        local url, target = rest[1], rest[2];
        local body = routes[url];
        if not body then return false; end
        local dir = parentDir(target);
        if dir then fsApi.makeDir(dir); end
        local f = fsApi.open(target, 'w');
        f.write(body);
        f.close();
        return true;
      end,
    },
  }, { __index = _G });

  local chunk, loadErr = loadfile('/trapos/install-trapos.lua', 't', env);
  if not chunk then error(loadErr, 0); end
  local ok, err = pcall(chunk, table.unpack(args));
  if not ok then error(err, 0); end

  return calls;
end

local function findExecute(calls, program, firstArg)
  for _, call in ipairs(calls.executes) do
    if call.program == program then
      if firstArg == nil or call.args[1] == firstArg then
        return true;
      end
    end
  end
  return false;
end

local function findExecuteIndex(calls, program, firstArg)
  for i, call in ipairs(calls.executes) do
    if call.program == program then
      if firstArg == nil or call.args[1] == firstArg then
        return i;
      end
    end
  end
  return nil;
end

testlib.test('programs path resolves the ccpm command', function()
  local previous = shell.path();
  shell.setPath('/rom/programs:/programs');
  local resolved = shell.resolveProgram('ccpm');
  shell.setPath(previous);
  testlib.assertEquals(resolved, 'programs/ccpm.lua');
end);

testlib.test('fresh bootstrap seeds a gitea master registry and runs ccpm', function()
  local root = '/install-trapos-test/full';
  local calls = runInstall(root, {});

  testlib.assertTrue(fs.exists(root .. '/programs/ccpm.lua'));
  testlib.assertTrue(string.find(calls.path, '/programs', 1, true));

  local f = fs.open(root .. '/trapos/ccpm.json', 'r');
  local config = textutils.unserializeJSON(f.readAll());
  f.close();
  testlib.assertEquals(config.registries[1].branch, 'master');
  testlib.assertEquals(config.registries[1].type, 'gitea');

  f = fs.open(root .. '/trapos/ccpm.lock.json', 'r');
  local lock = textutils.unserializeJSON(f.readAll());
  f.close();
  testlib.assertEquals(lock.packages['trapos-core'].explicit, false);
end);

testlib.test('default install runs update, install trapos and postinstall', function()
  local root = '/install-trapos-test/default';
  local calls = runInstall(root, {});

  testlib.assertTrue(findExecute(calls, 'ccpm', 'update'));
  testlib.assertTrue(findExecute(calls, 'ccpm', 'install'));
  testlib.assertTrue(findExecute(calls, 'trapos-postinstall'));
end);

testlib.test('default install runs postinstall before reboot', function()
  local root = '/install-trapos-test/order';
  local calls = runInstall(root, {});
  local postinstallIndex = findExecuteIndex(calls, 'trapos-postinstall');
  local rebootIndex = findExecuteIndex(calls, '__reboot__');

  testlib.assertTrue(postinstallIndex ~= nil, 'postinstall was not run');
  testlib.assertTrue(rebootIndex ~= nil, 'reboot was not run');
  testlib.assertTrue(postinstallIndex < rebootIndex, 'postinstall must run before reboot');
  testlib.assertEquals(calls.rebooted, true);
end);

testlib.test('--cpm-only stops after the ccpm bootstrap', function()
  local root = '/install-trapos-test/cpm-only';
  local calls = runInstall(root, { '--cpm-only' });

  testlib.assertTrue(fs.exists(root .. '/programs/ccpm.lua'));
  testlib.assertTrue(not findExecute(calls, 'ccpm', 'update'));
  testlib.assertTrue(not findExecute(calls, 'ccpm', 'install'));
  testlib.assertTrue(not findExecute(calls, 'trapos-postinstall'));
  testlib.assertEquals(calls.rebooted, false);

  local f = fs.open(root .. '/trapos/ccpm.lock.json', 'r');
  local lock = textutils.unserializeJSON(f.readAll());
  f.close();
  testlib.assertEquals(lock.packages['trapos-core'].explicit, true);
end);

testlib.run();
