defmodule ToweropsWeb.Org.GaiiaMappingLive do
  @moduledoc false
  use ToweropsWeb, :live_view

  alias Towerops.Devices
  alias Towerops.Gaiia
  alias Towerops.Repo
  alias Towerops.Sites

  @impl true
  def mount(_params, _session, socket) do
    org = socket.assigns.current_scope.organization

    {:ok,
     socket
     |> assign(:organization, org)
     |> assign(:tab, "sites")
     |> assign(:filter, "all")
     |> assign(:linking_id, nil)
     |> assign(:search_query, "")
     |> assign(:search_results, [])
     |> load_data()}
  end

  @impl true
  def handle_params(params, _url, socket) do
    tab =
      case params["tab"] do
        t when t in ~w(sites devices) -> t
        _ -> "sites"
      end

    filter =
      case params["filter"] do
        f when f in ~w(all mapped unmapped) -> f
        _ -> "all"
      end

    {:noreply,
     socket
     |> assign(:tab, tab)
     |> assign(:filter, filter)
     |> assign(:linking_id, nil)
     |> assign(:search_query, "")
     |> assign(:search_results, [])
     |> load_data()}
  end

  @impl true
  def handle_event("start_link", %{"id" => id}, socket) do
    {:noreply,
     socket
     |> assign(:linking_id, id)
     |> assign(:search_query, "")
     |> assign(:search_results, [])}
  end

  @impl true
  def handle_event("cancel_link", _params, socket) do
    {:noreply,
     socket
     |> assign(:linking_id, nil)
     |> assign(:search_query, "")
     |> assign(:search_results, [])}
  end

  @impl true
  def handle_event("search", %{"query" => query}, socket) do
    org = socket.assigns.organization

    results =
      if String.length(query) >= 2 do
        case socket.assigns.tab do
          "sites" -> search_sites(org.id, query)
          "devices" -> Devices.search_devices(org.id, query)
        end
      else
        []
      end

    {:noreply, socket |> assign(:search_query, query) |> assign(:search_results, results)}
  end

  @impl true
  def handle_event("link_site", %{"gaiia-id" => gaiia_id, "site-id" => site_id}, socket) do
    org = socket.assigns.organization

    case Gaiia.get_network_site(org.id, gaiia_id) do
      nil ->
        {:noreply, put_flash(socket, :error, "Gaiia network site not found")}

      network_site ->
        case Gaiia.update_network_site_mapping(network_site, %{site_id: site_id}) do
          {:ok, _} ->
            {:noreply,
             socket
             |> assign(:linking_id, nil)
             |> assign(:search_query, "")
             |> assign(:search_results, [])
             |> load_data()
             |> put_flash(:info, "Site linked successfully")}

          {:error, _} ->
            {:noreply, put_flash(socket, :error, "Failed to link site")}
        end
    end
  end

  @impl true
  def handle_event("unlink_site", %{"gaiia-id" => gaiia_id}, socket) do
    org = socket.assigns.organization

    case Gaiia.get_network_site(org.id, gaiia_id) do
      nil ->
        {:noreply, put_flash(socket, :error, "Gaiia network site not found")}

      network_site ->
        case Gaiia.update_network_site_mapping(network_site, %{site_id: nil}) do
          {:ok, _} ->
            {:noreply,
             socket
             |> load_data()
             |> put_flash(:info, "Site unlinked")}

          {:error, _} ->
            {:noreply, put_flash(socket, :error, "Failed to unlink site")}
        end
    end
  end

  @impl true
  def handle_event("link_device", %{"gaiia-id" => gaiia_id, "device-id" => device_id}, socket) do
    org = socket.assigns.organization

    case Gaiia.get_inventory_item(org.id, gaiia_id) do
      nil ->
        {:noreply, put_flash(socket, :error, "Gaiia inventory item not found")}

      item ->
        case Gaiia.update_inventory_item_mapping(item, %{device_id: device_id}) do
          {:ok, _} ->
            {:noreply,
             socket
             |> assign(:linking_id, nil)
             |> assign(:search_query, "")
             |> assign(:search_results, [])
             |> load_data()
             |> put_flash(:info, "Device linked successfully")}

          {:error, _} ->
            {:noreply, put_flash(socket, :error, "Failed to link device")}
        end
    end
  end

  @impl true
  def handle_event("unlink_device", %{"gaiia-id" => gaiia_id}, socket) do
    org = socket.assigns.organization

    case Gaiia.get_inventory_item(org.id, gaiia_id) do
      nil ->
        {:noreply, put_flash(socket, :error, "Gaiia inventory item not found")}

      item ->
        case Gaiia.update_inventory_item_mapping(item, %{device_id: nil}) do
          {:ok, _} ->
            {:noreply,
             socket
             |> load_data()
             |> put_flash(:info, "Device unlinked")}

          {:error, _} ->
            {:noreply, put_flash(socket, :error, "Failed to unlink device")}
        end
    end
  end

  defp load_data(socket) do
    org_id = socket.assigns.organization.id
    tab = socket.assigns.tab
    filter = socket.assigns.filter

    case tab do
      "sites" -> load_network_sites(socket, org_id, filter)
      "devices" -> load_inventory_items(socket, org_id, filter)
    end
  end

  defp load_network_sites(socket, org_id, filter) do
    sites =
      org_id
      |> Gaiia.list_network_sites()
      |> Repo.preload(:site)
      |> filter_by_mapping(filter)

    suggestions =
      Enum.reduce(sites, %{}, fn site, acc ->
        matches = Gaiia.suggest_site_matches(org_id, site)
        if matches == [], do: acc, else: Map.put(acc, site.gaiia_id, matches)
      end)

    socket
    |> assign(:items, sites)
    |> assign(:suggestions, suggestions)
  end

  defp load_inventory_items(socket, org_id, filter) do
    items =
      org_id
      |> Gaiia.list_inventory_items()
      |> Repo.preload(:device)
      |> filter_by_mapping(filter)

    suggestions =
      Enum.reduce(items, %{}, fn item, acc ->
        matches = Gaiia.suggest_device_matches(org_id, item)
        if matches == [], do: acc, else: Map.put(acc, item.gaiia_id, matches)
      end)

    socket
    |> assign(:items, items)
    |> assign(:suggestions, suggestions)
  end

  defp filter_by_mapping(items, "mapped") do
    Enum.filter(items, &mapped?/1)
  end

  defp filter_by_mapping(items, "unmapped") do
    Enum.reject(items, &mapped?/1)
  end

  defp filter_by_mapping(items, _), do: items

  defp mapped?(%Gaiia.NetworkSite{site_id: site_id}), do: not is_nil(site_id)
  defp mapped?(%Gaiia.InventoryItem{device_id: device_id}), do: not is_nil(device_id)

  defp search_sites(organization_id, query) do
    Sites.search_sites(organization_id, query)
  end
end
