defmodule Tarakan.Billing.AlertWorkerTest do
  use Tarakan.DataCase, async: false

  alias Tarakan.Accounts.Scope
  alias Tarakan.Billing
  alias Tarakan.Billing.AlertWorker
  alias Tarakan.Repo
  alias Tarakan.Scans.CanonicalFinding

  defp verified_finding(repository, account) do
    scan = scan_fixture(repository, account, %{"findings_json" => findings_json_fixture()})

    finding =
      Repo.one!(
        from f in CanonicalFinding,
          where: f.repository_id == ^repository.id,
          limit: 1
      )

    finding
    |> Ecto.Changeset.change(status: "verified")
    |> Repo.update!()

    {scan, finding}
  end

  defp watchlist(account, entries) do
    {:ok, watchlist} =
      Billing.create_watchlist(Scope.for_account(account), %{
        "name" => "My deps",
        "entries" => entries
      })

    watchlist
  end

  # Alerts are a paid feature: gives the account an active subscription.
  defp subscribe(account) do
    %Billing.Subscription{}
    |> Billing.Subscription.changeset(%{
      account_id: account.id,
      plan: "team",
      status: "active",
      stripe_customer_id: "cus_#{System.unique_integer([:positive])}",
      stripe_subscription_id: "sub_#{System.unique_integer([:positive])}"
    })
    |> Repo.insert!()
  end

  defp perform, do: AlertWorker.perform(%Oban.Job{})

  test "a watchlist match delivers a digest email and stamps last_notified_at" do
    account = account_fixture()
    subscribe(account)
    repository = listed_github_repository_fixture(account)
    {_scan, finding} = verified_finding(repository, account)
    wl = watchlist(account, ["#{repository.owner}/#{repository.name}"])

    assert :ok = perform()

    assert_received {:email, %Swoosh.Email{subject: "Tarakan alert:" <> suffix} = email}
    assert suffix =~ "1 findings across your watchlist"
    assert email.text_body =~ finding.title
    assert email.text_body =~ "github.com/#{repository.owner}/#{repository.name}"
    assert email.text_body =~ "/findings/#{finding.public_id}"

    assert Repo.get!(Billing.Watchlist, wl.id).last_notified_at
  end

  test "no match sends nothing and leaves last_notified_at unset" do
    account = account_fixture()
    subscribe(account)
    repository = listed_github_repository_fixture(account)
    verified_finding(repository, account)
    wl = watchlist(account, ["someone/else"])

    assert :ok = perform()

    refute_received {:email, %{subject: "Tarakan alert:" <> _}}
    assert Repo.get!(Billing.Watchlist, wl.id).last_notified_at == nil
  end

  test "open findings do not alert (verified/fixed only)" do
    account = account_fixture()
    subscribe(account)
    repository = listed_github_repository_fixture(account)
    scan_fixture(repository, account, %{"findings_json" => findings_json_fixture()})
    watchlist(account, ["#{repository.owner}/#{repository.name}"])

    assert :ok = perform()

    refute_received {:email, %{subject: "Tarakan alert:" <> _}}
  end

  test "last_notified_at dedupes the second run" do
    account = account_fixture()
    subscribe(account)
    repository = listed_github_repository_fixture(account)
    verified_finding(repository, account)
    watchlist(account, ["#{repository.owner}/#{repository.name}"])

    assert :ok = perform()
    assert_received {:email, %{subject: "Tarakan alert:" <> _}}

    assert :ok = perform()
    refute_received {:email, %{subject: "Tarakan alert:" <> _}}
  end

  test "notify: false watchlists are skipped" do
    account = account_fixture()
    subscribe(account)
    repository = listed_github_repository_fixture(account)
    verified_finding(repository, account)

    {:ok, watchlist} =
      Billing.create_watchlist(Scope.for_account(account), %{
        "name" => "Muted",
        "entries" => ["#{repository.owner}/#{repository.name}"],
        "notify" => false
      })

    assert :ok = perform()

    refute_received {:email, %{subject: "Tarakan alert:" <> _}}
    assert Repo.get!(Billing.Watchlist, watchlist.id).last_notified_at == nil
  end

  test "alerts are free: an account with no subscription still gets the digest" do
    account = account_fixture()
    repository = listed_github_repository_fixture(account)
    {_scan, finding} = verified_finding(repository, account)
    wl = watchlist(account, ["#{repository.owner}/#{repository.name}"])

    assert :ok = perform()

    assert_received {:email, %Swoosh.Email{subject: "Tarakan alert:" <> _} = email}
    assert email.text_body =~ finding.title
    assert Repo.get!(Billing.Watchlist, wl.id).last_notified_at
  end
end
