require_relative "test_helper"
require "tmpdir"
require "fileutils"
require "json"

class ProjectIndexTest < Minitest::Test
  def with_index
    Dir.mktmpdir do |dir|
      path = File.join(dir, ".cc-os", "projects.json")
      yield Backlog::ProjectIndex.new(path: path, today: -> { "2026-07-13" }), path
    end
  end

  def test_missing_file_reads_as_empty
    with_index do |index, _path|
      assert_equal({}, index.all)
    end
  end

  def test_corrupt_file_reads_as_empty_and_is_not_clobbered_by_reads
    with_index do |index, path|
      FileUtils.mkdir_p(File.dirname(path))
      File.write(path, "{not json")
      assert_equal({}, index.all)
      assert_equal "{not json", File.read(path) # read paths never rewrite
    end
  end

  def test_upsert_creates_row_with_updated_at
    with_index do |index, path|
      index.upsert(path: "/repos/foo", name: "foo", tracker: "planka:foo", remote: nil)
      data = JSON.parse(File.read(path))
      assert_equal 1, data["version"]
      row = data["projects"]["/repos/foo"]
      assert_equal "foo", row["name"]
      assert_equal "planka:foo", row["tracker"]
      assert_nil row["remote"]
      assert_equal "2026-07-13", row["updated_at"]
    end
  end

  def test_upsert_updates_existing_row_keyed_by_path
    with_index do |index, _path|
      index.upsert(path: "/repos/foo", name: "foo", tracker: "planka:foo", remote: nil)
      index.upsert(path: "/repos/foo", name: "foo", tracker: "github:me/foo", remote: "r")
      assert_equal 1, index.all.size
      assert_equal "github:me/foo", index.all["/repos/foo"]["tracker"]
    end
  end

  def test_name_collision_two_paths_same_basename_keeps_both
    with_index do |index, _path|
      index.upsert(path: "/a/foo", name: "foo", tracker: "planka:foo", remote: nil)
      index.upsert(path: "/b/foo", name: "foo", tracker: "github:me/foo", remote: nil)
      assert_equal %w[/a/foo /b/foo], index.all.keys.sort
    end
  end

  def test_upsert_over_corrupt_file_leaves_valid_json_and_no_temp_files
    with_index do |index, path|
      FileUtils.mkdir_p(File.dirname(path))
      File.write(path, "{not json")
      index.upsert(path: "/repos/foo", name: "foo", tracker: "repo:.", remote: nil)
      data = JSON.parse(File.read(path))
      assert_equal ["/repos/foo"], data["projects"].keys
      leftovers = Dir.children(File.dirname(path)).reject { |f| f == "projects.json" }
      assert_empty leftovers, "atomic write left temp files: #{leftovers}"
    end
  end

  def test_default_path_honors_cc_os_home_override
    path = Backlog::ProjectIndex.default_path(env: { "CC_OS_HOME" => "/tmp/x", "HOME" => "/home/y" })
    assert_equal "/tmp/x/projects.json", path
  end

  def test_default_path_falls_back_to_home
    path = Backlog::ProjectIndex.default_path(env: { "HOME" => "/home/y" })
    assert_equal "/home/y/.cc-os/projects.json", path
  end
end

class ProjectsCliContractTest < Minitest::Test
  BIN = File.expand_path("../bin/os-backlog", __dir__)

  # config-write must upsert the index (redirected via CC_OS_HOME so the
  # real ~/.cc-os is never touched), and `projects` must always emit a
  # top-level "projects" key — even when the index is empty.
  def test_config_write_upserts_index_and_projects_lists_it
    Dir.mktmpdir do |home|
      Dir.mktmpdir do |repo|
        env = "CC_OS_HOME=#{home} HOME=#{home}"
        out = Dir.chdir(repo) { `#{env} ruby #{BIN} projects 2>/dev/null` }
        assert_equal({ "projects" => {} }, JSON.parse(out))

        Dir.chdir(repo) { `#{env} ruby #{BIN} config-write forgejo:jared/demo 2>/dev/null` }
        out = Dir.chdir(repo) { `#{env} ruby #{BIN} projects 2>/dev/null` }
        projects = JSON.parse(out)["projects"]
        assert_equal 1, projects.size
        row = projects.values.first
        assert_equal "forgejo:jared/demo", row["tracker"]
        assert_equal File.basename(File.realpath(repo)), row["name"]

        filtered = Dir.chdir(repo) { `#{env} ruby #{BIN} projects no-such-name 2>/dev/null` }
        assert_equal({}, JSON.parse(filtered)["projects"])

        by_tracker = Dir.chdir(repo) { `#{env} ruby #{BIN} projects forgejo:jared/demo 2>/dev/null` }
        assert_equal 1, JSON.parse(by_tracker)["projects"].size
      end
    end
  end

  # Umbrella repos: config-write from a subdirectory writes the config to
  # that subdirectory, so the index row must be keyed by the SUBDIRECTORY
  # (the project the config describes), never the enclosing git toplevel —
  # otherwise every subproject masquerades as the umbrella repo.
  def test_config_write_in_umbrella_subdir_indexes_the_subdir
    Dir.mktmpdir do |home|
      Dir.mktmpdir do |repo|
        `git -C #{repo} init -q 2>/dev/null`
        subdir = File.join(repo, "child-project")
        FileUtils.mkdir_p(subdir)
        env = "CC_OS_HOME=#{home} HOME=#{home}"

        Dir.chdir(subdir) { `#{env} ruby #{BIN} config-write forgejo:jared/child 2>/dev/null` }

        projects = Dir.chdir(subdir) { JSON.parse(`#{env} ruby #{BIN} projects 2>/dev/null`)["projects"] }
        assert_equal [File.realpath(subdir)], projects.keys
        assert_equal "child-project", projects.values.first["name"]
      end
    end
  end
end
