defmodule Tarakan.CodePatternRulesTest do
  @moduledoc """
  A vaccine pack is only worth serving if it was shown to match real instances.
  These tests exist because the previous implementation always returned a rule
  and the rule matched nothing.
  """
  use Tarakan.DataCase, async: true

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

  @cluster "code:abc123"

  defp valid_rule(overrides \\ %{}) do
    Map.merge(
      %{
        engine: "semgrep",
        language: "elixir",
        rule_yaml: """
        rules:
          - id: tarakan-command-injection
            message: User input reaches System.cmd
            severity: ERROR
            languages: [elixir]
            patterns:
              - pattern: System.cmd($CMD, $ARGS)
        """,
        checked_count: 4,
        matched_count: 3,
        matched_finding_ids: [Ecto.UUID.generate(), Ecto.UUID.generate(), Ecto.UUID.generate()]
      },
      overrides
    )
  end

  setup do
    %{scope: Scope.for_account(reviewer_account_fixture())}
  end

  describe "put_rule/3" do
    test "stores and stamps a rule that matched instances", %{scope: scope} do
      assert {:ok, rule} = FindingSignals.put_rule(scope, @cluster, valid_rule())

      assert rule.matched_count == 3
      assert rule.validated_at
      assert Tarakan.Scans.CodePatternRule.servable?(rule)
    end

    test "a rule that matched nothing is stored but never validated", %{scope: scope} do
      attrs = valid_rule(%{matched_count: 0, matched_finding_ids: []})

      assert {:ok, rule} = FindingSignals.put_rule(scope, @cluster, attrs)
      refute rule.validated_at
      refute Tarakan.Scans.CodePatternRule.servable?(rule)
      assert is_nil(FindingSignals.best_rule(@cluster))
    end

    test "rejects the old placeholder body outright", %{scope: scope} do
      attrs =
        valid_rule(%{
          rule_yaml: """
          rules:
            - id: tarakan-x
              patterns:
                - pattern-regex: tarakan-vaccine-placeholder
          """
        })

      assert {:error, changeset} = FindingSignals.put_rule(scope, @cluster, attrs)
      assert %{rule_yaml: ["is still a placeholder, not a detector"]} = errors_on(changeset)
    end

    test "rejects a match count with no receipts", %{scope: scope} do
      attrs = valid_rule(%{matched_count: 3, matched_finding_ids: []})

      assert {:error, changeset} = FindingSignals.put_rule(scope, @cluster, attrs)
      assert %{matched_finding_ids: _} = errors_on(changeset)
    end

    test "rejects claiming more matches than instances checked", %{scope: scope} do
      attrs = valid_rule(%{checked_count: 1, matched_count: 3})

      assert {:error, changeset} = FindingSignals.put_rule(scope, @cluster, attrs)
      assert %{matched_count: _} = errors_on(changeset)
    end

    test "resubmitting replaces that author's rule rather than stacking", %{scope: scope} do
      assert {:ok, first} = FindingSignals.put_rule(scope, @cluster, valid_rule())

      assert {:ok, second} =
               FindingSignals.put_rule(
                 scope,
                 @cluster,
                 valid_rule(%{
                   matched_count: 4,
                   matched_finding_ids: Enum.map(1..4, fn _ -> Ecto.UUID.generate() end)
                 })
               )

      assert first.id == second.id
      assert second.matched_count == 4
    end
  end

  describe "best_rule/1" do
    test "prefers the rule that matched the most instances", %{scope: scope} do
      {:ok, _weak} =
        FindingSignals.put_rule(
          scope,
          @cluster,
          valid_rule(%{matched_count: 1, matched_finding_ids: [Ecto.UUID.generate()]})
        )

      other = Scope.for_account(reviewer_account_fixture())

      {:ok, strong} =
        FindingSignals.put_rule(
          other,
          @cluster,
          valid_rule(%{
            matched_count: 4,
            checked_count: 4,
            matched_finding_ids: Enum.map(1..4, fn _ -> Ecto.UUID.generate() end)
          })
        )

      assert FindingSignals.best_rule(@cluster).id == strong.id
    end

    test "returns nil for a cluster nobody has written a detector for" do
      assert is_nil(FindingSignals.best_rule("code:nothing"))
    end
  end

  describe "dominant_cluster/1" do
    test "picks the cluster shared by the most findings in an infestation" do
      owner = github_account_fixture()
      repository = listed_github_repository_fixture(owner)

      insert = fn cluster ->
        %CanonicalFinding{}
        |> CanonicalFinding.changeset(%{
          repository_id: repository.id,
          fingerprint: "fp-#{System.unique_integer([:positive])}",
          pattern_key: "title-pattern",
          file_path: "lib/a.ex",
          severity: "high",
          title: "Same title",
          description: "d",
          status: "open",
          first_seen_commit_sha: String.duplicate("a", 40),
          last_seen_commit_sha: String.duplicate("a", 40)
        })
        |> Repo.insert!()
        |> CanonicalFinding.embedding_changeset(%{code_pattern_key: cluster})
        |> Repo.update!()
      end

      insert.("code:majority")
      insert.("code:majority")
      insert.("code:minority")

      assert FindingSignals.dominant_cluster("title-pattern") == "code:majority"
    end
  end
end
