defmodule Berrypod.Redirects.LinkChecker do
  @moduledoc """
  Validates URLs found by the link scanner.

  Internal links are checked against the router and database.
  External links are checked via HTTP HEAD requests.
  """

  alias Berrypod.Products
  alias Berrypod.Pages

  # Static routes that are always valid
  @static_paths ~w(
    / /about /contact /delivery /privacy /terms /cart /search /coming-soon
    /collections/all
  )

  @doc """
  Checks whether an internal path is valid.

  Returns `:ok` or `{:error, reason}`.
  """
  def check_internal(path) do
    cond do
      path in @static_paths ->
        :ok

      match?("/products/" <> _, path) ->
        slug = String.replace_prefix(path, "/products/", "")
        if Products.get_visible_product(slug), do: :ok, else: {:error, "product not found"}

      match?("/collections/" <> _, path) ->
        # Collection routes are always valid (they filter, never 404)
        :ok

      true ->
        # Could be a custom page — check the slug (strip leading /)
        slug = String.replace_prefix(path, "/", "")

        if Pages.get_page(slug) do
          :ok
        else
          {:error, "page not found"}
        end
    end
  end

  @doc """
  Checks whether an external URL is reachable.

  Uses HTTP HEAD with a fallback to GET (some servers reject HEAD).
  Returns `:ok`, `{:error, status, reason}`, or `{:error, reason}`.
  """
  def check_external(url) do
    req = build_req()

    case Req.head(req, url: url) do
      {:ok, %{status: status}} when status in 200..399 ->
        :ok

      {:ok, %{status: 405}} ->
        # HEAD not allowed, try GET
        check_external_get(req, url)

      {:ok, %{status: status}} ->
        {:error, status, status_reason(status)}

      {:error, %{reason: reason}} ->
        {:error, format_error(reason)}

      {:error, reason} ->
        {:error, format_error(reason)}
    end
  end

  defp check_external_get(req, url) do
    case Req.get(req, url: url) do
      {:ok, %{status: status}} when status in 200..399 ->
        :ok

      {:ok, %{status: status}} ->
        {:error, status, status_reason(status)}

      {:error, %{reason: reason}} ->
        {:error, format_error(reason)}

      {:error, reason} ->
        {:error, format_error(reason)}
    end
  end

  defp build_req do
    Req.new(
      connect_options: [timeout: 5_000],
      receive_timeout: 5_000,
      max_retries: 0,
      redirect: true,
      max_redirects: 5,
      headers: [{"user-agent", "Berrypod Link Checker/1.0"}]
    )
    |> maybe_attach_test_plug()
  end

  defp maybe_attach_test_plug(req) do
    if plug = Application.get_env(:berrypod, :link_checker_plug) do
      Req.merge(req, plug: plug)
    else
      req
    end
  end

  defp status_reason(404), do: "not found"
  defp status_reason(403), do: "forbidden"
  defp status_reason(410), do: "gone"
  defp status_reason(500), do: "server error"
  defp status_reason(502), do: "bad gateway"
  defp status_reason(503), do: "service unavailable"
  defp status_reason(status), do: "HTTP #{status}"

  defp format_error(:timeout), do: "timeout"
  defp format_error(:econnrefused), do: "connection refused"
  defp format_error(:nxdomain), do: "DNS lookup failed"
  defp format_error(:closed), do: "connection closed"
  defp format_error(%Mint.TransportError{reason: reason}), do: format_error(reason)
  defp format_error(reason) when is_atom(reason), do: to_string(reason)
  defp format_error(reason), do: inspect(reason)
end
