defmodule Microwaveprop.Workers.RtmaFetchWorkerTest do
  use Microwaveprop.DataCase, async: false
  use Oban.Testing, repo: Microwaveprop.Repo

  alias Microwaveprop.Weather.RtmaClient
  alias Microwaveprop.Weather.RtmaObservation
  alias Microwaveprop.Workers.RtmaFetchWorker

  describe "perform/1" do
    test "skips the fetch (no HTTP) when a matching row already exists in the snapped bounding box" do
      valid_time = ~U[2026-04-15 18:00:00Z]

      # Pre-populate a row at the snapped coordinates (32.9 → 32.9 at 1/40°,
      # -97.0 → -97.0). The worker's bounding-box short-circuit must find it
      # and return :ok without contacting RtmaClient.
      %RtmaObservation{}
      |> RtmaObservation.changeset(%{
        lat: 32.9,
        lon: -97.0,
        valid_time: valid_time,
        temp_c: 20.0
      })
      |> Repo.insert!()

      Req.Test.stub(RtmaClient, fn _conn ->
        raise "RtmaClient should not be contacted when an observation already exists"
      end)

      args = %{
        "lat" => 32.9,
        "lon" => -97.0,
        "valid_time" => DateTime.to_iso8601(valid_time)
      }

      assert :ok = RtmaFetchWorker.perform(%Oban.Job{args: args})
      assert Repo.aggregate(RtmaObservation, :count, :id) == 1
    end

    # With garbage-bytes-on-the-wire (no real GRIB2 fixture), the extractor
    # yields no fields and RtmaClient returns {:error, "RTMA: no data ..."},
    # which the worker surfaces as {:error, _}. This characterizes that the
    # worker propagates the client error (and does NOT insert a blank row).
    test "returns {:error, _} and inserts nothing when the client fails to extract" do
      idx = """
      1:0:d=2026041518:TMP:2 m above ground:anl:
      2:100:d=2026041518:DPT:2 m above ground:anl:
      """

      Req.Test.stub(RtmaClient, fn conn ->
        if String.ends_with?(conn.request_path, ".idx") do
          Plug.Conn.send_resp(conn, 200, idx)
        else
          Plug.Conn.send_resp(conn, 206, :binary.copy(<<0>>, 1024))
        end
      end)

      args = %{
        "lat" => 32.9,
        "lon" => -97.0,
        "valid_time" => "2026-04-15T18:00:00Z"
      }

      assert {:error, reason} = RtmaFetchWorker.perform(%Oban.Job{args: args})
      assert reason =~ "RTMA: no data"
      assert Repo.aggregate(RtmaObservation, :count, :id) == 0
    end

    test "returns {:error, _} when upstream idx returns 404" do
      Req.Test.stub(RtmaClient, fn conn ->
        if String.ends_with?(conn.request_path, ".idx") do
          Plug.Conn.send_resp(conn, 404, "not yet published")
        else
          Plug.Conn.send_resp(conn, 500, "unexpected")
        end
      end)

      args = %{
        "lat" => 32.9,
        "lon" => -97.0,
        "valid_time" => "2026-04-15T18:00:00Z"
      }

      assert {:error, reason} = RtmaFetchWorker.perform(%Oban.Job{args: args})
      assert reason =~ "RTMA idx HTTP 404"
      assert Repo.aggregate(RtmaObservation, :count, :id) == 0
    end

    test "raises FunctionClauseError when args are missing required keys" do
      # The worker only matches the shape with lat/lon/valid_time — missing
      # keys must crash loudly rather than silently succeed. This pins the
      # current fail-fast contract.
      assert_raise FunctionClauseError, fn ->
        RtmaFetchWorker.perform(%Oban.Job{args: %{}})
      end
    end

    test "raises when valid_time is not an ISO8601 string" do
      # DateTime.from_iso8601/1 returns {:error, _}, and the `{:ok, _, _} =`
      # match raises — another fail-fast contract worth pinning.
      assert_raise MatchError, fn ->
        RtmaFetchWorker.perform(%Oban.Job{
          args: %{"lat" => 32.9, "lon" => -97.0, "valid_time" => "not-a-timestamp"}
        })
      end
    end
  end
end
