defmodule TarakanWeb.API.InfestationControllerTest do
  use TarakanWeb.ConnCase, async: true

  import Ecto.Query, only: [from: 2]

  alias Tarakan.FindingMemory

  setup do
    submitter = github_account_fixture()
    other = github_account_fixture()
    repo_a = listed_github_repository_fixture(submitter)
    {:ok, repo_b} = Tarakan.Repositories.register_github_repository("acme/widget", other)
    repo_b = listed_repository_fixture(repo_b)

    title = "API infestation sample #{System.unique_integer([:positive])}"

    findings = fn path ->
      Jason.encode!(%{
        "tarakan_scan_format" => 1,
        "findings" => [
          %{
            "file" => path,
            "line_start" => 1,
            "line_end" => 2,
            "severity" => "high",
            "title" => title,
            "description" => "Shared issue class for the patterns API test."
          }
        ]
      })
    end

    scan_fixture(repo_a, submitter, %{"findings_json" => findings.("lib/a.ex")})
    scan_fixture(repo_b, other, %{"findings_json" => findings.("src/b.rs")})

    %{pattern_key: FindingMemory.pattern_key(title), title: title}
  end

  test "lists patterns", %{conn: conn, pattern_key: pattern_key} do
    conn = get(conn, ~p"/api/infestations")

    assert %{"patterns" => patterns} = json_response(conn, 200)
    assert pattern = Enum.find(patterns, &(&1["pattern_key"] == pattern_key))
    assert pattern["repo_count"] >= 2
    assert pattern["instance_count"] >= 2
    assert pattern["record_url"] =~ "/infestations/#{pattern_key}"
  end

  test "whitelists query params", %{conn: conn, pattern_key: pattern_key} do
    conn = get(conn, ~p"/api/infestations?days=13&min_repos=0&limit=5000")
    assert %{"patterns" => _} = json_response(conn, 200)

    conn = get(conn, ~p"/api/infestations?days=7&min_repos=2&limit=10")
    assert %{"patterns" => patterns} = json_response(conn, 200)
    assert Enum.any?(patterns, &(&1["pattern_key"] == pattern_key))
  end

  test "shows a pattern with its first page of instances", %{
    conn: conn,
    pattern_key: pattern_key,
    title: title
  } do
    conn = get(conn, ~p"/api/infestations/#{pattern_key}")

    assert %{"pattern" => pattern, "instances" => instances} = json_response(conn, 200)
    assert pattern["pattern_key"] == pattern_key
    assert pattern["title"] =~ title

    assert length(instances) >= 2
    assert Enum.any?(instances, &(&1["repository"]["owner"] == "openai"))
    assert Enum.any?(instances, &(&1["repository"]["owner"] == "acme"))
  end

  test "404s for an unknown pattern", %{conn: conn} do
    conn = get(conn, ~p"/api/infestations/no-such-pattern")
    assert json_response(conn, 404)["error"] =~ "not found"
  end

  describe "vaccine packs" do
    alias Tarakan.Accounts.Scope
    alias Tarakan.FindingSignals
    alias Tarakan.Repo
    alias Tarakan.Scans.CanonicalFinding

    # The endpoint must refuse to invent a detector. It used to always return
    # one, and the one it returned matched nothing.
    test "404s while nobody has written a validated detector", %{
      conn: conn,
      pattern_key: pattern_key
    } do
      conn = get(conn, ~p"/api/infestations/#{pattern_key}/vaccine.yaml")

      assert %{"error" => error} = json_response(conn, 404)
      assert error =~ "no validated detector"
    end

    test "serves the validated rule once one exists", %{conn: conn, pattern_key: pattern_key} do
      # Put this infestation's findings into one code cluster.
      cluster = "code:vaccinetest"

      Repo.all(from f in CanonicalFinding, where: f.pattern_key == ^pattern_key)
      |> Enum.each(fn finding ->
        finding
        |> CanonicalFinding.embedding_changeset(%{code_pattern_key: cluster})
        |> Repo.update!()
      end)

      scope = Scope.for_account(reviewer_account_fixture())

      {:ok, _rule} =
        FindingSignals.put_rule(scope, cluster, %{
          engine: "semgrep",
          language: "elixir",
          rule_yaml: """
          rules:
            - id: tarakan-real-detector
              message: Shared issue class
              severity: ERROR
              languages: [elixir]
              patterns:
                - pattern: System.cmd($C, $A)
          """,
          checked_count: 2,
          matched_count: 2,
          matched_finding_ids: [Ecto.UUID.generate(), Ecto.UUID.generate()]
        })

      conn = get(conn, ~p"/api/infestations/#{pattern_key}/vaccine.yaml")

      assert body = response(conn, 200)
      assert get_resp_header(conn, "content-type") |> hd() =~ "application/x-yaml"
      assert body =~ "tarakan-real-detector"
      assert body =~ "matched 2 of 2 known instances"
      refute body =~ "tarakan-vaccine-placeholder"
    end
  end
end
