defmodule TeslaMateWeb.CarLive.Summary do
  use TeslaMateWeb, :live_view

  use Gettext, backend: TeslaMateWeb.Gettext

  alias TeslaMate.Vehicles.Vehicle.Summary
  alias TeslaMate.Vehicles.Vehicle
  alias TeslaMate.{Vehicles, Convert}

  on_mount {TeslaMateWeb.InitAssigns, :locale}

  @impl true
  def mount(_params, %{"summary" => %Summary{car: car} = summary} = session, socket) do
    if connected?(socket) do
      send(self(), :update_duration)
      send(self(), {:status, Vehicle.busy?(car.id)})

      :ok = Vehicles.subscribe_to_summary(car.id)
      :ok = Vehicles.subscribe_to_fetch(car.id)
    end

    assigns = %{
      car: car,
      summary: summary,
      fetch_status: Vehicle.busy?(car.id),
      fetch_start: 0,
      fetch_timer: nil,
      settings: session["settings"],
      translate_state: &translate_state/1,
      duration: humanize_duration(summary.since),
      error: nil,
      error_timeout: nil,
      loading: false
    }

    {:ok, assign(socket, assigns)}
  end

  @impl true
  def handle_event("suspend_logging", _val, socket) do
    cancel_timer(socket.assigns.error_timeout)
    send(self(), :suspend_logging)
    {:noreply, assign(socket, loading: true)}
  end

  def handle_event("resume_logging", _val, socket) do
    send(self(), :resume_logging)
    {:noreply, assign(socket, loading: true)}
  end

  @impl true
  def handle_info(:update_duration, socket) do
    Process.send_after(self(), :update_duration, :timer.seconds(1))
    {:noreply, assign(socket, duration: humanize_duration(socket.assigns.summary.since))}
  end

  def handle_info(:resume_logging, socket) do
    :ok = Vehicles.resume_logging(socket.assigns.car.id)
    {:noreply, socket}
  end

  def handle_info(:suspend_logging, socket) do
    assigns =
      case Vehicles.suspend_logging(socket.assigns.car.id) do
        :ok ->
          %{error: nil, error_timeout: nil, loading: false}

        {:error, reason} ->
          %{
            error: error_to_str(reason),
            error_timeout: Process.send_after(self(), :hide_error, 5_000),
            loading: false
          }
      end

    {:noreply, assign(socket, assigns)}
  end

  def handle_info(:hide_error, socket) do
    {:noreply, assign(socket, error: nil, error_timeout: nil)}
  end

  def handle_info(%Summary{since: since} = summary, socket) do
    {:noreply,
     assign(socket, summary: summary, duration: humanize_duration(since), loading: false)}
  end

  def handle_info({:status, true}, socket) do
    cancel_timer(socket.assigns.fetch_timer)

    assigns = %{
      fetch_status: true,
      fetch_start: System.monotonic_time(),
      fetch_timer: nil
    }

    {:noreply, assign(socket, assigns)}
  end

  # Note: this must be smaller than the @driving_interval
  @min_spinner_visibility_ms 1000

  def handle_info({:status, false}, socket) do
    fetch_duration =
      (System.monotonic_time() - socket.assigns.fetch_start) /
        System.convert_time_unit(1, :millisecond, :native)

    assigns =
      case @min_spinner_visibility_ms - fetch_duration do
        diff when 0 < diff ->
          %{fetch_timer: Process.send_after(self(), :set_status_to_false, round(diff))}

        _ ->
          %{fetch_status: false}
      end

    {:noreply, assign(socket, assigns)}
  end

  def handle_info(:set_status_to_false, socket) do
    {:noreply, assign(socket, fetch_status: false)}
  end

  def format_tpms(bar, :psi) when is_number(bar) do
    "#{Float.round(bar * 14.5038, 1)} PSI"
  end

  def format_tpms(bar, :bar) when is_number(bar) do
    "#{Float.round(bar, 1)} Bar"
  end

  defp translate_state(:start), do: ""
  defp translate_state(:driving), do: gettext("driving")
  defp translate_state(:charging), do: gettext("charging")
  defp translate_state(:updating), do: gettext("updating")
  defp translate_state(:suspended), do: gettext("falling asleep")
  defp translate_state(:online), do: gettext("online")
  defp translate_state(:offline), do: gettext("offline")
  defp translate_state(:asleep), do: gettext("asleep")
  defp translate_state(:unavailable), do: gettext("unavailable")

  defp error_to_str(:unlocked), do: gettext("Car is unlocked")
  defp error_to_str(:doors_open), do: gettext("Doors are open")
  defp error_to_str(:trunk_open), do: gettext("Trunk is open")
  defp error_to_str(:sentry_mode), do: gettext("Sentry mode is enabled")
  defp error_to_str(:preconditioning), do: gettext("Preconditioning")
  defp error_to_str(:dogmode), do: gettext("Dog mode is enabled")
  defp error_to_str(:user_present), do: gettext("Driver present")
  defp error_to_str(:downloading_update), do: gettext("Downloading update")
  defp error_to_str(:update_in_progress), do: gettext("Update in progress")
  defp error_to_str(:timeout), do: gettext("Timeout")
  defp error_to_str(_other), do: gettext("An error occurred")

  defp cancel_timer(nil), do: :ok
  defp cancel_timer(ref) when is_reference(ref), do: Process.cancel_timer(ref)

  defp humanize_duration(nil), do: nil

  defp humanize_duration(date) do
    case DateTime.utc_now() |> DateTime.diff(date, :second) do
      dur when dur < 5 -> nil
      dur when dur > 60 -> dur |> Convert.sec_to_str() |> Enum.reject(&String.ends_with?(&1, "s"))
      dur -> dur |> Convert.sec_to_str()
    end
  end
end
