defmodule Raxol.Payments.Xochi.Client do
  @moduledoc """
  Client for the Xochi private execution protocol.

  Talks directly to Riddler's `/xochi/*` endpoints. Riddler is the solver
  backend; agents get the cash-positive path with tier-based fees.

  ## Endpoints

  - `get_quote/2` -- POST /xochi/quote
  - `execute/2` -- POST /xochi/execute
  - `get_status/2` -- GET /xochi/status/:id
  - `get_history/2` -- GET /xochi/history?wallet=
  - `prepare_claim/2` -- POST /xochi/claim/prepare (client-side signing)
  - `submit_claim/2` -- POST /xochi/claim/submit (client-side signing)

  ## Configuration

      config = %{
        base_url: "https://riddler.example.com",
        auth_token: "bearer-token"
      }

      {:ok, quote} = Xochi.Client.get_quote(config, %QuoteRequest{...})
  """

  alias Raxol.Payments.Xochi.Schemas.{
    QuoteRequest,
    QuoteResponse,
    ExecuteRequest,
    ExecuteResponse,
    IntentStatus
  }

  @type config :: %{
          base_url: String.t(),
          auth_token: String.t()
        }

  @type error :: {:error, {:http, integer(), term()}} | {:error, term()}

  @doc "Request an intent quote."
  @spec get_quote(config(), QuoteRequest.t()) :: {:ok, QuoteResponse.t()} | error()
  def get_quote(config, %QuoteRequest{} = request) do
    with :ok <- QuoteRequest.validate(request) do
      config
      |> build_req()
      |> Req.post(url: "/xochi/quote", json: QuoteRequest.to_json(request))
      |> handle_response(&QuoteResponse.from_json/1)
    end
  end

  @doc "Execute a quoted intent with a signed payload."
  @spec execute(config(), ExecuteRequest.t()) :: {:ok, ExecuteResponse.t()} | error()
  def execute(config, %ExecuteRequest{} = request) do
    config
    |> build_req()
    |> Req.post(url: "/xochi/execute", json: ExecuteRequest.to_json(request))
    |> handle_response(&ExecuteResponse.from_json/1)
  end

  @doc "Get intent status by ID."
  @spec get_status(config(), String.t()) :: {:ok, IntentStatus.t()} | error()
  def get_status(config, intent_id) do
    config
    |> build_req()
    |> Req.get(url: "/xochi/status/#{intent_id}")
    |> handle_response(&IntentStatus.from_json/1)
  end

  @doc "Get intent history for a wallet."
  @spec get_history(config(), String.t(), keyword()) :: {:ok, [map()]} | error()
  def get_history(config, wallet, opts \\ []) do
    params = [wallet: wallet] ++ opts

    config
    |> build_req()
    |> Req.get(url: "/xochi/history", params: params)
    |> handle_response(fn body ->
      Map.get(body, "intents", [])
    end)
  end

  @doc """
  Prepare an unsigned claim for client-side signing.

  Returns the UserOp hash for the client to sign with their stealth key.
  No private keys are sent to the server.
  """
  @spec prepare_claim(config(), map()) :: {:ok, map()} | error()
  def prepare_claim(config, %{intent_id: _, recipient_address: _} = params) do
    json = %{
      "intentId" => params.intent_id,
      "recipientAddress" => params.recipient_address
    }

    config
    |> build_req()
    |> Req.post(url: "/xochi/claim/prepare", json: json)
    |> handle_response(& &1)
  end

  @doc """
  Submit a client-signed claim to the bundler.

  The client signs the hash from `prepare_claim/2` and submits
  the signature here.
  """
  @spec submit_claim(config(), map()) :: {:ok, map()} | error()
  def submit_claim(config, %{intent_id: _, signature: _} = params) do
    json = %{
      "intentId" => params.intent_id,
      "signature" => params.signature
    }

    config
    |> build_req()
    |> Req.post(url: "/xochi/claim/submit", json: json)
    |> handle_response(& &1)
  end

  # -- Private --

  defp build_req(config) do
    validate_base_url!(config.base_url)

    Req.new(
      base_url: config.base_url,
      headers: [{"authorization", "Bearer #{config.auth_token}"}],
      receive_timeout: 30_000
    )
  end

  defp validate_base_url!("https://" <> _), do: :ok
  defp validate_base_url!("http://localhost" <> _), do: :ok
  defp validate_base_url!("http://127.0.0.1" <> _), do: :ok

  defp validate_base_url!(url) do
    raise ArgumentError, "Xochi client requires HTTPS base_url, got: #{inspect(url)}"
  end

  defp handle_response({:ok, %Req.Response{status: status, body: body}}, transform)
       when status in 200..299 do
    {:ok, transform.(body)}
  end

  defp handle_response({:ok, %Req.Response{status: status, body: body}}, _transform) do
    {:error, {:http, status, body}}
  end

  defp handle_response({:error, reason}, _transform) do
    {:error, reason}
  end
end
