defmodule AprsmeWeb.WeatherLive.CallsignView do
  @moduledoc false
  use AprsmeWeb, :live_view
  use Gettext, backend: AprsmeWeb.Gettext

  import Phoenix.LiveView, only: [push_event: 3, connected?: 1]

  alias Aprsme.Callsign
  alias AprsmeWeb.MapLive.PacketUtils
  alias AprsmeWeb.TimeUtils
  alias AprsmeWeb.WeatherUnits

  @impl true
  def mount(%{"callsign" => callsign}, _session, socket) do
    normalized_callsign = Callsign.normalize(callsign)

    # Subscribe to weather-specific topic for targeted updates
    if connected?(socket) do
      weather_topic = "weather:#{normalized_callsign}"
      Phoenix.PubSub.subscribe(Aprsme.PubSub, weather_topic)
    end

    weather_packet = get_latest_weather_packet(normalized_callsign)
    {start_time, end_time} = default_time_range()
    weather_history = get_weather_history(normalized_callsign, start_time, end_time)

    # Get locale from socket assigns (set by LocaleHook)
    locale = Map.get(socket.assigns, :locale, "en")
    unit_labels = WeatherUnits.unit_labels(locale)

    weather_history_json = convert_weather_history_to_json(weather_history, locale)

    # Add chart labels for translation with locale-aware units
    chart_labels = %{
      temp_title: gettext("Temperature & Dew Point (%{unit})", unit: unit_labels.temperature),
      temp_label: gettext("Temperature (%{unit})", unit: unit_labels.temperature),
      dew_label: gettext("Dew Point (%{unit})", unit: unit_labels.temperature),
      humidity_title: gettext("Humidity (%)"),
      humidity_label: gettext("Humidity (%)"),
      pressure_title: gettext("Pressure (mb)"),
      pressure_label: gettext("Pressure (mb)"),
      wind_title: gettext("Wind (%{unit})", unit: unit_labels.wind_speed),
      wind_label: gettext("Wind Speed (%{unit})", unit: unit_labels.wind_speed),
      gust_label: gettext("Wind Gust (%{unit})", unit: unit_labels.wind_speed),
      time: gettext("Time"),
      degf: unit_labels.temperature,
      percent: gettext("%"),
      mb: gettext("mb")
    }

    socket =
      socket
      |> assign(:callsign, normalized_callsign)
      |> assign(:weather_packet, weather_packet)
      |> assign(:page_title, "Weather for #{normalized_callsign}")
      |> assign(:weather_history, weather_history)
      |> assign(:weather_history_json, weather_history_json)
      |> assign(:chart_labels, chart_labels)

    {:ok, socket}
  end

  @impl true
  def handle_info({:weather_packet, packet}, socket) do
    # Check if this packet is actually newer than what we have
    current_packet = socket.assigns.weather_packet

    if should_update_weather?(current_packet, packet) do
      update_weather_data(socket)
    else
      {:noreply, socket}
    end
  end

  # Keep the old handler for backward compatibility
  def handle_info({:postgres_packet, packet}, socket) do
    # Only process if it's a packet with weather data for this callsign
    has_weather_data =
      not is_nil(packet.temperature) or not is_nil(packet.humidity) or
        not is_nil(packet.pressure) or not is_nil(packet.wind_speed) or
        not is_nil(packet.wind_direction) or not is_nil(packet.rain_1h)

    if packet.sender == socket.assigns.callsign && has_weather_data do
      handle_info({:weather_packet, packet}, socket)
    else
      {:noreply, socket}
    end
  end

  def handle_info(_message, socket), do: {:noreply, socket}

  defp should_update_weather?(nil, _new_packet), do: true

  defp should_update_weather?(current_packet, new_packet) do
    new_received_at = Map.get(new_packet, :received_at) || Map.get(new_packet, "received_at")
    current_received_at = Map.get(current_packet, :received_at) || Map.get(current_packet, "received_at")

    compare_timestamps(new_received_at, current_received_at)
  end

  defp compare_timestamps(nil, _), do: false
  defp compare_timestamps(_, nil), do: true

  defp compare_timestamps(new_time, current_time) when is_binary(new_time) and is_binary(current_time) do
    new_time > current_time
  end

  defp compare_timestamps(new_time, current_time) when is_binary(new_time) do
    case DateTime.from_iso8601(new_time <> "Z") do
      {:ok, new_dt, _} -> DateTime.after?(new_dt, current_time)
      _ -> false
    end
  end

  defp compare_timestamps(new_time, current_time) when is_binary(current_time) do
    case DateTime.from_iso8601(current_time <> "Z") do
      {:ok, current_dt, _} -> DateTime.after?(new_time, current_dt)
      _ -> true
    end
  end

  defp compare_timestamps(new_time, current_time) do
    DateTime.after?(new_time, current_time)
  end

  defp update_weather_data(socket) do
    # Fetch updated data only once
    weather_packet = get_latest_weather_packet(socket.assigns.callsign)
    {start_time, end_time} = default_time_range()
    weather_history = get_weather_history(socket.assigns.callsign, start_time, end_time)

    # Get locale from socket assigns
    locale = Map.get(socket.assigns, :locale, "en")

    weather_history_json = convert_weather_history_to_json(weather_history, locale)

    socket =
      socket
      |> assign(:weather_packet, weather_packet)
      |> assign(:weather_history, weather_history)
      |> assign(:weather_history_json, weather_history_json)

    # Push event to update all charts with new data
    socket = push_event(socket, "update_weather_charts", %{weather_history: weather_history_json})

    {:noreply, socket}
  end

  defp get_latest_weather_packet(callsign) do
    # Use optimized cached query that checks recent data first
    Aprsme.Packets.get_latest_weather_packet(callsign)
  end

  defp get_weather_history(callsign, start_time, end_time) do
    # Use cached queries to avoid repeated database hits
    Aprsme.Packets.get_weather_packets(callsign, start_time, end_time, %{limit: 500})
  end

  defp default_time_range do
    TimeUtils.time_range(:last_two_days)
  end

  defp calc_dew_point(temp, humidity) when is_number(temp) and is_number(humidity) do
    temp - (100 - humidity) / 5
  end

  defp calc_dew_point(_, _), do: nil

  defp convert_weather_history_to_json(weather_history, locale) do
    weather_history
    |> Enum.map(fn pkt ->
      dew_point =
        if is_number(pkt.temperature) and is_number(pkt.humidity) do
          calc_dew_point(pkt.temperature, pkt.humidity)
        end

      # Convert units based on locale
      {temp_value, _} = WeatherUnits.format_temperature(pkt.temperature, locale)
      {dew_value, _} = if dew_point, do: WeatherUnits.format_temperature(dew_point, locale), else: {nil, nil}
      {wind_speed_value, _} = WeatherUnits.format_wind_speed(pkt.wind_speed, locale)
      {rain_1h_value, _} = WeatherUnits.format_rain(pkt.rain_1h, locale)
      {rain_24h_value, _} = WeatherUnits.format_rain(pkt.rain_24h, locale)
      {rain_since_midnight_value, _} = WeatherUnits.format_rain(pkt.rain_since_midnight, locale)

      %{
        timestamp: pkt.received_at,
        temperature: temp_value,
        dew_point: dew_value,
        humidity: pkt.humidity,
        pressure: pkt.pressure,
        wind_direction: pkt.wind_direction,
        wind_speed: wind_speed_value,
        rain_1h: rain_1h_value,
        rain_24h: rain_24h_value,
        rain_since_midnight: rain_since_midnight_value,
        luminosity: get_in(pkt.data || %{}, ["luminosity"])
      }
    end)
    |> Jason.encode!()
  end

  @doc """
  Gets weather field value, returning "0" instead of "N/A" for missing numeric data.
  """
  def get_weather_field_zero(packet, key) do
    value = PacketUtils.get_weather_field(packet, key)

    # Return "0" for missing numeric fields, keep other values as-is
    case value do
      "N/A" -> "0"
      _ -> value
    end
  end

  # Weather formatters mapping
  @weather_formatters %{
    temperature: {&WeatherUnits.format_temperature/2, ""},
    wind_speed: {&WeatherUnits.format_wind_speed/2, " "},
    wind_gust: {&WeatherUnits.format_wind_speed/2, " "},
    rain_1h: {&WeatherUnits.format_rain/2, " "},
    rain_24h: {&WeatherUnits.format_rain/2, " "},
    rain_since_midnight: {&WeatherUnits.format_rain/2, " "}
  }

  @doc """
  Checks if a weather field has a valid value (not nil, "N/A", or empty).
  """
  def has_weather_field?(packet, field) do
    value = PacketUtils.get_weather_field(packet, field)

    case value do
      "N/A" -> false
      "" -> false
      _ -> true
    end
  end

  @doc """
  Formats weather values with appropriate units based on locale.
  """
  def format_weather_value(packet, key, locale) do
    value = PacketUtils.get_weather_field(packet, key)

    case value do
      v when v == "N/A" or is_nil(v) ->
        nil

      value when is_binary(value) ->
        format_string_value(value, key, locale)

      value when is_number(value) ->
        format_numeric_value(value, key, locale)
    end
  end

  defp format_string_value(value, key, locale) do
    case @weather_formatters[key] do
      {formatter, separator} ->
        case Float.parse(value) do
          {num_value, _} ->
            {converted_value, unit} = formatter.(num_value, locale)
            "#{converted_value}#{separator}#{unit}"

          :error ->
            nil
        end

      nil ->
        "#{value}"
    end
  end

  defp format_numeric_value(value, key, locale) do
    case @weather_formatters[key] do
      {formatter, separator} ->
        {converted_value, unit} = formatter.(value, locale)
        "#{converted_value}#{separator}#{unit}"

      nil ->
        "#{value}"
    end
  end
end
