defmodule Microwaveprop.Repo.Migrations.FixEnrichmentStatusFromActualData do
  use Ecto.Migration

  def up do
    # First, backfill positions from grid squares where missing.
    # Done in Elixir since Maidenhead decoding isn't available in SQL.
    flush()
    backfill_positions_from_grids()

    # Reset all "complete" statuses to "pending", then re-mark as "complete"
    # only where actual data exists. This corrects the initial migration which
    # assumed queued=true meant data exists.

    # Mark contacts without pos1 as unavailable for everything
    execute """
    UPDATE qsos SET
      hrrr_status = 'unavailable',
      weather_status = 'unavailable',
      terrain_status = 'unavailable',
      iemre_status = 'unavailable'
    WHERE pos1 IS NULL
    """

    # Mark contacts without pos2 as unavailable for terrain
    execute """
    UPDATE qsos SET terrain_status = 'unavailable'
    WHERE pos2 IS NULL AND pos1 IS NOT NULL
    """

    # Terrain: check actual terrain_profiles table
    execute """
    UPDATE qsos SET terrain_status = 'pending'
    WHERE terrain_status = 'complete'
      AND id NOT IN (SELECT qso_id FROM terrain_profiles)
      AND pos1 IS NOT NULL AND pos2 IS NOT NULL
    """

    # HRRR: reset to pending where no nearby profile exists
    # (checking exact match is too slow for migration, reset all and let
    #  the backfill dashboard / page views re-enqueue as needed)
    execute """
    UPDATE qsos SET hrrr_status = 'pending'
    WHERE hrrr_status = 'complete'
      AND pos1 IS NOT NULL
      AND NOT EXISTS (
        SELECT 1 FROM hrrr_profiles h
        WHERE h.lat BETWEEN (qsos.pos1->>'lat')::float - 0.05
                        AND (qsos.pos1->>'lat')::float + 0.05
          AND h.lon BETWEEN COALESCE(qsos.pos1->>'lon', qsos.pos1->>'lng')::float - 0.05
                        AND COALESCE(qsos.pos1->>'lon', qsos.pos1->>'lng')::float + 0.05
          AND h.valid_time BETWEEN qsos.qso_timestamp - interval '1 hour'
                             AND qsos.qso_timestamp + interval '1 hour'
      )
    """

    # Weather: reset to pending where no surface observations exist nearby
    execute """
    UPDATE qsos SET weather_status = 'pending'
    WHERE weather_status = 'complete'
      AND pos1 IS NOT NULL
      AND NOT EXISTS (
        SELECT 1 FROM surface_observations o
        JOIN weather_stations ws ON ws.id = o.station_id
        WHERE ws.lat BETWEEN (qsos.pos1->>'lat')::float - 3
                        AND (qsos.pos1->>'lat')::float + 3
          AND ws.lon BETWEEN COALESCE(qsos.pos1->>'lon', qsos.pos1->>'lng')::float - 3
                        AND COALESCE(qsos.pos1->>'lon', qsos.pos1->>'lng')::float + 3
          AND o.observed_at BETWEEN qsos.qso_timestamp - interval '6 hours'
                              AND qsos.qso_timestamp + interval '6 hours'
      )
    """
  end

  def down do
    # No rollback — statuses are now more accurate
    :ok
  end

  defp backfill_positions_from_grids do
    import Ecto.Query

    contacts =
      from(q in "qsos",
        where: is_nil(q.pos1) and not is_nil(q.grid1),
        select: %{id: q.id, grid1: q.grid1, grid2: q.grid2}
      )
      |> repo().all()

    for c <- contacts do
      pos1 = grid_to_pos(c.grid1)
      pos2 = grid_to_pos(c.grid2)

      if pos1 do
        distance =
          if pos1 && pos2 do
            haversine(pos1["lat"], pos1["lon"], pos2["lat"], pos2["lon"])
            |> round()
          end

        updates =
          [pos1: pos1]
          |> then(fn u -> if pos2, do: [{:pos2, pos2} | u], else: u end)
          |> then(fn u -> if distance, do: [{:distance_km, distance} | u], else: u end)
          |> Keyword.put(:updated_at, DateTime.truncate(DateTime.utc_now(), :second))

        from(q in "qsos", where: q.id == ^c.id)
        |> repo().update_all(set: updates)
      end
    end
  end

  defp grid_to_pos(nil), do: nil

  defp grid_to_pos(grid) do
    case Microwaveprop.Radio.Maidenhead.to_latlon(grid) do
      {:ok, {lat, lon}} -> %{"lat" => lat, "lon" => lon}
      _ -> nil
    end
  end

  defp haversine(lat1, lon1, lat2, lon2) do
    dlat = (lat2 - lat1) * :math.pi() / 180
    dlon = (lon2 - lon1) * :math.pi() / 180
    rlat1 = lat1 * :math.pi() / 180
    rlat2 = lat2 * :math.pi() / 180

    a =
      :math.sin(dlat / 2) ** 2 +
        :math.cos(rlat1) * :math.cos(rlat2) * :math.sin(dlon / 2) ** 2

    2 * 6371.0 * :math.asin(:math.sqrt(a))
  end
end
