defmodule Microwaveprop.Commercial.PollWorkerTest do
  use Microwaveprop.DataCase, async: false

  alias Microwaveprop.Commercial
  alias Microwaveprop.Commercial.PollWorker
  alias Microwaveprop.Commercial.Sample
  alias Microwaveprop.Weather
  alias Microwaveprop.Weather.IemClient
  alias Microwaveprop.Weather.SurfaceObservation

  @link_attrs %{
    label: "test-poll-link",
    host: "10.0.0.1",
    community: "public",
    radio_type: "af11x",
    weather_station: "KTKI",
    enabled: true
  }

  describe "poll_and_record/2" do
    test "creates samples for enabled links when SNMP succeeds" do
      {:ok, link} = Commercial.create_link(@link_attrs)

      snmp_result = %{
        rx_power_0: -55,
        tx_power: 20,
        link_state: 1,
        link_uptime: 86_400
      }

      PollWorker.poll_and_record([link], fn _host, _community, _type -> {:ok, snmp_result} end)

      samples = Repo.all(Sample)
      assert length(samples) == 1
      assert hd(samples).rx_power_0 == -55.0
      assert hd(samples).link_id == link.id
    end

    test "skips links when SNMP fails" do
      {:ok, link} = Commercial.create_link(@link_attrs)

      PollWorker.poll_and_record(
        [link],
        fn _host, _community, _type -> {:error, :snmp_failed} end
      )

      assert Repo.all(Sample) == []
    end

    test "continues polling remaining links after one failure" do
      {:ok, link1} = Commercial.create_link(@link_attrs)
      {:ok, link2} = Commercial.create_link(%{@link_attrs | label: "test-poll-link-2", host: "10.0.0.2"})

      call_count = :counters.new(1, [:atomics])

      poll_fn = fn _host, _community, _type ->
        n = :counters.get(call_count, 1)
        :counters.add(call_count, 1, 1)

        if n == 0 do
          {:error, :snmp_failed}
        else
          {:ok, %{rx_power_0: -60, link_state: 1}}
        end
      end

      PollWorker.poll_and_record([link1, link2], poll_fn)

      assert length(Repo.all(Sample)) == 1
    end

    test "no-ops cleanly on an empty link list" do
      # Defensive: if the DB has no enabled links, the worker should
      # still return without calling the poll fn or touching samples.
      called? = :counters.new(1, [:atomics])

      poll_fn = fn _host, _community, _type ->
        :counters.add(called?, 1, 1)
        {:ok, %{}}
      end

      PollWorker.poll_and_record([], poll_fn)

      assert :counters.get(called?, 1) == 0
      assert Repo.all(Sample) == []
    end

    test "dispatches poll_fn with host, community, and radio_type from the link" do
      {:ok, _link} = Commercial.create_link(@link_attrs)

      test_pid = self()

      poll_fn = fn host, community, radio_type ->
        send(test_pid, {:poll_args, host, community, radio_type})
        {:error, :stop}
      end

      PollWorker.poll_and_record(Commercial.enabled_links(), poll_fn)

      assert_receive {:poll_args, "10.0.0.1", "public", "af11x"}, 500
    end

    test "logs and skips when create_sample rejects the changeset" do
      {:ok, link} = Commercial.create_link(@link_attrs)

      # radio link_state must be an integer; send a bogus shape that the
      # Sample changeset will reject so the error-path branch is hit.
      PollWorker.poll_and_record([link], fn _h, _c, _t ->
        {:ok, %{rx_power_0: "not-a-number"}}
      end)

      # No sample persisted and no process crash — the warning log is
      # the only side effect, which we can't easily assert on without
      # capture_log, but coverage of the {:error, changeset} branch is.
      assert Repo.all(Sample) == []
    end
  end

  describe "fetch_weather/1" do
    # Exercises the IEM-fetch + ASOS-upsert path (previously private) via
    # stubbed Req. Avoids the perform/1 SNMP round-trip which would call
    # every live commercial link at once.

    setup do
      # IemClient uses Req — stub it with a minimal ASOS tab-separated
      # response body so the JSON/CSV decoder path is exercised.
      Req.Test.stub(IemClient, fn conn ->
        Plug.Conn.send_resp(conn, 200, "station,valid\n")
      end)

      :ok
    end

    test "creates (or finds) a Weather.Station row for every unique link station" do
      {:ok, link} = Commercial.create_link(@link_attrs)

      assert :ok = PollWorker.fetch_weather([link])

      {:ok, station} =
        Weather.find_or_create_station(%{
          station_code: "KTKI",
          station_type: "asos",
          name: "KTKI",
          lat: 0.0,
          lon: 0.0
        })

      assert station.station_code == "KTKI"
    end

    test "deduplicates identical weather_station values across multiple links" do
      {:ok, link1} = Commercial.create_link(@link_attrs)

      {:ok, link2} =
        Commercial.create_link(%{@link_attrs | label: "sibling", host: "10.0.0.2"})

      call_count = :counters.new(1, [:atomics])

      Req.Test.stub(IemClient, fn conn ->
        :counters.add(call_count, 1, 1)
        Plug.Conn.send_resp(conn, 200, "station,valid\n")
      end)

      PollWorker.fetch_weather([link1, link2])

      # Both links share weather_station="KTKI" → only one IEM fetch.
      assert :counters.get(call_count, 1) == 1
    end

    test "skips links with no weather_station (nil)" do
      {:ok, link} =
        Commercial.create_link(%{@link_attrs | label: "stationless", weather_station: nil})

      call_count = :counters.new(1, [:atomics])

      Req.Test.stub(IemClient, fn conn ->
        :counters.add(call_count, 1, 1)
        Plug.Conn.send_resp(conn, 200, "")
      end)

      assert :ok = PollWorker.fetch_weather([link])
      assert :counters.get(call_count, 1) == 0
    end

    test "tolerates an IEM fetch failure without crashing" do
      {:ok, link} = Commercial.create_link(@link_attrs)

      Req.Test.stub(IemClient, fn conn ->
        Plug.Conn.send_resp(conn, 500, "server error")
      end)

      assert :ok = PollWorker.fetch_weather([link])

      # No surface observations persisted on HTTP error.
      assert Repo.all(SurfaceObservation) == []
    end
  end
end
