defmodule MicrowavepropWeb.EmeLiveTest do
  use MicrowavepropWeb.ConnCase, async: true
  use ExUnitProperties

  import Phoenix.LiveViewTest

  alias Microwaveprop.Propagation.BandConfig

  setup do
    # CallsignClient.locate/1 falls through to Qrz.Client for any input
    # that isn't a grid or coord pair. Stub the QRZ HTTP plug so the
    # "unknown callsign" path returns a deterministic error instead of
    # raising about a missing stub.
    Req.Test.stub(Microwaveprop.Qrz.Client, fn conn ->
      Plug.Conn.send_resp(conn, 404, "not found")
    end)

    :ok
  end

  describe "GET /eme" do
    test "renders the empty form when no source is supplied", %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/eme")

      assert html =~ "EME Calculator"
      assert html =~ "Station (callsign, grid, or"
      assert html =~ "Calculate"
      # No aim readout yet.
      refute html =~ "Antenna aim"
    end

    test "renders the live Moon aim + link budget when a grid source is provided", %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/eme?source=EM13&band=1296&tx_power_dbm=50&tx_gain_dbi=30")

      assert html =~ "Antenna aim"
      assert html =~ "Link budget"
      assert html =~ "EM13"
      assert html =~ "Azimuth"
      assert html =~ "Elevation"
      assert html =~ "Slant range"
      # Path loss should be in the high 200s (dB) for 1296 MHz.
      assert html =~ "dB"
    end

    test "submitting the form patches the URL with the supplied params", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/eme")

      view
      |> form("form",
        source: "EM13",
        band: "432",
        tx_power_dbm: "60",
        tx_gain_dbi: "25",
        bandwidth_hz: "100",
        t_sys_k: "150"
      )
      |> render_submit()

      # Flush any pending assign updates to catch redirects/patches.
      html = render(view)

      assert html =~ "EM13"
      assert html =~ "Antenna aim"
    end

    test "coordinate pair 'lat,lon' resolves without touching the callsign API", %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/eme?source=32.9,-97.0&band=1296")
      assert html =~ "Antenna aim"
      assert html =~ "32.9"
      assert html =~ "-97.0"
    end

    test "invalid Maidenhead grid surfaces a validation error", %{conn: conn} do
      # A 4-char prefix that passes the loose regex but trips
      # Maidenhead.to_latlon's strict validator (subsquare "ZZ" isn't A-X).
      {:ok, _view, html} = live(conn, ~p"/eme?source=EM12ZZ")

      assert html =~ "Invalid grid square"
      refute html =~ "Antenna aim"
    end

    test "unknown callsign input surfaces a 'Could not find' error", %{conn: conn} do
      # A random string that's neither a coord nor a maidenhead grid falls
      # into the CallsignClient.locate path. The test DB has no cached
      # entry and the real QRZ API is unreachable in tests → error branch.
      {:ok, _view, html} = live(conn, ~p"/eme?source=NOSUCHCALL")

      assert html =~ "Could not find"
      refute html =~ "Antenna aim"
    end

    test "empty source renders the 'enter callsign or grid' empty-state message",
         %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/eme?source=&band=1296")

      assert html =~ "Enter your callsign or grid"
      refute html =~ "Antenna aim"
    end

    test "typing into form emits the WebGL globe hook state via push_event",
         %{conn: conn} do
      # Initial render: empty source, no eme:update. Triggering a form
      # change with a valid grid should both patch the URL and push the
      # eme:update payload to the hook.
      {:ok, view, _html} = live(conn, ~p"/eme")

      render_change(
        form(view, "form",
          source: "EM13",
          band: "1296",
          tx_power_dbm: "50",
          tx_gain_dbi: "30",
          bandwidth_hz: "100",
          t_sys_k: "150"
        )
      )

      assert_push_event(view, "eme:update", %{lat: _, lon: _, az: _, el: _})
    end

    test "renders the Earth–Moon geometry WebGL hook with initial station geometry", %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/eme?source=EM13&band=1296")

      assert html =~ "Earth–Moon geometry"
      # The WebGL globe is rendered by a LiveView hook — assert the
      # hook element + the initial station/moon data attributes that
      # feed the Three.js scene on mount.
      assert html =~ ~s(id="eme-globe")
      assert html =~ ~s(phx-hook="EmeGlobe")
      assert html =~ ~s(phx-update="ignore")
      assert html =~ ~s(data-station-lat=")
      assert html =~ ~s(data-station-lon=")
      assert html =~ ~s(data-moon-az=")
      assert html =~ ~s(data-moon-el=")
    end
  end

  describe "property: form validation across valid/invalid source inputs" do
    property "valid inputs render an Antenna aim readout, invalid inputs render a validation error" do
      bands = Enum.map(BandConfig.band_options(), fn {_label, value} -> value end)

      # A valid-input sample is a Maidenhead grid + any configured band;
      # an invalid-input sample is a non-grid/non-callsign string that
      # neither resolves as coords nor passes the maidenhead prefix
      # regex. Both categories are exercised in the same property.
      valid_source_gen =
        StreamData.member_of(["EM13", "FM19", "EN00", "DM04", "FN31", "CM87", "EL09"])

      invalid_source_gen =
        StreamData.member_of(["ZZ99", "ZZ00", "Z1", "@@@@", "ZZZZZZ"])

      check all(
              band <- StreamData.member_of(bands),
              valid_source <- valid_source_gen,
              invalid_source <- invalid_source_gen,
              max_runs: 10
            ) do
        conn = Phoenix.ConnTest.build_conn()

        {:ok, _valid_view, valid_html} =
          live(conn, ~p"/eme?source=#{valid_source}&band=#{band}")

        assert valid_html =~ "Antenna aim", """
        Expected a valid input to produce an Antenna aim readout.
        source=#{inspect(valid_source)} band=#{band}
        """

        {:ok, _invalid_view, invalid_html} =
          live(conn, ~p"/eme?source=#{invalid_source}&band=#{band}")

        # Either the loose maidenhead regex catches it (invalid grid
        # error) or it falls through to callsign lookup (not found).
        assert invalid_html =~ "Invalid grid square" or
                 invalid_html =~ "Could not find",
               """
               Expected an invalid input to produce a validation error.
               source=#{inspect(invalid_source)} band=#{band}
               """

        refute invalid_html =~ "Antenna aim"
      end
    end
  end
end
