defmodule Tarakan.WorkFindingJobsTest do
  use Tarakan.DataCase, async: true

  alias Tarakan.Accounts.Scope
  alias Tarakan.Repo
  alias Tarakan.Scans.CanonicalFinding
  alias Tarakan.Work

  defp setup_finding do
    submitter = github_account_fixture()
    repository = listed_github_repository_fixture(submitter)

    findings =
      Jason.encode!(%{
        "tarakan_scan_format" => 1,
        "findings" => [
          %{
            "file" => "lib/vulnerable.ex",
            "severity" => "high",
            "title" => "Command injection in exec path",
            "description" => "User input reaches System.cmd without escaping."
          }
        ]
      })

    scan_fixture(repository, submitter, %{"findings_json" => findings})
    finding = Repo.one!(from f in CanonicalFinding, where: f.repository_id == ^repository.id)

    %{repository: repository, finding: finding, opener: submitter}
  end

  for kind <- ~w(refute_finding reproduce_finding calibrate_severity) do
    test "opens a public agent job for #{kind}" do
      %{finding: finding, opener: opener} = setup_finding()

      assert {:ok, task} =
               Work.open_finding_job(Scope.for_account(opener), finding, unquote(kind))

      assert task.kind == unquote(kind)
      assert task.capability == "agent"
      assert task.status == "open"
      assert task.visibility == "public"
      assert task.target_canonical_finding_id == finding.id
      # Pinned to where the finding was last seen, not to the branch tip.
      assert task.commit_sha == finding.last_seen_commit_sha
      assert task.published_at
    end
  end

  test "will not stack a second open job of the same kind on one finding" do
    %{finding: finding, opener: opener} = setup_finding()
    scope = Scope.for_account(opener)

    assert {:ok, _task} = Work.open_finding_job(scope, finding, "refute_finding")
    assert {:ok, :skipped_duplicate} = Work.open_finding_job(scope, finding, "refute_finding")
  end

  test "different kinds on the same finding are independent" do
    %{finding: finding, opener: opener} = setup_finding()
    scope = Scope.for_account(opener)

    assert {:ok, _} = Work.open_finding_job(scope, finding, "refute_finding")
    assert {:ok, task} = Work.open_finding_job(scope, finding, "reproduce_finding")
    assert task.kind == "reproduce_finding"
  end

  test "rejects a kind that is not finding-targeted" do
    %{finding: finding, opener: opener} = setup_finding()

    assert {:error, :unauthorized} =
             Work.open_finding_job(Scope.for_account(opener), finding, "code_review")
  end

  describe "open_rule_job/2" do
    test "opens a detector job pinned to a real finding in the cluster" do
      %{finding: finding, opener: opener} = setup_finding()

      finding =
        finding
        |> CanonicalFinding.embedding_changeset(%{code_pattern_key: "code:cluster1"})
        |> Repo.update!()

      assert {:ok, task} = Work.open_rule_job(Scope.for_account(opener), "code:cluster1")

      assert task.kind == "synthesize_rule"
      assert task.target_code_pattern_key == "code:cluster1"
      # Needs real code to write and validate a rule against.
      assert task.commit_sha == finding.last_seen_commit_sha
      assert task.description =~ "matches nothing is rejected"
    end

    test "will not stack a second detector job on one cluster" do
      %{finding: finding, opener: opener} = setup_finding()

      finding
      |> CanonicalFinding.embedding_changeset(%{code_pattern_key: "code:cluster2"})
      |> Repo.update!()

      scope = Scope.for_account(opener)
      assert {:ok, _} = Work.open_rule_job(scope, "code:cluster2")
      assert {:ok, :skipped_duplicate} = Work.open_rule_job(scope, "code:cluster2")
    end

    test "a cluster with no findings has nothing to write a rule from" do
      %{opener: opener} = setup_finding()

      assert {:error, :not_found} =
               Work.open_rule_job(Scope.for_account(opener), "code:empty")
    end
  end

  test "the brief tells a refuter to attack rather than agree" do
    %{finding: finding, opener: opener} = setup_finding()

    assert {:ok, task} =
             Work.open_finding_job(Scope.for_account(opener), finding, "refute_finding")

    assert task.description =~ "prove this finding wrong"
  end
end
