require_relative "test_helper"
require "tmpdir"

class FakeIssuesRunner
  attr_reader :calls

  def initialize(responses)
    @responses = responses.dup
    @calls = []
  end

  def capture(*cmd)
    @calls << cmd
    @responses.shift || ["", true]
  end
end

class IssuesCreateTest < Minitest::Test
  def test_forgejo_create_shells_out_to_tea_with_priority_label
    runner = FakeIssuesRunner.new([["Created issue #7: https://forgejo.example/jared/cc-os/issues/7\n", true]])
    issues = Backlog::Issues.new(runner: runner)

    result = issues.create(tracker: "forgejo:jared/cc-os", title: "Fix the thing", body: "context here", priority: "P1")

    assert_equal 7, result[:number]
    assert_equal "https://forgejo.example/jared/cc-os/issues/7", result[:url]
    cmd = runner.calls.first
    assert_equal "tea", cmd[0]
    assert_includes cmd, "--repo"
    assert_includes cmd, "jared/cc-os"
    assert_includes cmd, "--title"
    assert_includes cmd, "Fix the thing"
    assert_includes cmd, "--labels"
    assert_includes cmd, "P1"
  end

  def test_forgejo_create_omits_labels_flag_without_priority
    runner = FakeIssuesRunner.new([["Created issue #1: https://x/issues/1\n", true]])
    Backlog::Issues.new(runner: runner).create(tracker: "forgejo:jared/cc-os", title: "T", body: "B")

    refute_includes runner.calls.first, "--labels"
  end

  def test_github_create_shells_out_to_gh
    runner = FakeIssuesRunner.new([["https://github.com/acme/widget/issues/42\n", true]])
    issues = Backlog::Issues.new(runner: runner)

    result = issues.create(tracker: "github:acme/widget", title: "Fix", body: "ctx", priority: "P0")

    assert_equal 42, result[:number]
    assert_equal "https://github.com/acme/widget/issues/42", result[:url]
    cmd = runner.calls.first
    assert_equal "gh", cmd[0]
    assert_includes cmd, "--label"
    assert_includes cmd, "P0"
  end

  def test_create_raises_command_failed_on_nonzero_exit
    runner = FakeIssuesRunner.new([["auth error", false]])
    issues = Backlog::Issues.new(runner: runner)

    assert_raises(Backlog::Issues::CommandFailed) do
      issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B")
    end
  end

  def test_create_rejects_invalid_priority
    issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))

    assert_raises(ArgumentError) do
      issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B", priority: "P9")
    end
  end

  def test_create_can_never_apply_the_next_label
    # There is no `labels:`/state-label parameter on #create at all — the
    # only label surface is `priority:`, restricted to P0-P3. This test
    # documents that constraint as executable spec.
    issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))

    assert_raises(ArgumentError) do
      issues.create(tracker: "forgejo:jared/cc-os", title: "T", body: "B", priority: "next")
    end
  end

  def test_repo_create_writes_a_frontmattered_markdown_file
    Dir.mktmpdir do |dir|
      issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir,
                                    clock: -> { Time.new(2026, 7, 16) })
      result = issues.create(tracker: "repo:docs/issues", title: "Fix the Thing!", body: "details here",
                              priority: "P2")

      assert_equal 1, result[:number]
      assert_equal File.join(dir, "docs/issues/001-fix-the-thing.md"), result[:path]
      contents = File.read(result[:path])
      assert_includes contents, "number: 1"
      assert_includes contents, "title: Fix the Thing!"
      assert_includes contents, "labels: P2"
      assert_includes contents, "state: open"
      assert_includes contents, "created: 2026-07-16"
      assert_includes contents, "details here"
    end
  end

  def test_repo_create_numbers_sequentially
    Dir.mktmpdir do |dir|
      issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
      issues.create(tracker: "repo:docs/issues", title: "First", body: "b")
      second = issues.create(tracker: "repo:docs/issues", title: "Second", body: "b")

      assert_equal 2, second[:number]
    end
  end
end

class IssuesListTest < Minitest::Test
  def test_forgejo_list_groups_by_state_label
    tea_json = JSON.generate([
      { "index" => 1, "title" => "Next thing", "labels" => "P1,next", "assignees" => "jared" },
      { "index" => 2, "title" => "Blocked thing", "labels" => "waiting", "assignees" => "" },
      { "index" => 3, "title" => "Shipped semi", "labels" => "review", "assignees" => "" },
      { "index" => 4, "title" => "Ad hoc", "labels" => "P3", "assignees" => "" }
    ])
    runner = FakeIssuesRunner.new([[tea_json, true]])
    grouped = Backlog::Issues.new(runner: runner).list(tracker: "forgejo:jared/cc-os")

    assert_equal [1], grouped[:next].map { |i| i[:number] }
    assert_equal [2], grouped[:waiting].map { |i| i[:number] }
    assert_equal [3], grouped[:review].map { |i| i[:number] }
    assert_equal [4], grouped[:other].map { |i| i[:number] }
    assert_equal "jared", grouped[:next].first[:assignee]
    cmd = runner.calls.first
    assert_equal "tea", cmd[0]
  end

  def test_github_list_groups_by_state_label
    gh_json = JSON.generate([
      { "number" => 5, "title" => "Ship it", "labels" => [{ "name" => "review" }],
        "assignees" => [{ "login" => "jared" }] }
    ])
    runner = FakeIssuesRunner.new([[gh_json, true]])
    grouped = Backlog::Issues.new(runner: runner).list(tracker: "github:acme/widget")

    assert_equal [5], grouped[:review].map { |i| i[:number] }
    assert_equal "jared", grouped[:review].first[:assignee]
    assert_equal "gh", runner.calls.first[0]
  end

  def test_list_raises_command_failed_on_nonzero_exit
    runner = FakeIssuesRunner.new([["boom", false]])
    assert_raises(Backlog::Issues::CommandFailed) do
      Backlog::Issues.new(runner: runner).list(tracker: "forgejo:jared/cc-os")
    end
  end

  def test_repo_list_reads_open_issue_files_and_groups_them
    Dir.mktmpdir do |dir|
      issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
      issues.create(tracker: "repo:docs/issues", title: "Waiting one", body: "b", priority: "P1")
      waiting_path = Dir.glob(File.join(dir, "docs/issues/*.md")).first
      File.write(waiting_path, File.read(waiting_path).sub("labels: P1", "labels: P1,waiting"))

      grouped = issues.list(tracker: "repo:docs/issues")
      assert_equal ["Waiting one"], grouped[:waiting].map { |i| i[:title] }
    end
  end

  def test_repo_list_is_empty_when_directory_absent
    Dir.mktmpdir do |dir|
      grouped = Backlog::Issues.new(runner: FakeIssuesRunner.new([]), repo_root: dir)
                                .list(tracker: "repo:docs/issues")
      assert_equal [], grouped[:other]
    end
  end

  def test_unroutable_tracker_raises
    issues = Backlog::Issues.new(runner: FakeIssuesRunner.new([]))
    assert_raises(ArgumentError) { issues.list(tracker: "planka:board") }
    assert_raises(ArgumentError) { issues.create(tracker: nil, title: "T", body: "B") }
  end
end
