defmodule BerrypodWeb.Admin.Redirects do
  use BerrypodWeb, :live_view

  alias Berrypod.Redirects
  alias Berrypod.Redirects.LinkScanner

  @valid_tabs ~w(redirects broken dead_links create)

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket), do: Redirects.subscribe()

    redirect_page = Redirects.list_redirects_paginated(page: 1)
    broken_page = Redirects.list_broken_urls_paginated(page: 1)
    dead_link_page = Redirects.list_dead_links_paginated(page: 1)

    socket =
      socket
      |> assign(:page_title, "Redirects")
      |> assign(:redirect_pagination, redirect_page)
      |> assign(:broken_url_pagination, broken_page)
      |> assign(:dead_link_pagination, dead_link_page)
      |> assign(:dead_link_count, Redirects.count_dead_links())
      |> stream(:redirects, redirect_page.items)
      |> stream(:broken_urls, broken_page.items)
      |> stream(:dead_links, dead_link_page.items)
      |> assign(
        :form,
        to_form(%{"from_path" => "", "to_path" => "", "status_code" => "301"}, as: :redirect)
      )

    {:ok, socket}
  end

  @impl true
  def handle_params(params, _uri, socket) do
    tab = if params["tab"] in @valid_tabs, do: params["tab"], else: "redirects"
    page_num = Berrypod.Pagination.parse_page(params)

    socket = assign(socket, :tab, tab)

    socket =
      case tab do
        "redirects" ->
          page = Redirects.list_redirects_paginated(page: page_num)

          socket
          |> assign(:redirect_pagination, page)
          |> stream(:redirects, page.items, reset: true)

        "broken" ->
          page = Redirects.list_broken_urls_paginated(page: page_num)

          socket
          |> assign(:broken_url_pagination, page)
          |> stream(:broken_urls, page.items, reset: true)

        "dead_links" ->
          page = Redirects.list_dead_links_paginated(page: page_num)

          socket
          |> assign(:dead_link_pagination, page)
          |> stream(:dead_links, page.items, reset: true)

        _ ->
          socket
      end

    {:noreply, socket}
  end

  @impl true
  def handle_info({:redirects_changed, _action}, socket) do
    page = Redirects.list_redirects_paginated(page: socket.assigns.redirect_pagination.page)

    {:noreply,
     socket
     |> assign(:redirect_pagination, page)
     |> stream(:redirects, page.items, reset: true)}
  end

  def handle_info({:broken_urls_changed, _path}, socket) do
    page = Redirects.list_broken_urls_paginated(page: socket.assigns.broken_url_pagination.page)

    {:noreply,
     socket
     |> assign(:broken_url_pagination, page)
     |> stream(:broken_urls, page.items, reset: true)}
  end

  def handle_info(:dead_links_changed, socket) do
    page = Redirects.list_dead_links_paginated(page: socket.assigns.dead_link_pagination.page)

    {:noreply,
     socket
     |> assign(:dead_link_pagination, page)
     |> assign(:dead_link_count, Redirects.count_dead_links())
     |> stream(:dead_links, page.items, reset: true)}
  end

  @impl true
  def handle_event("switch_tab", %{"tab" => tab}, socket) do
    {:noreply, push_patch(socket, to: ~p"/admin/redirects?#{%{tab: tab}}")}
  end

  def handle_event("delete_redirect", %{"id" => id}, socket) do
    redirect_rec = Redirects.get_redirect!(id)
    {:ok, _} = Redirects.delete_redirect(redirect_rec)

    page = Redirects.list_redirects_paginated(page: socket.assigns.redirect_pagination.page)

    {:noreply,
     socket
     |> assign(:redirect_pagination, page)
     |> stream(:redirects, page.items, reset: true)}
  end

  def handle_event("create_redirect", %{"redirect" => params}, socket) do
    from_path = params["from_path"]

    case Redirects.create_manual(%{
           from_path: from_path,
           to_path: params["to_path"],
           status_code: String.to_integer(params["status_code"])
         }) do
      {:ok, _redirect} ->
        resolve_matching_broken_url(from_path)

        socket =
          socket
          |> assign(
            :form,
            to_form(%{"from_path" => "", "to_path" => "", "status_code" => "301"}, as: :redirect)
          )
          |> put_flash(:info, "Redirect created")
          |> push_patch(to: ~p"/admin/redirects?#{%{tab: "redirects"}}")

        {:noreply, socket}

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

  def handle_event("ignore_broken_url", %{"id" => id}, socket) do
    broken_url = Redirects.get_broken_url!(id)
    {:ok, _} = Redirects.ignore_broken_url(broken_url)

    page = Redirects.list_broken_urls_paginated(page: socket.assigns.broken_url_pagination.page)

    {:noreply,
     socket
     |> assign(:broken_url_pagination, page)
     |> stream(:broken_urls, page.items, reset: true)}
  end

  def handle_event("ignore_dead_link", %{"id" => id}, socket) do
    dead_link = Redirects.get_dead_link!(id)
    {:ok, _} = Redirects.ignore_dead_link(dead_link)

    page = Redirects.list_dead_links_paginated(page: socket.assigns.dead_link_pagination.page)

    {:noreply,
     socket
     |> assign(:dead_link_pagination, page)
     |> assign(:dead_link_count, Redirects.count_dead_links())
     |> stream(:dead_links, page.items, reset: true)}
  end

  def handle_event("recheck_dead_link", %{"id" => id}, socket) do
    dead_link = Redirects.get_dead_link!(id)

    Oban.insert(Berrypod.Workers.DeadLinkCheckerWorker.new(%{"check_url" => dead_link.url}))

    {:noreply, put_flash(socket, :info, "Re-checking #{dead_link.url}...")}
  end

  def handle_event("check_all_links", _params, socket) do
    Oban.insert(Berrypod.Workers.DeadLinkCheckerWorker.new(%{}))
    {:noreply, put_flash(socket, :info, "Full link check started...")}
  end

  def handle_event("redirect_broken_url", %{"path" => path}, socket) do
    socket =
      socket
      |> assign(
        :form,
        to_form(%{"from_path" => path, "to_path" => "", "status_code" => "301"}, as: :redirect)
      )
      |> push_patch(to: ~p"/admin/redirects?#{%{tab: "create"}}")

    {:noreply, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.header>
      Redirects
    </.header>

    <div class="admin-filter-row">
      <.tab_button
        tab="redirects"
        label="Active"
        count={@redirect_pagination.total_count}
        active={@tab}
      />
      <.tab_button
        tab="broken"
        label="Broken URLs"
        count={@broken_url_pagination.total_count}
        active={@tab}
      />
      <.tab_button
        tab="dead_links"
        label="Dead links"
        count={@dead_link_count}
        active={@tab}
      />
      <.tab_button tab="create" label="Create" active={@tab} />
    </div>

    <%= if @tab == "redirects" do %>
      <.redirects_table streams={@streams} pagination={@redirect_pagination} />
    <% end %>

    <%= if @tab == "broken" do %>
      <.broken_urls_table streams={@streams} pagination={@broken_url_pagination} />
    <% end %>

    <%= if @tab == "dead_links" do %>
      <.dead_links_table streams={@streams} pagination={@dead_link_pagination} />
    <% end %>

    <%= if @tab == "create" do %>
      <.create_form form={@form} />
    <% end %>
    """
  end

  defp redirects_table(assigns) do
    ~H"""
    <%= if @pagination.total_count == 0 do %>
      <p>No redirects yet.</p>
    <% else %>
      <div class="admin-table-wrap">
        <table class="admin-table">
          <thead>
            <tr>
              <th>From</th>
              <th>To</th>
              <th>Source</th>
              <th>Hits</th>
              <th>Created</th>
              <th></th>
            </tr>
          </thead>
          <tbody id="redirects-table" phx-update="stream">
            <tr :for={{dom_id, redirect} <- @streams.redirects} id={dom_id}>
              <td><code>{redirect.from_path}</code></td>
              <td><code>{redirect.to_path}</code></td>
              <td>
                <span class={[
                  "admin-badge admin-badge-sm",
                  "admin-badge-#{source_colour(redirect.source)}"
                ]}>
                  {redirect.source}
                </span>
              </td>
              <td>{redirect.hit_count}</td>
              <td>{Calendar.strftime(redirect.inserted_at, "%d %b %Y")}</td>
              <td>
                <button
                  phx-click="delete_redirect"
                  phx-value-id={redirect.id}
                  data-confirm="Delete this redirect?"
                  class="admin-btn admin-btn-sm admin-btn-ghost"
                >
                  Delete
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <.admin_pagination
        page={@pagination}
        patch={~p"/admin/redirects"}
        params={%{"tab" => "redirects"}}
      />
    <% end %>
    """
  end

  defp broken_urls_table(assigns) do
    ~H"""
    <%= if @pagination.total_count == 0 do %>
      <p>No broken URLs detected.</p>
    <% else %>
      <div class="admin-table-wrap">
        <table class="admin-table">
          <thead>
            <tr>
              <th>Path</th>
              <th>Prior traffic</th>
              <th>404s</th>
              <th>First seen</th>
              <th>Last seen</th>
              <th></th>
            </tr>
          </thead>
          <tbody id="broken-urls-table" phx-update="stream">
            <tr :for={{dom_id, broken_url} <- @streams.broken_urls} id={dom_id}>
              <td><code>{broken_url.path}</code></td>
              <td>{broken_url.prior_analytics_hits}</td>
              <td>{broken_url.recent_404_count}</td>
              <td>{Calendar.strftime(broken_url.first_seen_at, "%d %b %Y")}</td>
              <td>{Calendar.strftime(broken_url.last_seen_at, "%d %b %Y")}</td>
              <td class="admin-table-actions-row">
                <button
                  phx-click="redirect_broken_url"
                  phx-value-path={broken_url.path}
                  class="admin-btn admin-btn-sm admin-btn-primary"
                >
                  Redirect
                </button>
                <button
                  phx-click="ignore_broken_url"
                  phx-value-id={broken_url.id}
                  class="admin-btn admin-btn-sm admin-btn-ghost"
                >
                  Ignore
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <.admin_pagination
        page={@pagination}
        patch={~p"/admin/redirects"}
        params={%{"tab" => "broken"}}
      />
    <% end %>
    """
  end

  defp dead_links_table(assigns) do
    ~H"""
    <div class="admin-tab-actions">
      <button phx-click="check_all_links" class="admin-btn admin-btn-sm admin-btn-ghost">
        Check all
      </button>
    </div>

    <%= if @pagination.total_count == 0 do %>
      <p>No dead links detected.</p>
    <% else %>
      <div class="admin-table-wrap">
        <table class="admin-table">
          <thead>
            <tr>
              <th>URL</th>
              <th>Type</th>
              <th>Error</th>
              <th>Used in</th>
              <th>Last checked</th>
              <th></th>
            </tr>
          </thead>
          <tbody id="dead-links-table" phx-update="stream">
            <tr :for={{dom_id, dead_link} <- @streams.dead_links} id={dom_id}>
              <td class="admin-cell-truncate truncate"><code>{dead_link.url}</code></td>
              <td>
                <span class={[
                  "admin-badge admin-badge-sm",
                  "admin-badge-#{dead_link_type_colour(dead_link.url_type)}"
                ]}>
                  {dead_link.url_type}
                </span>
              </td>
              <td>{format_dead_link_error(dead_link)}</td>
              <td><.dead_link_sources url={dead_link.url} /></td>
              <td>{Calendar.strftime(dead_link.last_checked_at, "%d %b %Y %H:%M")}</td>
              <td class="admin-table-actions-row">
                <button
                  phx-click="recheck_dead_link"
                  phx-value-id={dead_link.id}
                  class="admin-btn admin-btn-sm admin-btn-ghost"
                >
                  Re-check
                </button>
                <button
                  phx-click="ignore_dead_link"
                  phx-value-id={dead_link.id}
                  data-confirm="Ignore this dead link?"
                  class="admin-btn admin-btn-sm admin-btn-ghost"
                >
                  Ignore
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <.admin_pagination
        page={@pagination}
        patch={~p"/admin/redirects"}
        params={%{"tab" => "dead_links"}}
      />
    <% end %>
    """
  end

  defp create_form(assigns) do
    ~H"""
    <.form for={@form} phx-submit="create_redirect" class="admin-content-narrow">
      <.input
        field={@form[:from_path]}
        label="From path"
        placeholder="/products/old-slug"
        required
      />
      <.input
        field={@form[:to_path]}
        label="To path"
        placeholder="/products/new-slug"
        required
      />
      <.input
        field={@form[:status_code]}
        type="select"
        label="Status code"
        options={[{"301 (permanent)", "301"}, {"302 (temporary)", "302"}]}
      />
      <button type="submit" class="admin-btn admin-btn-primary">Create redirect</button>
    </.form>
    """
  end

  defp tab_button(assigns) do
    assigns =
      assigns
      |> assign(:active, assigns.active == assigns.tab)
      |> assign_new(:count, fn -> nil end)

    ~H"""
    <button
      phx-click="switch_tab"
      phx-value-tab={@tab}
      class={[
        "admin-btn admin-btn-sm",
        @active && "admin-btn-primary",
        !@active && "admin-btn-ghost"
      ]}
    >
      {@label}
      <span
        :if={@count && @count > 0}
        class="admin-badge admin-badge-sm admin-badge-count"
      >
        {@count}
      </span>
    </button>
    """
  end

  defp dead_link_type_colour("internal"), do: "warning"
  defp dead_link_type_colour("external"), do: "info"
  defp dead_link_type_colour(_), do: "neutral"

  defp format_dead_link_error(%{http_status: status, error: error}) when not is_nil(status) do
    "#{status} #{error}"
  end

  defp format_dead_link_error(%{error: error}) when not is_nil(error), do: error
  defp format_dead_link_error(_), do: "—"

  defp dead_link_sources(assigns) do
    assigns = assign(assigns, :sources, LinkScanner.find_sources(assigns.url))

    ~H"""
    <%= case @sources do %>
      <% [] -> %>
        <span>—</span>
      <% [source] -> %>
        <.link navigate={source.edit_path} class="admin-link">{source.label}</.link>
      <% sources -> %>
        <ul class="admin-source-list">
          <li :for={source <- sources}>
            <.link navigate={source.edit_path} class="admin-link">{source.label}</.link>
          </li>
        </ul>
    <% end %>
    """
  end

  defp resolve_matching_broken_url(from_path) do
    case Redirects.get_broken_url_by_path(from_path) do
      nil -> :ok
      broken_url -> Redirects.mark_broken_url_resolved(broken_url)
    end
  end

  defp source_colour("auto_slug_change"), do: "info"
  defp source_colour("auto_product_deleted"), do: "warning"
  defp source_colour("analytics_detected"), do: "accent"
  defp source_colour("admin"), do: "neutral"
  defp source_colour(_), do: "neutral"
end
