defmodule MicrowavepropWeb.StatusLiveTest do
  use MicrowavepropWeb.ConnCase, async: false

  import Microwaveprop.AccountsFixtures
  import Phoenix.LiveViewTest

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

  setup %{conn: conn} do
    Cache.invalidate({MicrowavepropWeb.StatusLive, :count_unprocessed})
    Cache.invalidate({MicrowavepropWeb.StatusLive, :stats})
    Cache.invalidate({MicrowavepropWeb.StatusLive, :db_stats})
    Cache.invalidate({MicrowavepropWeb.StatusLive, :grid_tasks_stats})

    user = user_fixture()
    {:ok, admin} = user |> Ecto.Changeset.change(%{is_admin: true}) |> Repo.update()
    conn = log_in_user(conn, admin)
    {:ok, conn: conn}
  end

  describe "grid_tasks panel — Rust prop-grid-rs worker" do
    defp insert_grid_task(attrs) do
      id = Ecto.UUID.bingenerate()
      now = DateTime.truncate(DateTime.utc_now(), :second)

      row =
        Map.merge(
          %{
            id: id,
            run_time: ~N[2026-04-19 19:00:00],
            forecast_hour: 5,
            valid_time: ~N[2026-04-20 00:00:00],
            status: "queued",
            attempt: 0,
            inserted_at: now,
            updated_at: now
          },
          attrs
        )

      {1, nil} = Repo.insert_all("grid_tasks", [row])
      id
    end

    test "shows an in-flight row with the forecast hour it's working on", %{conn: conn} do
      insert_grid_task(%{
        status: "running",
        forecast_hour: 7,
        attempt: 1,
        claimed_at: DateTime.truncate(DateTime.utc_now(), :microsecond)
      })

      {:ok, lv, _html} = live(conn, ~p"/status")
      panel = lv |> element("#grid-tasks-panel") |> render()
      assert panel =~ "prop-grid-rs"
      assert panel =~ "f07"
      assert panel =~ "running"
    end

    test "shows queued and completed counts", %{conn: conn} do
      insert_grid_task(%{forecast_hour: 1, status: "queued"})
      insert_grid_task(%{forecast_hour: 2, status: "queued"})

      insert_grid_task(%{
        forecast_hour: 3,
        status: "done",
        completed_at: DateTime.truncate(DateTime.utc_now(), :microsecond)
      })

      {:ok, lv, _html} = live(conn, ~p"/status")
      panel = lv |> element("#grid-tasks-panel") |> render()

      assert panel =~ ~r/Queued[^0-9]*<\/th>.*?<td>2<\/td>/s
      assert panel =~ ~r/Done \(1h\)[^0-9]*<\/th>.*?<td>1<\/td>/s
    end
  end

  defp create_narr_candidate do
    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(%{
        station1: "W5TEST",
        station2: "K5TEST",
        qso_timestamp: ~U[2010-06-15 18:00:00Z],
        mode: "CW",
        band: Decimal.new("1296"),
        grid1: "EM12",
        grid2: "EM00",
        pos1: %{"lat" => 33.0, "lon" => -97.0},
        pos2: %{"lat" => 30.3, "lon" => -97.7},
        distance_km: Decimal.new("300")
      })
      |> Ecto.Changeset.change(%{hrrr_status: :unavailable})
      |> Repo.insert()

    contact
  end

  # NARR fetches land in era5_profiles (table rename is a follow-up migration).
  # The status UI labels this "NARR" now.
  test "NARR progress bar numerator is actual profile matches not total-minus-candidates", %{conn: conn} do
    create_narr_candidate()

    # No NARR profile yet — candidates = 1, done = 0
    {:ok, lv, _html} = live(conn, ~p"/status")
    narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
    assert narr_row =~ "NARR"
    assert narr_row =~ "0 / 1"

    # Insert a matching profile row — done should become 1
    {:ok, _} =
      Repo.insert(%NarrProfile{
        valid_time: ~U[2010-06-15 18:00:00Z],
        lat: 33.0,
        lon: -97.0,
        ducting_detected: false
      })

    Cache.invalidate({MicrowavepropWeb.StatusLive, :count_unprocessed})

    {:ok, lv, _html} = live(conn, ~p"/status")
    narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
    assert narr_row =~ "1 / 1"
  end

  # NARR serves pre-2014 contacts only (NCEI archive ends 2014-10-02). A
  # post-2014 contact with hrrr_status=:unavailable is a HRRR retry case,
  # not a NARR candidate, and must not inflate the NARR denominator.
  test "NARR progress excludes post-2014 hrrr-unavailable contacts", %{conn: conn} do
    # pre-2014 candidate (counts toward NARR)
    create_narr_candidate()

    # post-2014 hrrr-unavailable contact (does NOT count toward NARR)
    {:ok, _} =
      %Contact{}
      |> Contact.changeset(%{
        station1: "W5POST",
        station2: "K5POST",
        qso_timestamp: ~U[2020-06-15 18:00:00Z],
        mode: "CW",
        band: Decimal.new("1296"),
        grid1: "EM12",
        grid2: "EM00",
        pos1: %{"lat" => 33.0, "lon" => -97.0},
        pos2: %{"lat" => 30.3, "lon" => -97.7},
        distance_km: Decimal.new("300")
      })
      |> Ecto.Changeset.change(%{hrrr_status: :unavailable})
      |> Repo.insert()

    {:ok, lv, _html} = live(conn, ~p"/status")
    narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
    assert narr_row =~ "0 / 1"
  end

  # NARR analyses are archived at 3-hour marks (00/03/06/09/12/15/18/21 UTC).
  # `NarrClient.snap_to_analysis_hour/1` floors the QSO timestamp to the
  # previous 3-hour mark before fetch. The status numerator query must mirror
  # that snap — a naive ±30 min window around the raw qso_timestamp misses
  # the stored profile whenever the contact lands more than 30 min past the
  # last analysis hour.
  test "NARR progress counts contacts whose qso_timestamp doesn't land on a 3-hour analysis mark",
       %{conn: conn} do
    {:ok, _} =
      %Contact{}
      |> Contact.changeset(%{
        station1: "W5OFF",
        station2: "K5OFF",
        qso_timestamp: ~U[2010-06-15 18:50:00Z],
        mode: "CW",
        band: Decimal.new("1296"),
        grid1: "EM12",
        grid2: "EM00",
        pos1: %{"lat" => 33.0, "lon" => -97.0},
        pos2: %{"lat" => 30.3, "lon" => -97.7},
        distance_km: Decimal.new("300")
      })
      |> Ecto.Changeset.change(%{hrrr_status: :unavailable})
      |> Repo.insert()

    {:ok, _} =
      Repo.insert(%NarrProfile{
        valid_time: ~U[2010-06-15 18:00:00Z],
        lat: 33.0,
        lon: -97.0,
        ducting_detected: false
      })

    {:ok, lv, _html} = live(conn, ~p"/status")
    narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
    assert narr_row =~ "1 / 1"
  end

  # NARR eligibility is defined by qso_timestamp, not hrrr_status. A
  # pre-2014 contact whose hrrr_status is still :queued (the ping-pong
  # window before reconcile flips it) must still be counted — otherwise
  # the denominator misses rows for several cron cycles after submission.
  test "NARR candidates include pre-2014 contacts regardless of hrrr_status", %{conn: conn} do
    {:ok, _} =
      %Contact{}
      |> Contact.changeset(%{
        station1: "W5QUE",
        station2: "K5QUE",
        qso_timestamp: ~U[2010-06-15 18:00:00Z],
        mode: "CW",
        band: Decimal.new("1296"),
        grid1: "EM12",
        grid2: "EM00",
        pos1: %{"lat" => 33.0, "lon" => -97.0},
        pos2: %{"lat" => 30.3, "lon" => -97.7},
        distance_km: Decimal.new("300")
      })
      |> Ecto.Changeset.change(%{hrrr_status: :queued})
      |> Repo.insert()

    {:ok, lv, _html} = live(conn, ~p"/status")
    narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
    assert narr_row =~ "0 / 1"
  end
end
