defmodule MicrowavepropWeb.RoverLocationsLive do
  @moduledoc """
  Globally-shared list of rover-friendly (or bad) parking
  locations. Everyone sees the table; only logged-in users can add or
  edit entries (and only the creator can edit/delete their own).
  """
  use MicrowavepropWeb, :live_view
  use MicrowavepropWeb.LiveTableResource, schema: Microwaveprop.Rover.Location

  import Ecto.Query

  alias Microwaveprop.Accounts.User
  alias Microwaveprop.Radio.Maidenhead
  alias Microwaveprop.Repo
  alias Microwaveprop.Rover
  alias Microwaveprop.Rover.Location

  @valid_status_filters ~w(good bad)

  def table_options do
    %{sorting: %{default_sort: [inserted_at: :desc]}}
  end

  def fields do
    [
      id: %{label: "ID", hidden: true},
      user_id: %{label: "Owner", hidden: true},
      status: %{label: "Status", sortable: true, renderer: &status_cell/1},
      lat: %{label: "Location", sortable: true, renderer: &location_cell/2},
      lon: %{label: "Lon", hidden: true},
      notes: %{label: "Notes", searchable: true, renderer: &notes_cell/1},
      inserted_at: %{label: "Added (UTC)", sortable: true, renderer: &format_ts/1}
    ]
  end

  def filters, do: []

  @doc false
  # Called by LiveTable to get the base queryable. Filters by status when
  # the user picks one in the in-page select; nil means show all.
  def visible_query_provider(status_filter) do
    base = from(l in Location, as: :resource)

    case status_filter do
      "good" -> from(l in base, where: l.status == :good)
      "bad" -> from(l in base, where: l.status == :bad)
      _ -> base
    end
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(
       page_title: "Rover Locations",
       form: nil,
       editing_id: nil,
       grid_input: "",
       status_filter: nil
     )
     |> assign(:data_provider, {__MODULE__, :visible_query_provider, [nil]})}
  end

  @impl true
  def handle_event("filter_status", %{"value" => value}, socket) do
    status = parse_status(value)
    data_provider = {__MODULE__, :visible_query_provider, [status]}

    {:noreply,
     socket
     |> assign(:status_filter, status)
     |> assign(:data_provider, data_provider)
     |> reload()}
  end

  def handle_event("new", _params, socket) do
    if user = current_user(socket) do
      {:noreply, assign(socket, form: blank_form(user), editing_id: nil, grid_input: "")}
    else
      {:noreply, put_flash(socket, :error, "Sign in to add a location.")}
    end
  end

  def handle_event("edit", %{"id" => id}, socket) do
    user = current_user(socket)

    cond do
      is_nil(user) ->
        {:noreply, put_flash(socket, :error, "Sign in to edit a location.")}

      loc = editable_location(user, id) ->
        cs = Location.changeset(loc, %{})
        grid = Maidenhead.from_latlon(loc.lat, loc.lon, 6)
        {:noreply, assign(socket, form: to_form(cs), editing_id: id, grid_input: grid)}

      true ->
        {:noreply, put_flash(socket, :error, "You can only edit your own locations.")}
    end
  end

  def handle_event("cancel", _, socket) do
    {:noreply, assign(socket, form: nil, editing_id: nil, grid_input: "")}
  end

  def handle_event("validate", %{"location" => params} = event_params, socket) do
    target = event_params["_target"]
    {params, grid_input} = sync_grid_and_latlon(params, target, socket.assigns.grid_input)

    base = if socket.assigns.editing_id, do: location_for_edit(socket), else: %Location{}
    cs = base |> Location.changeset(params) |> Map.put(:action, :validate)
    {:noreply, assign(socket, form: to_form(cs), grid_input: grid_input)}
  end

  def handle_event("save", %{"location" => params}, socket) do
    user = current_user(socket)

    cond do
      is_nil(user) ->
        {:noreply, put_flash(socket, :error, "Sign in required.")}

      socket.assigns.editing_id ->
        save_update(socket, user, params)

      true ->
        save_create(socket, user, params)
    end
  end

  def handle_event("delete", %{"id" => id}, socket) do
    case current_user(socket) do
      %User{} = user ->
        case Rover.delete_location(user, id) do
          {:ok, _} ->
            {:noreply,
             socket
             |> put_flash(:info, "Location removed.")
             |> reload()}

          {:error, :not_found} ->
            {:noreply, put_flash(socket, :error, "You can only delete your own locations.")}
        end

      _ ->
        {:noreply, put_flash(socket, :error, "Sign in required.")}
    end
  end

  defp save_create(socket, user, params) do
    case Rover.create_location(user, params) do
      {:ok, _loc} ->
        {:noreply,
         socket
         |> assign(form: nil, editing_id: nil)
         |> put_flash(:info, "Location added.")
         |> reload()}

      {:error, cs} ->
        {:noreply, assign(socket, form: to_form(cs))}
    end
  end

  defp save_update(socket, user, params) do
    case Rover.update_location(user, socket.assigns.editing_id, params) do
      {:ok, _loc} ->
        {:noreply,
         socket
         |> assign(form: nil, editing_id: nil)
         |> put_flash(:info, "Location updated.")
         |> reload()}

      {:error, :not_found} ->
        {:noreply, put_flash(socket, :error, "You can only edit your own locations.")}

      {:error, cs} ->
        {:noreply, assign(socket, form: to_form(cs))}
    end
  end

  defp location_for_edit(%{assigns: %{editing_id: id}}) when is_binary(id) do
    Repo.get(Location, id) || %Location{}
  end

  defp location_for_edit(_), do: %Location{}

  defp editable_location(%User{is_admin: true}, id) do
    Repo.get(Location, id)
  end

  defp editable_location(%User{id: user_id}, id) do
    case Repo.get(Location, id) do
      %Location{user_id: ^user_id} = loc -> loc
      _ -> nil
    end
  end

  defp blank_form(_user) do
    %Location{} |> Location.changeset(%{}) |> to_form()
  end

  # Re-runs the LiveTable fetch with the current data_provider + options.
  # Cheaper and simpler than push_patch — preserves search/sort/page state
  # without round-tripping through the URL.
  defp reload(socket) do
    options = socket.assigns.options
    data_provider = socket.assigns.data_provider
    table_options = get_merged_table_options()

    {resources, updated_options} = fetch_resources(options, data_provider)

    socket
    |> assign_to_socket(resources, table_options)
    |> assign(:options, updated_options)
  end

  defp parse_status(value) when value in @valid_status_filters, do: value
  defp parse_status(_), do: nil

  # Two-way grid <-> lat/lon sync. `_target` from the phx-change event
  # tells us which field the user actually edited; we mirror the change
  # into the other side. Unparseable input leaves the other side alone.
  defp sync_grid_and_latlon(params, ["location", "grid"], _old_grid) do
    raw = params |> Map.get("grid", "") |> to_string() |> String.trim()

    case Maidenhead.to_latlon(raw) do
      {:ok, {lat, lon}} ->
        params =
          params
          |> Map.put("lat", format_coord(lat))
          |> Map.put("lon", format_coord(lon))

        {params, raw}

      :error ->
        {params, raw}
    end
  end

  defp sync_grid_and_latlon(params, ["location", field], old_grid) when field in ["lat", "lon"] do
    with {lat, ""} <- params |> Map.get("lat", "") |> to_string() |> String.trim() |> Float.parse(),
         {lon, ""} <- params |> Map.get("lon", "") |> to_string() |> String.trim() |> Float.parse(),
         lat when lat >= -90.0 and lat <= 90.0 <- lat,
         lon when lon >= -180.0 and lon <= 180.0 <- lon do
      grid = Maidenhead.from_latlon(lat, lon, 6)
      {Map.put(params, "grid", grid), grid}
    else
      _ -> {params, old_grid}
    end
  end

  defp sync_grid_and_latlon(params, _target, old_grid), do: {params, old_grid}

  defp format_coord(value) do
    value |> Float.round(6) |> Float.to_string()
  end

  defp current_user(%Phoenix.LiveView.Socket{assigns: assigns}), do: scope_user(assigns)
  defp current_user(assigns) when is_map(assigns), do: scope_user(assigns)

  defp scope_user(assigns) do
    case assigns[:current_scope] do
      %{user: %User{} = user} -> user
      _ -> nil
    end
  end

  defp can_modify?(scope, %{user_id: uid}) do
    case scope_user(%{current_scope: scope}) do
      %User{is_admin: true} -> true
      %User{id: ^uid} when not is_nil(uid) -> true
      _ -> false
    end
  end

  defp can_modify?(_, _), do: false

  defp actions_for(scope) do
    [
      buttons: fn %{record: record} ->
        row_actions(record, scope)
      end
    ]
  end

  defp row_actions(record, scope) do
    assigns = %{record: record, can_modify?: can_modify?(scope, record)}

    ~H"""
    <div :if={@can_modify?} class="flex gap-2">
      <button
        type="button"
        phx-click="edit"
        phx-value-id={@record.id}
        class="btn btn-xs btn-ghost"
      >
        Edit
      </button>
      <button
        type="button"
        phx-click="delete"
        phx-value-id={@record.id}
        data-confirm="Delete this location?"
        class="btn btn-xs btn-ghost text-error"
      >
        Delete
      </button>
    </div>
    """
  end

  defp status_cell(:good) do
    assigns = %{}
    ~H|<span class="badge badge-success">Good</span>|
  end

  defp status_cell(:bad) do
    assigns = %{}
    ~H|<span class="badge badge-error">Bad</span>|
  end

  defp status_cell(_), do: ""

  defp location_cell(_value, %{id: id, lat: lat, lon: lon}) when is_number(lat) and is_number(lon) do
    grid = Maidenhead.from_latlon(lat, lon, 10)
    assigns = %{id: id, lat: lat, lon: lon, grid: grid}

    ~H"""
    <.link navigate={~p"/rover-locations/#{@id}"} class="flex flex-col hover:underline">
      <span class="font-mono text-sm font-semibold">{@grid}</span>
      <span class="font-mono text-xs text-base-content/60">
        {Float.round(@lat, 5)}, {Float.round(@lon, 5)}
      </span>
    </.link>
    """
  end

  defp location_cell(_, _), do: ""

  defp notes_cell(nil), do: ""
  defp notes_cell(""), do: ""

  defp notes_cell(notes) when is_binary(notes) do
    assigns = %{notes: notes}
    ~H|<span class="whitespace-pre-line">{@notes}</span>|
  end

  defp format_ts(nil), do: ""
  defp format_ts(%DateTime{} = dt), do: Calendar.strftime(dt, "%Y-%m-%d")
  defp format_ts(%NaiveDateTime{} = dt), do: Calendar.strftime(dt, "%Y-%m-%d")
  defp format_ts(other), do: to_string(other)

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope} max_width="max-w-5xl">
      <.header>
        Rover Locations
        <:subtitle>Community-shared parking spots for portable rover ops.</:subtitle>
        <:actions>
          <.link navigate={~p"/rover-locations/map"} class="btn btn-ghost">
            <.icon name="hero-map" class="w-5 h-5" /> Map
          </.link>
          <button
            type="button"
            phx-click="new"
            class="btn btn-primary"
            disabled={is_nil(current_user(assigns))}
          >
            <.icon name="hero-plus" class="w-5 h-5" /> Add location
          </button>
        </:actions>
      </.header>

      <p :if={is_nil(current_user(assigns))} class="alert alert-info text-sm mb-4">
        <a href={~p"/users/log-in"} class="link link-primary">Sign in</a> to add or edit locations.
      </p>

      <div :if={@form} class="card bg-base-200 p-4 mb-4">
        <h2 class="text-lg font-semibold mb-2">
          {if @editing_id, do: "Edit location", else: "New location"}
        </h2>
        <.form for={@form} phx-change="validate" phx-submit="save" id="location-form">
          <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
            <.input
              name="location[grid]"
              value={@grid_input}
              type="text"
              label="Grid (Maidenhead)"
              placeholder="EM12kp"
            />
            <.input
              field={@form[:status]}
              type="select"
              label="Status"
              options={[{"Good", :good}, {"Bad", :bad}]}
            />
          </div>
          <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
            <.input
              field={@form[:lat]}
              type="number"
              step="any"
              label="Latitude"
              placeholder="32.5"
            />
            <.input
              field={@form[:lon]}
              type="number"
              step="any"
              label="Longitude"
              placeholder="-97.5"
            />
          </div>
          <.input
            field={@form[:notes]}
            type="textarea"
            label="Notes"
            rows="3"
            placeholder="Trail access, line-of-sight notes, contact info, etc."
          />
          <div class="flex gap-2 mt-3">
            <button type="submit" class="btn btn-primary btn-sm">
              {if @editing_id, do: "Save", else: "Add"}
            </button>
            <button type="button" phx-click="cancel" class="btn btn-ghost btn-sm">
              Cancel
            </button>
          </div>
        </.form>
      </div>

      <form phx-change="filter_status" class="mb-4 flex items-center gap-2">
        <label for="status-filter" class="text-sm font-medium">Show:</label>
        <select
          id="status-filter"
          name="value"
          class="select select-sm select-bordered"
        >
          <option value="all" selected={is_nil(@status_filter)}>All</option>
          <option value="good" selected={@status_filter == "good"}>Good</option>
          <option value="bad" selected={@status_filter == "bad"}>Bad</option>
        </select>
      </form>

      <.live_table
        fields={fields()}
        filters={filters()}
        options={@options}
        streams={@streams}
        actions={actions_for(@current_scope)}
      />
    </Layouts.app>
    """
  end
end
