defmodule Aprsme.Swoosh.ResendAdapterTest do
  use ExUnit.Case, async: true

  alias Aprsme.Swoosh.ResendAdapter
  alias Swoosh.Email

  defp stub_config(plug_name) do
    [api_key: "re_test_key", plug: {Req.Test, plug_name}]
  end

  defp new_email(opts \\ []) do
    defaults = [
      from: {"Sender Name", "sender@example.com"},
      to: [{"Recipient", "recipient@example.com"}],
      subject: "Test Subject",
      html_body: "<p>Hello</p>",
      text_body: "Hello"
    ]

    Email.new(Keyword.merge(defaults, opts))
  end

  describe "deliver/2" do
    test "posts to Resend API and returns id on success" do
      Req.Test.stub(__MODULE__, fn conn ->
        Req.Test.json(conn, %{"id" => "msg_abc123"})
      end)

      email = new_email()

      assert {:ok, %{id: "msg_abc123"}} = ResendAdapter.deliver(email, stub_config(__MODULE__))
    end

    test "returns error on API error response" do
      Req.Test.stub(__MODULE__, fn conn ->
        conn
        |> Plug.Conn.put_status(422)
        |> Req.Test.json(%{"name" => "validation_error", "message" => "Invalid email"})
      end)

      email = new_email()

      assert {:error, {"validation_error", "Invalid email"}} =
               ResendAdapter.deliver(email, stub_config(__MODULE__))
    end

    test "returns error on network failure" do
      Req.Test.stub(__MODULE__, fn conn ->
        Req.Test.transport_error(conn, :econnrefused)
      end)

      email = new_email()

      assert {:error, %Req.TransportError{reason: :econnrefused}} =
               ResendAdapter.deliver(email, stub_config(__MODULE__))
    end

    test "formats named sender as 'Name <email>'" do
      Req.Test.stub(__MODULE__, fn conn ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        send(self(), {:request_body, decoded})
        Req.Test.json(conn, %{"id" => "msg_1"})
      end)

      email = new_email(from: {"My App", "noreply@example.com"})
      ResendAdapter.deliver(email, stub_config(__MODULE__))

      assert_received {:request_body, body}
      assert body["from"] == "My App <noreply@example.com>"
    end

    test "formats bare email sender without name prefix" do
      Req.Test.stub(__MODULE__, fn conn ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        send(self(), {:request_body, decoded})
        Req.Test.json(conn, %{"id" => "msg_1"})
      end)

      email = new_email(from: {"", "noreply@example.com"})
      ResendAdapter.deliver(email, stub_config(__MODULE__))

      assert_received {:request_body, body}
      assert body["from"] == "noreply@example.com"
    end

    test "formats list of recipients as list of email strings" do
      Req.Test.stub(__MODULE__, fn conn ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        send(self(), {:request_body, decoded})
        Req.Test.json(conn, %{"id" => "msg_1"})
      end)

      email =
        new_email(to: [{"Alice", "alice@example.com"}, {"Bob", "bob@example.com"}])

      ResendAdapter.deliver(email, stub_config(__MODULE__))

      assert_received {:request_body, body}
      assert body["to"] == ["alice@example.com", "bob@example.com"]
    end

    test "omits nil fields from request body" do
      Req.Test.stub(__MODULE__, fn conn ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        send(self(), {:request_body, decoded})
        Req.Test.json(conn, %{"id" => "msg_1"})
      end)

      email = new_email()
      ResendAdapter.deliver(email, stub_config(__MODULE__))

      assert_received {:request_body, body}
      refute Map.has_key?(body, "cc")
      refute Map.has_key?(body, "bcc")
      refute Map.has_key?(body, "reply_to")
    end
  end

  describe "validate_config/1" do
    test "returns :ok when api_key is present" do
      assert :ok = ResendAdapter.validate_config(api_key: "re_abc")
    end

    test "returns error when api_key is missing" do
      assert {:error, _reason} = ResendAdapter.validate_config([])
    end
  end

  describe "deliver/2 catch-all body branch" do
    test "returns the raw body as the error when shape is unrecognized" do
      Req.Test.stub(__MODULE__, fn conn ->
        conn
        |> Plug.Conn.put_status(500)
        |> Req.Test.json(%{"unexpected" => "shape"})
      end)

      email = new_email()
      assert {:error, %{"unexpected" => "shape"}} = ResendAdapter.deliver(email, stub_config(__MODULE__))
    end
  end

  describe "deliver_many/2" do
    test "delivers each email and returns the collected ids" do
      Req.Test.stub(__MODULE__, fn conn ->
        Req.Test.json(conn, %{"id" => "msg_many"})
      end)

      emails = [new_email(), new_email(subject: "Second")]

      assert {:ok, [%{id: "msg_many"}, %{id: "msg_many"}]} =
               ResendAdapter.deliver_many(emails, stub_config(__MODULE__))
    end

    test "halts and returns the first error" do
      Req.Test.stub(__MODULE__, fn conn ->
        conn
        |> Plug.Conn.put_status(422)
        |> Req.Test.json(%{"name" => "validation_error", "message" => "boom"})
      end)

      emails = [new_email(), new_email()]

      assert {:error, {"validation_error", "boom"}} =
               ResendAdapter.deliver_many(emails, stub_config(__MODULE__))
    end
  end

  describe "format_sender/1 and format_recipients/1 nil/binary branches" do
    test "deliver/2 sends nil and binary fields through the formatters" do
      Req.Test.stub(__MODULE__, fn conn ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)
        send(self(), {:request_body, Jason.decode!(body)})
        Req.Test.json(conn, %{"id" => "msg_xyz"})
      end)

      email =
        Email.new(
          from: "bare@example.com",
          to: "single@example.com",
          subject: "Subject",
          html_body: "<p>Hi</p>",
          text_body: "Hi"
        )

      assert {:ok, _} = ResendAdapter.deliver(email, api_key: "re_test_key", plug: {Req.Test, __MODULE__})

      assert_received {:request_body, body}
      assert body["from"] == "bare@example.com"
      # Swoosh always wraps `to` in a list; format_recipients/1 maps over it,
      # exercising the binary-recipient clause for each element.
      assert body["to"] == ["single@example.com"]
    end
  end
end
