local createCloud = require('/apis/libcloud');
local createVersion = require('/apis/libversion');
local createEventLoop = require('/apis/eventloop');

local URL_SETTING = 'cloud.url';
local PASSWORD_SETTING = 'cloud.password';
local RECONNECT_EVENT = 'trapos_cloud_reconnect';
local VERIFY_TIMEOUT = 12; -- one hello attempt; daemon helloTimeout is 10s

local cloudApi = createCloud();

local rawArgs = table.pack(...);
local command = rawArgs[1];

local function isBlank(value)
  return type(value) ~= 'string' or value == '';
end

local function printUsage()
  print('cloud usage:');
  print();
  print('  cloud status');
  print('  cloud login [--force|-f] [password]');
  print('  cloud --version');
  print('  cloud --help');
  print();
  print('settings required:');
  print('  cloud.url       (gateway origin, e.g. ws://host:4444)');
  print('settings optional:');
  print('  cloud.password  (shared secret for the hello handshake)');
end

local function readPassword()
  write('Password: ');
  if type(read) == 'function' then return read('*'); end
  if io and type(io.read) == 'function' then return io.read(); end
  return '';
end

local function parseLoginArgs(args)
  local force = false;
  local password = nil;

  for i = 2, args.n do
    local arg = args[i];
    if arg == '--force' or arg == '-f' then
      force = true;
    elseif arg and string.sub(arg, 1, 1) == '-' then
      return nil, 'unknown option: ' .. arg;
    elseif not password then
      password = arg;
    else
      return nil, 'unexpected argument: ' .. tostring(arg);
    end
  end

  return { force = force, password = password }, nil;
end

if command == '--version' or command == '-version' or command == 'version' or command == '-v' then
  print('v' .. createVersion().forSelf());
  return;
end

if command == '--help' or command == '-help' or command == 'help' or command == '-h' then
  printUsage();
  return;
end

if command == 'status' then
  local url = settings.get(URL_SETTING);
  if isBlank(url) then
    print('set cloud.url first');
    return;
  end

  local ok, result = cloudApi.request('probe_server');
  if not ok then
    local code = (type(result) == 'table' and result.code) or 'internal';
    local message = (type(result) == 'table' and result.message) or '';
    print('cloud status failed: ' .. tostring(code) .. ' ' .. tostring(message));
    return;
  end

  print('serverOk: ' .. tostring(result.serverOk));
  print('opencodeOk: ' .. tostring(result.opencodeOk));
  if not result.opencodeOk and result.opencodeDetail then
    print('opencode: ' .. tostring(result.opencodeDetail));
  end
  return;
end

if command == 'login' then
  local opts, parseError = parseLoginArgs(rawArgs);
  if parseError then
    print(parseError);
    printUsage();
    return;
  end

  if not opts.force and not isBlank(settings.get(PASSWORD_SETTING)) then
    print('cloud.password is already set; use cloud login --force to replace it');
    return;
  end

  if isBlank(settings.get(URL_SETTING)) then
    print('set cloud.url first');
    return;
  end

  -- Ask the daemon to reconnect with `candidate` (never persisted) and wait for the handshake
  -- verdict carrying our token. Returns 'ok' | 'unauthorized' | 'timeout' | 'terminate'. Other
  -- daemon verdicts (tokenless or for a different token) are ignored.
  local function verifyPassword(candidate)
    local token = cloudApi.uuid();
    local result = nil;
    local el = createEventLoop();

    local function finish(value)
      if result ~= nil then return; end
      result = value;
      el.stopLoop();
    end

    el.register('trapos_cloud_connected', function(_traposId, eventToken)
      if eventToken == token then finish('ok'); end
    end);
    el.register('trapos_cloud_unauthorized', function(_traposId, _err, eventToken)
      if eventToken == token then finish('unauthorized'); end
    end);
    -- terminate stops the default loop on its own; just record it for the caller.
    el.register('terminate', function() result = 'terminate'; end);
    el.setTimeout(function() finish('timeout'); end, VERIFY_TIMEOUT);

    os.queueEvent(RECONNECT_EVENT, 'login-verify', candidate, token);
    el.runLoop();
    return result;
  end

  local function restore()
    os.queueEvent(RECONNECT_EVENT, 'login-restore');
  end

  -- Persist only after a verified password. Roll the live setting back if the save fails so the
  -- daemon (which reads live settings) never keeps an unsaved secret. No reconnect on success:
  -- the live socket already authenticated with this secret.
  local function savePassword(password)
    local old = settings.get(PASSWORD_SETTING);
    settings.set(PASSWORD_SETTING, password);
    if not settings.save() then
      if old ~= nil then
        settings.set(PASSWORD_SETTING, old);
      else
        settings.unset(PASSWORD_SETTING);
      end
      restore();
      error('cloud login failed: could not save settings', 0);
    end
    print('logged in');
  end

  if opts.password == nil then
    -- Interactive: re-prompt on a wrong password; ctrl+t cancels.
    while true do
      local candidate = readPassword();
      if isBlank(candidate) then
        print('password cannot be empty');
      else
        local result = verifyPassword(candidate);
        if result == 'ok' then
          savePassword(candidate);
          return;
        elseif result == 'unauthorized' then
          restore();
          print('invalid password, try again (ctrl+t to cancel)');
        elseif result == 'timeout' then
          restore();
          print('cloud login failed: could not reach gateway');
          return;
        else -- terminate
          restore();
          error('Terminated', 0);
        end
      end
    end
  end

  -- Non-interactive: a wrong password fails with a non-zero exit.
  if isBlank(opts.password) then
    error('cloud login failed: password cannot be empty', 0);
  end

  local result = verifyPassword(opts.password);
  if result == 'ok' then
    savePassword(opts.password);
    return;
  elseif result == 'unauthorized' then
    restore();
    error('cloud login failed: invalid password', 0);
  elseif result == 'timeout' then
    restore();
    error('cloud login failed: could not reach gateway', 0);
  else -- terminate
    restore();
    error('Terminated', 0);
  end
end

printUsage();
