defmodule ToweropsWeb.UserCredentialController do
  @moduledoc false

  use ToweropsWeb, :controller

  alias Towerops.Accounts
  alias Towerops.Organizations

  @doc """
  Generates a WebAuthn registration challenge.
  Returns challenge data for the browser to use during credential creation.
  """
  def registration_challenge(conn, _params) do
    user = conn.assigns.current_scope.user

    if Accounts.passkey_registration_allowed?(user) do
      challenge_data = Accounts.generate_registration_challenge(user)

      conn
      |> put_session(:webauthn_challenge, challenge_data.challenge)
      |> json(challenge_data)
    else
      conn
      |> put_status(:forbidden)
      |> json(%{error: "Email confirmation required before registering passkeys"})
    end
  end

  @doc """
  Completes WebAuthn registration.
  Receives attestation from the browser and creates the credential.
  """
  def register(conn, params) do
    user = conn.assigns.current_scope.user
    challenge = get_session(conn, :webauthn_challenge)

    case Accounts.register_credential(user, params, challenge) do
      {:ok, credential} ->
        conn
        |> delete_session(:webauthn_challenge)
        |> put_status(:created)
        |> json(%{
          id: credential.id,
          name: credential.name,
          created_at: credential.inserted_at
        })

      {:error, :email_not_confirmed} ->
        conn
        |> put_status(:forbidden)
        |> json(%{error: "Email confirmation required"})

      {:error, _reason} ->
        conn
        |> put_status(:unprocessable_entity)
        |> json(%{error: "Failed to register passkey"})
    end
  end

  @doc """
  Generates a WebAuthn authentication challenge.
  Returns challenge data for the browser to use during authentication.
  Supports both discoverable credentials (no email) and account-specific (with email).
  """
  def authentication_challenge(conn, params) do
    case params do
      %{"email" => email} when is_binary(email) ->
        # Account-specific authentication
        handle_email_authentication_challenge(conn, email)

      _ ->
        # Discoverable credential authentication (usernameless)
        handle_discoverable_authentication_challenge(conn)
    end
  end

  defp handle_email_authentication_challenge(conn, email) do
    case Accounts.get_user_by_email(email) do
      nil ->
        # Don't reveal if user exists
        conn
        |> put_status(:not_found)
        |> json(%{error: "No passkeys found for this email"})

      user ->
        credentials = Accounts.list_user_credentials(user.id)

        if Enum.empty?(credentials) do
          conn
          |> put_status(:not_found)
          |> json(%{error: "No passkeys found for this email"})
        else
          challenge_data = Accounts.generate_authentication_challenge(user)

          conn
          |> put_session(:webauthn_challenge, challenge_data.challenge)
          |> put_session(:webauthn_user_id, user.id)
          |> json(challenge_data)
        end
    end
  end

  defp handle_discoverable_authentication_challenge(conn) do
    # For discoverable credentials, we don't need to know the user ahead of time
    # The authenticator will present credentials for this RP ID
    challenge_data = Accounts.generate_discoverable_authentication_challenge()

    conn
    |> put_session(:webauthn_challenge, challenge_data.challenge)
    |> json(challenge_data)
  end

  @doc """
  Completes WebAuthn authentication.
  Verifies the assertion and logs the user in.
  Supports both discoverable and account-specific authentication.
  """
  def authenticate(conn, params) do
    challenge = get_session(conn, :webauthn_challenge)
    expected_user_id = get_session(conn, :webauthn_user_id)

    cond do
      is_nil(challenge) ->
        conn
        |> put_status(:unauthorized)
        |> json(%{error: "Authentication failed"})

      # Discoverable credential (no expected user)
      is_nil(expected_user_id) ->
        handle_discoverable_authentication(conn, params, challenge)

      # Account-specific authentication
      true ->
        handle_account_authentication(conn, params, challenge, expected_user_id)
    end
  end

  defp handle_discoverable_authentication(conn, params, challenge) do
    case Accounts.authenticate_with_credential(params, challenge) do
      {:ok, user} ->
        conn
        |> delete_session(:webauthn_challenge)
        |> put_status(:ok)
        |> json(%{success: true, redirect: get_post_login_path(user)})

      {:error, _reason} ->
        conn
        |> put_status(:unauthorized)
        |> json(%{error: "Authentication failed"})
    end
  end

  defp handle_account_authentication(conn, params, challenge, expected_user_id) do
    with {:ok, user} <- Accounts.authenticate_with_credential(params, challenge),
         true <- user.id == expected_user_id do
      conn
      |> delete_session(:webauthn_challenge)
      |> delete_session(:webauthn_user_id)
      |> put_status(:ok)
      |> json(%{success: true, redirect: get_post_login_path(user)})
    else
      false ->
        conn
        |> put_status(:unauthorized)
        |> json(%{error: "Authentication failed"})

      {:error, _reason} ->
        conn
        |> put_status(:unauthorized)
        |> json(%{error: "Authentication failed"})
    end
  end

  @doc """
  Deletes a credential.
  Only allows deletion if the credential belongs to the current user.
  """
  def delete(conn, %{"id" => credential_id}) do
    user = conn.assigns.current_scope.user

    case Accounts.delete_credential(credential_id, user.id) do
      {:ok, _credential} ->
        conn
        |> put_flash(:info, "Passkey deleted successfully.")
        |> redirect(to: ~p"/users/settings")

      {:error, :not_found} ->
        conn
        |> put_flash(:error, "Passkey not found.")
        |> redirect(to: ~p"/users/settings")

      {:error, _reason} ->
        conn
        |> put_flash(:error, "Failed to delete passkey.")
        |> redirect(to: ~p"/users/settings")
    end
  end

  defp get_post_login_path(user) do
    # Get the first organization or redirect to orgs list
    case Organizations.list_user_organizations(user.id) do
      [org | _] -> ~p"/orgs/#{org.slug}"
      [] -> ~p"/orgs"
    end
  end
end
