defmodule Microwaveprop.Radio.EditNotifierTest do
  @moduledoc """
  Characterization tests for the edit-review email notifier.

  These pin down the current behavior of `deliver_edit_approved/1` and
  `deliver_edit_rejected/1` so future refactors get a compile-in safety net.
  """
  use Microwaveprop.DataCase, async: true

  import Swoosh.TestAssertions

  alias Microwaveprop.Accounts
  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Radio.ContactEdit
  alias Microwaveprop.Radio.EditNotifier

  @contact_attrs %{
    station1: "W5ISP",
    station2: "K5TR",
    qso_timestamp: ~U[2026-04-01 14:30:00Z],
    grid1: "EM13",
    grid2: "EL29",
    pos1: %{"lat" => 33.5, "lon" => -97.0},
    pos2: %{"lat" => 29.5, "lon" => -98.5},
    mode: "CW",
    band: Decimal.new("10000"),
    distance_km: Decimal.new("450")
  }

  @user_attrs %{
    callsign: "W5TEST",
    name: "Test User",
    email: "test@example.com",
    password: "testpassword123"
  }

  defp insert_contact(attrs \\ %{}) do
    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(Map.merge(@contact_attrs, attrs))
      |> Repo.insert()

    contact
  end

  defp insert_user(attrs \\ %{}) do
    {:ok, user} = Accounts.register_user(Map.merge(@user_attrs, attrs))
    user
  end

  defp insert_edit(contact, user, attrs \\ %{}) do
    defaults = %{
      contact_id: contact.id,
      user_id: user.id,
      proposed_changes: %{"grid1" => "EM13kk"}
    }

    {:ok, edit} =
      %ContactEdit{}
      |> ContactEdit.changeset(Map.merge(defaults, attrs))
      |> Repo.insert()

    # apply anything the review_changeset path wouldn't (admin_note, etc.)
    extras =
      attrs
      |> Map.drop([:contact_id, :user_id, :proposed_changes])
      |> Map.to_list()

    if extras == [] do
      edit
    else
      edit |> Ecto.Changeset.change(extras) |> Repo.update!()
    end
  end

  describe "deliver_edit_approved/1" do
    test "sends a single email to the submitter from the configured defaults" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user, %{admin_note: "Looks right, thanks"})

      assert {:ok, %Swoosh.Email{}} = EditNotifier.deliver_edit_approved(edit)

      assert_email_sent(fn email ->
        assert email.to == [{"", "test@example.com"}]
        assert email.from == {"NTMS Propagation", "prop@w5isp.com"}
        assert email.reply_to == {"", "graham@mcintire.me"}
        assert email.subject == "Your contact edit was approved"
      end)
    end

    test "body addresses the submitter by callsign and names APPROVED" do
      contact = insert_contact()
      user = insert_user(%{callsign: "K5ABC", email: "k5abc@example.com"})
      edit = insert_edit(contact, user)

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "Hi K5ABC,"
      assert email.text_body =~ "APPROVED"
      refute email.text_body =~ "REJECTED"
    end

    test "body lists the contact endpoints, band, and qso_timestamp" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user)

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "W5ISP <-> K5TR"
      assert email.text_body =~ "10 GHz"
      assert email.text_body =~ "2026-04-01 14:30 UTC"
    end

    test "formats sub-GHz bands in MHz" do
      contact = insert_contact(%{band: Decimal.new("432")})
      user = insert_user()
      edit = insert_edit(contact, user)

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "432 MHz"
      refute email.text_body =~ "GHz"
    end

    test "body lists each proposed change on its own line" do
      contact = insert_contact()
      user = insert_user()

      edit =
        insert_edit(contact, user, %{
          proposed_changes: %{"grid1" => "EM13kk", "mode" => "SSB"}
        })

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "  - grid1: EM13kk"
      assert email.text_body =~ "  - mode: SSB"
    end

    test "includes an admin note when present" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user, %{admin_note: "Verified against QRZ"})

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "Admin note: Verified against QRZ"
    end

    test "omits the admin-note line when note is nil" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user, %{admin_note: nil})

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      refute email.text_body =~ "Admin note:"
    end

    test "omits the admin-note line when note is empty string" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user, %{admin_note: ""})

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      refute email.text_body =~ "Admin note:"
    end

    test "includes a contact view link built from the endpoint url" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user)

      {:ok, email} = EditNotifier.deliver_edit_approved(edit)

      assert email.text_body =~ "#{MicrowavepropWeb.Endpoint.url()}/contacts/#{contact.id}"
    end

    test "preloads associations if the caller passes a bare record" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user)

      # Pass the schema back after re-fetch without preloads — the notifier
      # must tolerate that and still resolve user.callsign / contact.station1.
      bare = Repo.get!(ContactEdit, edit.id)

      assert {:ok, email} = EditNotifier.deliver_edit_approved(bare)
      assert email.text_body =~ "Hi W5TEST,"
      assert email.text_body =~ "W5ISP <-> K5TR"
    end
  end

  describe "deliver_edit_rejected/1" do
    test "sends an email with the rejected subject to the submitter" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user)

      assert {:ok, %Swoosh.Email{}} = EditNotifier.deliver_edit_rejected(edit)

      assert_email_sent(fn email ->
        assert email.to == [{"", "test@example.com"}]
        assert email.from == {"NTMS Propagation", "prop@w5isp.com"}
        assert email.reply_to == {"", "graham@mcintire.me"}
        assert email.subject == "Your contact edit was rejected"
      end)
    end

    test "body says REJECTED and labels the changes as 'Proposed changes:'" do
      contact = insert_contact()
      user = insert_user()

      edit =
        insert_edit(contact, user, %{
          proposed_changes: %{"station2" => "W5XYZ"},
          admin_note: "Mismatch with log"
        })

      {:ok, email} = EditNotifier.deliver_edit_rejected(edit)

      assert email.text_body =~ "REJECTED"
      refute email.text_body =~ "APPROVED"
      assert email.text_body =~ "Proposed changes:\n  - station2: W5XYZ"
      assert email.text_body =~ "Admin note: Mismatch with log"
    end

    test "includes the contact view link for rejected edits too" do
      contact = insert_contact()
      user = insert_user()
      edit = insert_edit(contact, user)

      {:ok, email} = EditNotifier.deliver_edit_rejected(edit)

      assert email.text_body =~ "#{MicrowavepropWeb.Endpoint.url()}/contacts/#{contact.id}"
    end
  end
end
