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

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

local PACKAGES_DIR = '/trapos/packages';
local INDEX_PATH = PACKAGES_DIR .. '/index.json';

local function readJson(path)
  local file = fs.open(path, 'r');
  testlib.assertTrue(file, 'missing JSON file: ' .. path);
  local body = file.readAll();
  file.close();
  local value = textutils.unserializeJSON(body);
  testlib.assertTrue(value, 'invalid JSON file: ' .. path);
  return value;
end

local function sortedKeys(map)
  local keys = {};
  for key in pairs(map or {}) do
    keys[#keys + 1] = key;
  end
  table.sort(keys);
  return keys;
end

local function descriptorPaths()
  local paths = {};
  for _, name in ipairs(fs.list(PACKAGES_DIR)) do
    local dir = PACKAGES_DIR .. '/' .. name;
    local path = dir .. '/ccpm.json';
    if fs.isDir(dir) and fs.exists(path) then
      paths[#paths + 1] = { name = name, path = path };
    end
  end
  table.sort(paths, function(a, b) return a.name < b.name; end);
  return paths;
end

local function packageMap()
  local packages = {};
  for _, entry in ipairs(descriptorPaths()) do
    packages[entry.name] = readJson(entry.path);
  end
  return packages;
end

local function assertSemver(version, label)
  testlib.assertTrue(type(version) == 'string', label .. ' version must be a string');
  testlib.assertTrue(string.match(version, '^%d+%.%d+%.%d+$') ~= nil, label .. ' version must be x.y.z');
end

testlib.test('package index matches descriptors', function()
  local index = readJson(INDEX_PATH);
  local packages = packageMap();
  local indexNames = sortedKeys(index.packages);
  local descriptorNames = sortedKeys(packages);

  testlib.assertEquals(#indexNames, #descriptorNames, 'index and descriptor counts differ');
  for i, name in ipairs(descriptorNames) do
    testlib.assertEquals(indexNames[i], name, 'index package names differ');
    testlib.assertEquals(index.packages[name], packages[name].version, name .. ' index version differs');
  end
end);

testlib.test('package descriptors are internally consistent', function()
  local packages = packageMap();
  for name, desc in pairs(packages) do
    testlib.assertEquals(desc.name, name, name .. ' descriptor name differs from directory');
    assertSemver(desc.version, name);
    testlib.assertTrue(type(desc.dependencies) == 'table', name .. ' dependencies must be a table');
    testlib.assertTrue(type(desc.files) == 'table', name .. ' files must be a table');
    testlib.assertTrue(type(desc.autostart) == 'table', name .. ' autostart must be a table');

    if name ~= 'trapos' then
      testlib.assertTrue(#desc.files > 0, name .. ' must ship at least one file');
    end

    for _, dependency in ipairs(desc.dependencies) do
      testlib.assertTrue(packages[dependency] ~= nil, name .. ' depends on unknown package ' .. dependency);
    end

    local files = {};
    for _, filePath in ipairs(desc.files) do
      files[filePath] = true;
      testlib.assertTrue(fs.exists(PACKAGES_DIR .. '/' .. name .. '/' .. filePath), name .. ' missing shipped file ' .. filePath);
    end

    for _, autostart in ipairs(desc.autostart) do
      testlib.assertTrue(files[autostart .. '.lua'], name .. ' autostart not listed in files: ' .. autostart);
    end
  end
end);

testlib.test('root manifest tracks trapos package version', function()
  local manifest = readJson('/trapos/manifest.json');
  local trapos = readJson(PACKAGES_DIR .. '/trapos/ccpm.json');
  testlib.assertEquals(manifest.version, trapos.version);
end);

testlib.run();
