/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: Michael Terry
 */

using GLib;

void metadir_ignored()
{
  var tree = new DejaDup.FileTree();

  tree.add("random/file", FileType.REGULAR);

  // Current location
  tree.add(
    Path.build_filename(
      Environment.get_user_data_dir(),
      Config.PACKAGE,
      "metadata",
      "README"
    ),
    FileType.REGULAR
  );

  // Old location
  tree.add(
    Path.build_filename(
      Environment.get_user_cache_dir(),
      Config.PACKAGE,
      "metadata",
      "README"
    ),
    FileType.REGULAR
  );

  // Confirm we don't remove dirs with more content than we expect
  tree.add(
    Path.build_filename("multiple", Config.PACKAGE, "metadata", "README"),
    FileType.REGULAR
  );
  tree.add(
    Path.build_filename("multiple", Config.PACKAGE, "metadata", "hello.txt"),
    FileType.REGULAR
  );

  // Confirm that we start with 3 roots - /random, /multiple, and /home-root
  assert(tree.root.children.size() == 3);

  tree.finish();

  // OK confirm the tree filters out the home entry
  assert(tree.root.children.size() == 2);
  assert(tree.root.children.contains("multiple"));
  assert(tree.root.children.contains("random"));
}

void next_backup()
{
  var settings = DejaDup.get_settings();
  settings.set_int(DejaDup.PERIODIC_PERIOD_KEY, 3);

  // No backup yet (next will be now)
  var next = DejaDup.next_possible_run_date();
  assert(next.format_iso8601() == DejaDup.now().format_iso8601());

  // With a previous backup (next will be 3 days later).
  // The nearly-4AM bit is because that's the "midnight" fuzz we get with
  // the unit test random seed.
  settings.set_string(DejaDup.LAST_BACKUP_KEY, "2025-01-05T01:34:15Z");
  next = DejaDup.next_possible_run_date();
  assert(next.format_iso8601() == "2025-01-07T03:59:39.730619-10");

  // Badly specified period (will use 1 day)
  settings.set_int(DejaDup.PERIODIC_PERIOD_KEY, -3);
  next = DejaDup.next_possible_run_date();
  assert(next.format_iso8601() == "2025-01-05T03:59:39.730619-10");
}

void late_timestamp()
{
  var settings = DejaDup.get_settings();

  // Nothing set
  assert(DejaDup.get_late_timespan() == 0);

  settings.set_boolean(DejaDup.PERIODIC_KEY, true);
  settings.set_int(DejaDup.PERIODIC_PERIOD_KEY, 3);

  // No timestamps set
  assert(DejaDup.get_late_timespan() == 0);

  // Just periodic timestamp (target is immediately, so any delay is late)
  // Remember: "now" for unit tests is 2023-08-23T15:15:15-10
  settings.set_string(DejaDup.PERIODIC_TIMESTAMP_KEY, "2023-08-19T01:11:15-10");
  var expected_delta = 4 * TimeSpan.DAY + 14 * TimeSpan.HOUR + 4 * TimeSpan.MINUTE;
  assert(DejaDup.get_late_timespan() == expected_delta);

  // Both a backup date and a periodic timestamp, with way older backup.
  // This shouldn't change things.
  settings.set_string(DejaDup.LAST_BACKUP_KEY, "2023-08-14T00:00:00-10");
  assert(DejaDup.get_late_timespan() == expected_delta);

  // Now try a backup date older than the periodic timestamp, but the target
  // date being after the periodic timestamp (which is the date we'll use).
  // The target date will use a "midnight" fuzzy time of 03:59:39.730619
  // because of the unit test random seed.
  settings.set_string(DejaDup.LAST_BACKUP_KEY, "2023-08-18T00:00:00-10");
  expected_delta = 2 * TimeSpan.DAY + 11 * TimeSpan.HOUR + 15 * TimeSpan.MINUTE + 35 * TimeSpan.SECOND + 269381;
  assert(DejaDup.get_late_timespan() == expected_delta);

  // Just for completeness, test what happens when only the backup date is set
  // and not a periodic timestamp (though we ensure there is one on startup).
  // We should just be looking at the backup date.
  // Delta will be from the 17th at nearly 4AM.
  settings.set_string(DejaDup.LAST_BACKUP_KEY, "2023-08-14T00:00:00-10");
  settings.set_string(DejaDup.PERIODIC_TIMESTAMP_KEY, "");
  expected_delta += 4 * TimeSpan.DAY;
  assert(DejaDup.get_late_timespan() == expected_delta);
}

void parse_one_dir (string to_parse, string? result)
{
  if (result != null)
    assert(DejaDup.parse_dir(to_parse).equal(File.new_for_path(result)));
}

void parse_dir()
{
  parse_one_dir("", Environment.get_home_dir());
  parse_one_dir("$HOME", Environment.get_home_dir());
  parse_one_dir("$TRASH", DejaDup.InstallEnv.instance().get_trash_dir());
  parse_one_dir("$DESKTOP", Environment.get_user_special_dir(UserDirectory.DESKTOP));
  parse_one_dir("$DOCUMENTS", Environment.get_user_special_dir(UserDirectory.DOCUMENTS));
  parse_one_dir("$DOWNLOAD", Environment.get_user_special_dir(UserDirectory.DOWNLOAD));
  parse_one_dir("$MUSIC", Environment.get_user_special_dir(UserDirectory.MUSIC));
  parse_one_dir("$PICTURES", Environment.get_user_special_dir(UserDirectory.PICTURES));
  parse_one_dir("$PUBLIC_SHARE", Environment.get_user_special_dir(UserDirectory.PUBLIC_SHARE));
  parse_one_dir("$TEMPLATES", Environment.get_user_special_dir(UserDirectory.TEMPLATES));
  parse_one_dir("/backup/$USER", Path.build_filename("/backup", Environment.get_user_name()));
  parse_one_dir("backup/$USER", Path.build_filename(Environment.get_home_dir(), "backup", Environment.get_user_name()));
  parse_one_dir("$VIDEOS", Environment.get_user_special_dir(UserDirectory.VIDEOS));
  parse_one_dir("VIDEOS", Path.build_filename(Environment.get_home_dir(), "VIDEOS"));
  parse_one_dir("/VIDEOS", "/VIDEOS");
  parse_one_dir("file:///VIDEOS", "/VIDEOS");
  assert(DejaDup.parse_dir("file:VIDEOS").equal(File.parse_name("file:VIDEOS")));
}

void parse_one_version(string str, int maj, int min, int mic)
{
  int pmaj, pmin, pmic;
  assert(DejaDup.parse_version(str, out pmaj, out pmin, out pmic));
  assert(pmaj == maj);
  assert(pmin == min);
  assert(pmic == mic);
}

void parse_bad_version(string str)
{
  int pmaj, pmin, pmic;
  assert(!DejaDup.parse_version(str, out pmaj, out pmin, out pmic));
  assert(pmaj == 0);
  assert(pmin == 0);
  assert(pmic == 0);
}

void parse_version()
{
  parse_bad_version("");
  parse_one_version("a", 0, 0, 0);
  parse_one_version("1", 1, 0, 0);
  parse_one_version("1.2", 1, 2, 0);
  parse_one_version("1.2.3", 1, 2, 3);
  parse_one_version("1.2.3.4", 1, 2, 3);
  parse_one_version("1.2.3a4", 1, 2, 3);
  parse_one_version("1.2a3.4", 1, 2, 4);
  parse_one_version("1.2 3.4", 1, 2, 4);
  parse_one_version("1.2-3.4", 1, 2, 4);
}

void prompt()
{
  var settings = DejaDup.get_settings();

  settings.set_string(DejaDup.PROMPT_CHECK_KEY, "");
  DejaDup.update_prompt_time(true);
  assert(settings.get_string(DejaDup.PROMPT_CHECK_KEY) == "disabled");

  assert(DejaDup.make_prompt_check() == false);
  assert(settings.get_string(DejaDup.PROMPT_CHECK_KEY) == "disabled");
  DejaDup.update_prompt_time(); // shouldn't change anything
  assert(settings.get_string(DejaDup.PROMPT_CHECK_KEY) == "disabled");

  settings.set_string(DejaDup.PROMPT_CHECK_KEY, "");
  assert(DejaDup.make_prompt_check() == false);
  var time_now = settings.get_string(DejaDup.PROMPT_CHECK_KEY);
  assert(time_now != "");
  assert(DejaDup.make_prompt_check() == false);
  assert(settings.get_string(DejaDup.PROMPT_CHECK_KEY) == time_now);

  var cur_time = DejaDup.now();
  cur_time = cur_time.add_seconds(-1 * DejaDup.get_prompt_delay());
  cur_time = cur_time.add_hours(1);
  settings.set_string(DejaDup.PROMPT_CHECK_KEY, cur_time.format("%Y-%m-%dT%H:%M:%S%z"));
  assert(DejaDup.make_prompt_check() == false);

  cur_time = cur_time.add_hours(-2);
  settings.set_string(DejaDup.PROMPT_CHECK_KEY, cur_time.format("%Y-%m-%dT%H:%M:%S%z"));
  assert(DejaDup.make_prompt_check() == true);
}

void tool_migration()
{
  DejaDup.get_settings().set_string("tool", "restic");
  DejaDup.get_settings("Google").set_string("folder", "/root/");
  DejaDup.get_settings("Local").set_string("folder", "");

  DejaDup.initialize();

  assert(DejaDup.get_settings().get_string("tool") == "migrated");
  assert(DejaDup.get_settings().get_string("tool-when-new") == "restic");
  assert(DejaDup.get_settings("Drive").get_string("folder") == "$HOSTNAME/restic");
  assert(DejaDup.get_settings("Google").get_string("folder") == "/root/restic");
  assert(DejaDup.get_settings("Local").get_string("folder") == "restic");
  assert(DejaDup.get_settings("Microsoft").get_string("folder") == "$HOSTNAME/restic");
  assert(DejaDup.get_settings("Remote").get_string("folder") == "$HOSTNAME/restic");
}

async void tool_detect() throws Error
{
  var root = DirUtils.make_tmp("deja-XXXXXX");
  DejaDup.get_settings("Local").set_string("folder", root);

  var backend = new DejaDup.BackendLocal(null);
  yield backend.prepare();

  DejaDup.ToolPlugin plugin;

  // check that the detected tool on an empty folder is null
  plugin = yield DejaDup.get_tool_for_backend(backend);
  assert(plugin == null);

  // is the default tool correct?
  plugin = DejaDup.get_default_tool();
  assert(plugin.name == "restic");

  // can adjust the tool for empty folders
  DejaDup.get_settings().set_string("tool-when-new", "restic");
  plugin = DejaDup.get_default_tool();
  assert(plugin.name == "restic");

  DejaDup.get_settings().set_string("tool-when-new", "duplicity");
  plugin = DejaDup.get_default_tool();
  assert(plugin.name == "duplicity");

  // can detect duplicity files
  DejaDup.get_settings("Local").set_string("folder", root + "/dup");
  yield backend.prepare();
  FileUtils.set_contents(root + "/dup/bogus-extra-file", ""); // ignored
  FileUtils.set_contents(root + "/dup/.bogus-hidden-file", ""); // ignored
  FileUtils.set_contents(root + "/dup/duplicity-longname.vol1.gpg", "");
  DirUtils.create(root + "/dup/restic", 0700); // confirm this doesn't confuse us
  plugin = yield DejaDup.get_tool_for_backend(backend);
  assert(plugin.name == "duplicity");

  // can detect restic files
  DejaDup.get_settings("Local").set_string("folder", root + "/rest");
  yield backend.prepare();
  FileUtils.set_contents(root + "/rest/config", "");
  DirUtils.create(root + "/rest/data", 0700);
  DirUtils.create(root + "/rest/index", 0700);
  DirUtils.create(root + "/rest/keys", 0700);
  DirUtils.create(root + "/rest/snapshots", 0700);
  DirUtils.create(root + "/rest/.bogus-hidden-dir", 0700); // ignored
  DirUtils.create(root + "/rest/bogus-extra-dir", 0700); // ignored
  plugin = yield DejaDup.get_tool_for_backend(backend);
  assert(plugin.name == "restic");

  // if both exist, we go restic
  DejaDup.get_settings("Local").set_string("folder", root + "/both");
  yield backend.prepare();
  FileUtils.set_contents(root + "/both/bogus-extra-file", ""); // ignored
  FileUtils.set_contents(root + "/both/config", "");
  FileUtils.set_contents(root + "/both/duplicity-longname.vol1.gpg", "");
  DirUtils.create(root + "/both/data", 0700);
  DirUtils.create(root + "/both/index", 0700);
  DirUtils.create(root + "/both/keys", 0700);
  DirUtils.create(root + "/both/snapshots", 0700);
  plugin = yield DejaDup.get_tool_for_backend(backend);
  assert(plugin.name == "restic");

  // errors out with unknown files
  DejaDup.get_settings("Local").set_string("folder", root + "/bogus");
  yield backend.prepare();
  FileUtils.set_contents(root + "/bogus/hello.txt", "");
  try {
    yield DejaDup.get_tool_for_backend(backend);
    assert(false);
  } catch (OptionError.FAILED err) {
    assert(err.message == "Unrecognized file in storage location: 'hello.txt'. Choose an empty folder instead.");
  }
}

void wrap_tool_detect()
{
  var loop = new MainLoop();
  tool_detect.begin((obj, res) => {
    try {
      tool_detect.end(res);
    } catch (Error err) {
      warning("%s", err.message);
      assert(false);
    }
    loop.quit();
  });
  loop.run();
}

string get_srcdir()
{
  var srcdir = Environment.get_variable("srcdir");
  if (srcdir == null)
    srcdir = ".";
  return srcdir;
}

void setup()
{
}

void reset_keys(Settings settings)
{
  var source = SettingsSchemaSource.get_default();
  var schema = source.lookup(settings.schema_id, true);

  foreach (string key in schema.list_keys())
    settings.reset(key);

  foreach (string child in schema.list_children())
    reset_keys(settings.get_child(child));
}

void teardown()
{
  reset_keys(new Settings(Config.APPLICATION_ID));
}

int main(string[] args)
{
  Test.init(ref args);

  Environment.set_variable("PATH",
                           get_srcdir() + "/../mock:" +
                             Environment.get_variable("PATH"),
                           true);

  var unit = new TestSuite("libdeja");
  unit.add(new TestCase("late-timestamp", setup, late_timestamp, teardown));
  unit.add(new TestCase("metadir-ignored", setup, metadir_ignored, teardown));
  unit.add(new TestCase("next-backup", setup, next_backup, teardown));
  unit.add(new TestCase("parse-dir", setup, parse_dir, teardown));
  unit.add(new TestCase("parse-version", setup, parse_version, teardown));
  unit.add(new TestCase("prompt", setup, prompt, teardown));
  unit.add(new TestCase("tool-migration", setup, tool_migration, teardown));
  unit.add(new TestCase("tool-detect", setup, wrap_tool_detect, teardown));
  TestSuite.get_root().add_suite((owned)unit);

  return Test.run();
}
