defmodule AprsmeWeb.InfoLive.ShowFullRenderTest do
  use AprsmeWeb.ConnCase

  import Aprsme.PacketsFixtures
  import Phoenix.LiveViewTest

  @moduletag :capture_log

  describe "InfoLive.Show with real packet data" do
    test "renders full station info for a callsign with a position packet", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "INFO1",
          base_callsign: "INFO1",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          path: "WIDE1-1,qAC,T2TEXAS",
          data_type: "position"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/INFO1", on_error: :warn)

      assert html =~ "INFO1"
    end

    test "renders info with a weather packet", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "INFO2",
          base_callsign: "INFO2",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("40.7"),
          lon: Decimal.new("-74.0"),
          has_position: true,
          temperature: 72.5,
          humidity: 50,
          pressure: 1013.2,
          path: "qAC,T2NY"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/INFO2", on_error: :warn)
      assert html =~ "INFO2"
    end

    test "renders info with a mic-e data_extended packet", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "MICE1",
          base_callsign: "MICE1",
          ssid: "9",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("35.0"),
          lon: Decimal.new("-90.0"),
          has_position: true,
          data_type: "mic_e",
          course: 45,
          speed: 10.5,
          altitude: 100.0,
          path: "WIDE1-1"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/MICE1-9", on_error: :warn)
      assert html =~ "MICE1"
    end

    test "renders info with an SSID-only path query (e.g., K1ABC-15)", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "K1ABC-15",
          base_callsign: "K1ABC",
          ssid: "15",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("42.0"),
          lon: Decimal.new("-72.0"),
          has_position: true,
          path: "WIDE1-1,WIDE2-1,qAO,K5XYZ*"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/K1ABC-15", on_error: :warn)
      assert html =~ "K1ABC-15"
    end

    test "renders info for a station with a weather-only packet", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "WXONLY",
          base_callsign: "WXONLY",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("38.0"),
          lon: Decimal.new("-77.0"),
          has_position: true,
          has_weather: true,
          data_type: "weather",
          temperature: 68.0,
          humidity: 80.0,
          pressure: 1010.0,
          wind_speed: 3.0,
          wind_direction: 270,
          wind_gust: 5.0,
          rain_1h: 0.0,
          rain_24h: 0.5,
          rain_since_midnight: 0.3
        })

      {:ok, _lv, html} = live(conn, ~p"/info/WXONLY", on_error: :warn)
      assert html =~ "WXONLY"
    end

    test "renders a station with a rich APRS path exercising decoders", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "PATH2",
          base_callsign: "PATH2",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("30.0"),
          lon: Decimal.new("-90.0"),
          has_position: true,
          # Mixes q-construct, WIDE, TRACE, RELAY, TCPIP, generic digi, and bare station.
          path: "WIDE1-1,WIDE2-2,TRACE3-3,RELAY,TCPIP*,K5ABC-1,N0CALL,qAC"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/PATH2", on_error: :warn)
      assert html =~ "PATH2"
    end

    test "renders a station with a packet that has a nil path", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "NOPATH",
          base_callsign: "NOPATH",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("30.0"),
          lon: Decimal.new("-90.0"),
          has_position: true,
          path: nil
        })

      {:ok, _lv, html} = live(conn, ~p"/info/NOPATH", on_error: :warn)
      assert html =~ "NOPATH"
    end

    test "posts a broadcast that triggers a position change", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "MOVED",
          base_callsign: "MOVED",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true,
          path: ""
        })

      {:ok, lv, _html} = live(conn, ~p"/info/MOVED", on_error: :warn)

      # Insert a newer packet with a significantly different position.
      _new_packet =
        packet_fixture(%{
          sender: "MOVED",
          base_callsign: "MOVED",
          ssid: "0",
          received_at: DateTime.add(DateTime.utc_now(), 1, :second),
          lat: Decimal.new("34.0"),
          lon: Decimal.new("-95.0"),
          has_position: true,
          path: ""
        })

      send(lv.pid, {:postgres_packet, %{sender: "MOVED"}})
      Process.sleep(30)
      assert render(lv) =~ "MOVED"
    end

    test "posts a broadcast without position change keeps neighbors intact", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "STILL",
          base_callsign: "STILL",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true,
          path: ""
        })

      {:ok, lv, _html} = live(conn, ~p"/info/STILL", on_error: :warn)

      # Another packet with the SAME position — position_changed? returns false.
      _new_packet =
        packet_fixture(%{
          sender: "STILL",
          base_callsign: "STILL",
          ssid: "0",
          received_at: DateTime.add(DateTime.utc_now(), 1, :second),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true,
          path: ""
        })

      send(lv.pid, {:postgres_packet, %{sender: "STILL"}})
      Process.sleep(30)
      assert render(lv) =~ "STILL"
    end

    test "ignores unknown messages without crashing", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "UNK",
          base_callsign: "UNK",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true
        })

      {:ok, lv, _html} = live(conn, ~p"/info/UNK", on_error: :warn)

      send(lv.pid, :some_arbitrary_message)
      Process.sleep(20)
      assert Process.alive?(lv.pid)
    end

    test "renders a packet with rich metadata (altitude, course, speed, comment, device)", %{
      conn: conn
    } do
      _packet =
        packet_fixture(%{
          sender: "RICH1",
          base_callsign: "RICH1",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          altitude: 300.5,
          course: 90,
          speed: 25.0,
          comment: "Some comment text",
          device_identifier: "APRS",
          path: "WIDE1-1,WIDE2-2,K5ABC*"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/RICH1", on_error: :warn)
      assert html =~ "RICH1"
      assert html =~ "Some comment text"
    end

    test "renders 'Weather charts' link when station has weather packets", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "WXLINK",
          base_callsign: "WXLINK",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          has_weather: true,
          temperature: 72.0,
          humidity: 50.0,
          path: ""
        })

      {:ok, _lv, html} = live(conn, ~p"/info/WXLINK", on_error: :warn)
      # The Weather charts link should be rendered since has_weather_packets? = true.
      assert html =~ ~r/weather/i
    end

    test "renders 'no packets' branch when no packet exists", %{conn: conn} do
      # Callsign with no packets in DB.
      {:ok, _lv, html} = live(conn, ~p"/info/NOPACKET1", on_error: :warn)
      assert html =~ "NOPACKET1"
      # The 'if @packet do' branch falls through to the empty-state.
    end

    test "renders PHG (Power-Height-Gain) data when present", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "PHG1",
          base_callsign: "PHG1",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          data: %{
            "phg_power" => 9,
            "phg_height" => 30,
            "phg_gain" => 3,
            "phg_directivity" => 0
          },
          path: "WIDE1-1"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/PHG1", on_error: :warn)
      assert html =~ "PHG1"
      # Power-Height-Gain section renders when phg_ fields are present.
      # credo:disable-for-next-line Jump.CredoChecks.ConditionalAssertion
      assert html =~ "PHG" or html =~ "Power"
      # PHG section renders either "PHG" label or "Power" expansion
    end

    test "renders PHG with non-omni directivity", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "PHG2",
          base_callsign: "PHG2",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          data: %{
            "phg_power" => 25,
            "phg_directivity" => 90
          },
          path: ""
        })

      {:ok, _lv, html} = live(conn, ~p"/info/PHG2", on_error: :warn)
      assert html =~ "PHG2"
    end

    test "renders heard_by stations section when path has a digipeater", %{conn: conn} do
      now = DateTime.utc_now()

      # The station we'll view info for, with a path showing it was heard
      # by digipeater K5ABC-1.
      _heard =
        packet_fixture(%{
          sender: "HEARDME",
          base_callsign: "HEARDME",
          ssid: "0",
          received_at: now,
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          path: "K5ABC-1*,WIDE1-1,qAO,T2TEXAS"
        })

      # The digipeater station itself, needed for ST_Distance calc
      _digi =
        packet_fixture(%{
          sender: "K5ABC-1",
          base_callsign: "K5ABC",
          ssid: "1",
          received_at: now,
          lat: Decimal.new("33.5"),
          lon: Decimal.new("-96.8"),
          has_position: true,
          path: ""
        })

      {:ok, _lv, html} = live(conn, ~p"/info/HEARDME", on_error: :warn)
      assert html =~ "HEARDME"
    end

    test "renders with a device identifier that matches a seeded device", %{conn: conn} do
      # Seed a device that enrich_with_device_info can find
      Aprsme.Repo.delete_all(Aprsme.Devices)

      Aprsme.Repo.insert!(%Aprsme.Devices{
        identifier: "APTESTX",
        vendor: "TestVendor",
        model: "TestModel",
        contact: "test@example.com",
        class: "Tracker"
      })

      devices = Aprsme.Repo.all(Aprsme.Devices)
      Aprsme.Cache.put(:device_cache, :all_devices, devices)

      _packet =
        packet_fixture(%{
          sender: "WITHDEV",
          base_callsign: "WITHDEV",
          ssid: "0",
          destination: "APTESTX",
          device_identifier: "APTESTX",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          path: "WIDE1-1"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/WITHDEV", on_error: :warn)

      # The device enrichment should add vendor/model to the rendered page.
      assert html =~ "WITHDEV"
    end

    test "renders a packet with an object_name", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "OBJSRC",
          base_callsign: "OBJSRC",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          object_name: "K5SGD",
          data_type: "object",
          path: "WIDE1-1"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/OBJSRC", on_error: :warn)
      assert html =~ "OBJSRC"
    end

    test "renders when the station has other SSIDs active", %{conn: conn} do
      now = DateTime.utc_now()

      _p1 =
        packet_fixture(%{
          sender: "MULTI-1",
          base_callsign: "MULTI",
          ssid: "1",
          received_at: now,
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          path: "WIDE1-1"
        })

      _p2 =
        packet_fixture(%{
          sender: "MULTI-2",
          base_callsign: "MULTI",
          ssid: "2",
          received_at: DateTime.add(now, -60, :second),
          lat: Decimal.new("33.1"),
          lon: Decimal.new("-96.6"),
          has_position: true,
          path: "WIDE1-1"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/MULTI-1", on_error: :warn)
      assert html =~ "MULTI-1"
    end

    test "posts a postgres_packet broadcast to InfoLive.Show without crashing", %{conn: conn} do
      _packet =
        packet_fixture(%{
          sender: "BC1",
          base_callsign: "BC1",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.5"),
          has_position: true,
          path: "WIDE1-1"
        })

      {:ok, lv, _html} = live(conn, ~p"/info/BC1", on_error: :warn)

      broadcast_payload = %{
        sender: "BC1",
        base_callsign: "BC1",
        ssid: "0",
        lat: 33.1,
        lon: -96.6,
        received_at: DateTime.utc_now(),
        has_position: true,
        path: "WIDE1-1"
      }

      send(lv.pid, {:postgres_packet, broadcast_payload})
      Process.sleep(50)
      assert render(lv) =~ "BC1"
    end

    test "fills in position from an earlier position-bearing packet when the latest has no lat/lon",
         %{conn: conn} do
      # Older packet with a position — this is what get_latest_position_packet
      # should return.
      _pos_packet =
        packet_fixture(%{
          sender: "POSFILL",
          base_callsign: "POSFILL",
          ssid: "0",
          received_at: DateTime.add(DateTime.utc_now(), -60, :second),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true,
          path: "WIDE1-1"
        })

      # A newer, non-position packet — for example a status packet.
      # Set lat/lon to nil so the latest packet lacks coordinates, forcing
      # get_latest_packet to fall back via get_latest_position_packet.
      _status_packet =
        packet_fixture(%{
          sender: "POSFILL",
          base_callsign: "POSFILL",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: nil,
          lon: nil,
          has_position: false,
          path: "WIDE1-1",
          data_type: "status"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/POSFILL", on_error: :warn)
      assert html =~ "POSFILL"
    end

    test "renders a packet with an extended path exercising decode_wide/trace/relay/tcpip branches",
         %{conn: conn} do
      # Covers the rare-variant decode clauses: WIDE3-1, WIDE4-1, WIDE1-2,
      # TRACE2-1 through TRACE7-1, RELAY-1, RELAY-2, TCPIP, TCPIP*, and a
      # generic TRACE8-8 fallthrough that exercises the catch-all clauses.
      _packet =
        packet_fixture(%{
          sender: "PATHZ",
          base_callsign: "PATHZ",
          ssid: "0",
          received_at: DateTime.utc_now(),
          lat: Decimal.new("33.0"),
          lon: Decimal.new("-96.0"),
          has_position: true,
          path:
            "WIDE3-1,WIDE4-1,WIDE5-1,WIDE6-1,WIDE7-1,WIDE1-2,WIDE2-2,TRACE2-1,TRACE3-1,TRACE4-1,TRACE5-1,TRACE6-1,TRACE7-1,TRACE8-8,RELAY-1,RELAY-2,RELAYX,TCPIP,TCPIP*,TCPIPX,qAC,T2TEXAS"
        })

      {:ok, _lv, html} = live(conn, ~p"/info/PATHZ", on_error: :warn)
      assert html =~ "PATHZ"
    end
  end
end
