defmodule MicrowavepropWeb.ContactLiveTest do
  use MicrowavepropWeb.ConnCase, async: true

  import Phoenix.LiveViewTest

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Repo
  alias Microwaveprop.Weather.HrrrNativeProfile
  alias Microwaveprop.Weather.NarrProfile

  setup do
    Req.Test.stub(Microwaveprop.Terrain.ElevationClient, fn conn ->
      Req.Test.json(conn, [])
    end)

    Req.Test.stub(Microwaveprop.Weather.HrrrClient, fn conn ->
      Plug.Conn.send_resp(conn, 404, "not found")
    end)

    Req.Test.stub(Microwaveprop.Weather.SolarClient, fn conn ->
      Req.Test.text(conn, "")
    end)

    :ok
  end

  defp create_contact(attrs \\ %{}) do
    default = %{
      station1: "W5XD",
      station2: "K5TR",
      qso_timestamp: ~U[2026-03-28 18:00:00Z],
      mode: "CW",
      band: Decimal.new("1296"),
      grid1: "EM12",
      grid2: "EM00",
      pos1: %{"lat" => 32.9, "lon" => -97.0},
      pos2: %{"lat" => 30.3, "lon" => -97.7},
      distance_km: Decimal.new("295")
    }

    {changeset_attrs, direct_attrs} = Map.split(Map.merge(default, attrs), [:user_submitted])

    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(direct_attrs)
      |> Ecto.Changeset.change(changeset_attrs)
      |> Repo.insert()

    contact
  end

  describe "Index" do
    test "renders page with Contacts heading", %{conn: conn} do
      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "Contacts"
    end

    test "shows total contact count in subtitle", %{conn: conn} do
      create_contact()
      create_contact(%{station1: "N5AC"})
      create_contact(%{station1: "K5TR"})

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "3 total"
    end

    test "table shows contact data", %{conn: conn} do
      create_contact()

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "W5XD"
      assert html =~ "K5TR"
      assert html =~ "CW"
      assert html =~ "1296"
    end

    test "row links to detail page", %{conn: conn} do
      contact = create_contact()

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ ~p"/contacts/#{contact.id}"
    end

    test "sort params order the table", %{conn: conn} do
      create_contact(%{station1: "ZZ9ZZ"})
      create_contact(%{station1: "AA1AA"})

      {:ok, _lv, html} = live(conn, ~p"/contacts?sort_params[station1]=asc")

      aa_pos = html |> :binary.match("AA1AA") |> elem(0)
      zz_pos = html |> :binary.match("ZZ9ZZ") |> elem(0)
      assert aa_pos < zz_pos
    end

    test "search by single callsign matches either station", %{conn: conn} do
      create_contact(%{station1: "W5LUA", station2: "W5HN"})
      create_contact(%{station1: "K5TR", station2: "N5AC"})

      {:ok, _lv, html} = live(conn, ~p"/contacts?search=W5LUA")
      # W5LUA appears in the matching row; K5TR/N5AC should not render in
      # the table body because the search filtered that contact out.
      assert html =~ "W5LUA"
      refute html =~ "N5AC"
    end

    test "pagination reaches later pages", %{conn: conn} do
      for i <- 1..25 do
        ts = DateTime.add(~U[2026-01-01 00:00:00Z], i * 3600, :second)
        create_contact(%{qso_timestamp: ts, station1: "W#{i}TEST"})
      end

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "Page"

      {:ok, _lv, html2} = live(conn, ~p"/contacts?page=2")
      assert html2 =~ "Page"
    end
  end

  describe "Index private visibility" do
    import Microwaveprop.AccountsFixtures

    test "anonymous viewer does not see private contacts in the table", %{conn: conn} do
      _private = create_contact(%{station1: "W5PRV", private: true})
      _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]})

      {:ok, _lv, html} = live(conn, ~p"/contacts")

      assert html =~ "W5PUB"
      refute html =~ "W5PRV"
    end

    test "owner sees their own private inline with Yes badge", %{conn: conn} do
      owner = user_fixture()
      _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id})
      _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]})
      conn = log_in_user(conn, owner)

      {:ok, _lv, html} = live(conn, ~p"/contacts")

      assert html =~ "W5PRV"
      assert html =~ "W5PUB"
      assert html =~ "badge-warning"
    end

    test "admin sees all private contacts", %{conn: conn} do
      admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
      _private = create_contact(%{station1: "W5PRV", private: true})
      _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]})
      conn = log_in_user(conn, admin)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "W5PRV"
      assert html =~ "W5PUB"
    end

    test "non-owning user does not see others' private", %{conn: conn} do
      owner = user_fixture()
      viewer = user_fixture()
      _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id})
      _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]})
      conn = log_in_user(conn, viewer)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ "W5PUB"
      refute html =~ "W5PRV"
    end
  end

  describe "Index private column visibility" do
    import Microwaveprop.AccountsFixtures

    test "column hidden for anonymous viewer when no private contacts exist", %{conn: conn} do
      _public = create_contact(%{station1: "W5PUB"})

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      refute html =~ ">Private<"
    end

    test "column hidden for anonymous viewer even when others have private contacts", %{conn: conn} do
      _private = create_contact(%{station1: "W5PRV", private: true})
      _public = create_contact(%{station1: "W5PUB"})

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      refute html =~ ">Private<"
    end

    test "column hidden for a user who has no private contacts of their own", %{conn: conn} do
      owner = user_fixture()
      viewer = user_fixture()
      _others_private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id})
      conn = log_in_user(conn, viewer)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      refute html =~ ">Private<"
    end

    test "column visible for a user with at least one of their own private contacts", %{conn: conn} do
      owner = user_fixture()
      _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id})
      conn = log_in_user(conn, owner)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ ">Private<"
    end

    test "column hidden for admin when there are no private contacts anywhere", %{conn: conn} do
      admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
      _public = create_contact(%{station1: "W5PUB"})
      conn = log_in_user(conn, admin)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      refute html =~ ">Private<"
    end

    test "column visible for admin as soon as any private contact exists", %{conn: conn} do
      admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
      _private = create_contact(%{station1: "W5PRV", private: true})
      conn = log_in_user(conn, admin)

      {:ok, _lv, html} = live(conn, ~p"/contacts")
      assert html =~ ">Private<"
    end
  end

  describe "Show private visibility" do
    import Microwaveprop.AccountsFixtures

    test "anonymous viewer 404s on a private contact", %{conn: conn} do
      contact = create_contact(%{private: true})

      assert_raise Ecto.NoResultsError, fn ->
        live(conn, ~p"/contacts/#{contact.id}")
      end
    end

    test "non-owning user 404s on a private contact", %{conn: conn} do
      owner = user_fixture()
      other = user_fixture()
      contact = create_contact(%{private: true, user_id: owner.id})
      conn = log_in_user(conn, other)

      assert_raise Ecto.NoResultsError, fn ->
        live(conn, ~p"/contacts/#{contact.id}")
      end
    end

    test "owner can view their own private contact", %{conn: conn} do
      owner = user_fixture()
      contact = create_contact(%{private: true, user_id: owner.id})
      conn = log_in_user(conn, owner)

      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "W5XD"
    end

    test "admin can view anyone's private contact", %{conn: conn} do
      owner = user_fixture()
      admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
      contact = create_contact(%{private: true, user_id: owner.id})
      conn = log_in_user(conn, admin)

      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "W5XD"
    end
  end

  describe "Show" do
    test "renders contact detail fields", %{conn: conn} do
      contact = create_contact()

      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "W5XD"
      assert html =~ "K5TR"
      # Mode, band, grids are shown in the elevation profile section (requires SRTM)
      # or in the header subtitle
      assert html =~ "2026-03-28"
    end

    test "shows surface observations section", %{conn: conn} do
      contact = create_contact()
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "Surface Observations"
    end

    test "shows sounding section", %{conn: conn} do
      contact = create_contact()
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "Soundings"
    end

    test "shows solar index section", %{conn: conn} do
      contact = create_contact()
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "Solar Conditions"
    end

    test "renders with sort assigns", %{conn: conn} do
      contact = create_contact()
      {:ok, lv, html} = live(conn, ~p"/contacts/#{contact.id}")

      # Page renders without error
      assert html =~ "Surface Observations"
      assert html =~ "Soundings"

      # Sort assigns are initialized (no crash on render)
      assert render(lv) =~ contact.station1
    end

    # Band display on the detail page: below 10 GHz renders in MHz, at/above
    # 10 GHz renders in GHz. Amateur microwave convention — 2304 is "2304",
    # 3456 is "3456", 5760 is "5760", not "2.3 GHz" etc.
    test "renders sub-10-GHz bands in MHz", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("432")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "432 MHz"
    end

    test "renders 50 MHz in MHz", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("50")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "50 MHz"
    end

    test "renders 1296 MHz in MHz", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("1296")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "1296 MHz"
      refute html =~ "1.3 GHz"
    end

    test "renders 2304 MHz in MHz", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("2304")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "2304 MHz"
      refute html =~ "2.3 GHz"
    end

    test "renders 10 GHz in GHz (threshold)", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("10000")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "10 GHz"
    end

    test "renders 24 GHz in GHz", %{conn: conn} do
      contact = create_contact(%{band: Decimal.new("24000")})
      {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
      assert html =~ "24 GHz"
    end

    test "raises for bad UUID", %{conn: conn} do
      assert_raise Ecto.NoResultsError, fn ->
        live(conn, ~p"/contacts/#{Ecto.UUID.generate()}")
      end
    end

    test "shows atmospheric profile section with NARR source for pre-HRRR contact", %{conn: conn} do
      # Pre-2014 contact — HRRR is unavailable, NARR is the fallback source.
      contact =
        create_contact(%{
          qso_timestamp: ~U[2010-06-15 18:00:00Z],
          pos1: %{"lat" => 32.9, "lon" => -97.0},
          pos2: %{"lat" => 30.3, "lon" => -97.7}
        })

      {:ok, _} =
        Repo.insert(%NarrProfile{
          valid_time: ~U[2010-06-15 18:00:00Z],
          lat: 33.0,
          lon: -97.0,
          profile: [
            %{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => 10.0, "hght" => 1500.0}
          ],
          surface_temp_c: 25.0,
          surface_dewpoint_c: 18.0,
          surface_pressure_mb: 1012.0,
          hpbl_m: 1200.0,
          pwat_mm: 30.0,
          surface_refractivity: 320.0,
          min_refractivity_gradient: -50.0,
          ducting_detected: false
        })

      {:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")

      html = render_async(lv)
      assert html =~ "Atmospheric Profile"
      assert html =~ "NARR"
      # Collapsed summary shows HPBL / PWAT / lat,lon from the injected NARR row.
      assert html =~ "1200"
      assert html =~ "33.0"
    end

    test "skew-T uses native HRRR profile when backfilled", %{conn: conn} do
      # Native backfill has reached this hour — use its full-atmosphere
      # profile for the skew-T instead of the 700 mb-capped pressure-level one.
      contact =
        create_contact(%{
          qso_timestamp: ~U[2024-06-15 18:00:00Z],
          pos1: %{"lat" => 32.9, "lon" => -97.0},
          pos2: %{"lat" => 30.3, "lon" => -97.7}
        })

      # NARR row keeps the atmospheric profile section rendering with its
      # aggregate metadata (HPBL / PWAT / surface scalars).
      {:ok, _} =
        Repo.insert(%NarrProfile{
          valid_time: ~U[2024-06-15 18:00:00Z],
          lat: 32.9,
          lon: -97.0,
          profile: [%{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => 10.0, "hght" => 1500.0}],
          surface_temp_c: 25.0,
          surface_dewpoint_c: 18.0,
          surface_pressure_mb: 1012.0,
          hpbl_m: 1200.0,
          pwat_mm: 30.0,
          surface_refractivity: 320.0,
          min_refractivity_gradient: -50.0,
          ducting_detected: false
        })

      # Native profile at the same point/time. `pressure_pa: [..., 30_000.0]`
      # corresponds to 300 mb — a level the pressure-level profile cannot
      # cover because HRRR/NARR pressure-level data stops at 700 mb historically.
      {:ok, _} =
        %HrrrNativeProfile{}
        |> HrrrNativeProfile.changeset(%{
          valid_time: ~U[2024-06-15 18:00:00Z],
          run_time: ~U[2024-06-15 18:00:00Z],
          lat: 32.9,
          lon: -97.0,
          level_count: 3,
          heights_m: [10.0, 1500.0, 9500.0],
          temp_k: [298.0, 288.0, 228.0],
          spfh: [0.012, 0.006, 0.0001],
          pressure_pa: [101_000.0, 85_000.0, 30_000.0],
          u_wind_ms: [2.0, 5.0, 20.0],
          v_wind_ms: [1.0, 2.0, 5.0],
          tke_m2s2: [0.5, 0.2, 0.05]
        })
        |> Repo.insert()

      {:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
      html = render_async(lv)

      assert html =~ "HRRR native"
      # 300 mb comes from the native profile — pressure-level data capped at 700.
      assert html =~ "300.0"
    end
  end
end
