defmodule Livebook.ZTA.Tailscale do
  @behaviour Livebook.ZTA
  require Logger

  @impl true
  def child_spec(opts) do
    %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}}
  end

  def start_link(opts) do
    name = Keyword.fetch!(opts, :name)
    address = Keyword.fetch!(opts, :identity_key)

    if not String.starts_with?(address, "http") and
         not File.exists?(address) do
      Logger.error("Tailscale socket does not exist: #{inspect(address)}")
      raise "invalid Tailscale ZTA configuration"
    end

    Livebook.ZTA.put(name, address)
    :ignore
  end

  @impl true
  def authenticate(name, conn, _opts) do
    remote_ip = to_string(:inet_parse.ntoa(conn.remote_ip))
    tailscale_address = Livebook.ZTA.get(name)
    user = authenticate_ip(remote_ip, tailscale_address)
    {conn, user}
  end

  defp authenticate_ip(remote_ip, address) do
    {url, options} =
      if String.starts_with?(address, "http") do
        uri = URI.parse(address)

        options =
          if uri.userinfo do
            # Req does not handle userinfo as part of the URL
            [auth: "Basic #{Base.encode64(uri.userinfo)}"]
          else
            []
          end

        url = to_string(%{uri | userinfo: nil, path: "/localapi/v0/whois?addr=#{remote_ip}:1"})

        {url, options}
      else
        {
          "http://local-tailscaled.sock/localapi/v0/whois?addr=#{remote_ip}:1",
          [
            unix_socket: address,
            # Req or Finch do not pass on the host from the URL when using a unix socket,
            # so we set the host header explicitly
            headers: [host: "local-tailscaled.sock"]
          ]
        }
      end

    with {:ok, response} <- Req.get(url, options),
         200 <- response.status,
         %{"UserProfile" => user} <- response.body do
      %{
        id: to_string(user["ID"]),
        name: user["DisplayName"],
        email: user["LoginName"]
      }
    else
      _ -> nil
    end
  end
end
