require "httparty"

module NuID::SDK::API
   
  # This class wraps the NuID Auth API endpoints for simpler integration into
  # existing authentication flows.
  #
  # @example User Registration
  #   class UsersController < ApplicationController
  #     NUID_API = ::NuID::SDK::API::Auth.new(ENV["NUID_API_KEY"])
  #   
  #     # The registration form should send the verified credential to be
  #     # recorded in the NuID Auth API. The response to that interaction
  #     # will provide a `nu/id` key in the response which should be stored
  #     # with the newly created user record.
  #     #
  #     # The "verified credential" is generated by your client application
  #     # using `Zk.verifiableFromSecret(password)` from the `@nuid/zk` npm
  #     # package.
  #     def register
  #       credential_res = nuid_api.credential_create(params[:credential])
  #       unless credential_res.code == 201
  #         return render_error("Unable to create the credential", :bad_request)
  #       end
  #   
  #       user = User.create!({
  #         email: params[:email].strip.downcase,
  #         first_name: params[:firstName],
  #         last_name: params[:lastName],
  #         nuid: credential_res.parsed_response["nu/id"]
  #       })
  #   
  #       render(json: { user: user }, status: :created)
  #     rescue => exception
  #       render_error(exception.message, 500)
  #     end
  #   end
  #
  # @example User Login
  #   class SessionsController < ApplicationController
  #     NUID_API = ::NuID::SDK::API::Auth.new(ENV["NUID_API_KEY"])
  #   
  #     # Get a challenge from the Auth API. The client form should request
  #     # a challenge as the first of two phases to login. Once a succesful
  #     # challenge has been fetched, return it to the client so a proof
  #     # can be generated from the challenge claims and the user's password.
  #     def login_challenge
  #       user = User.where(email: params[:email].strip.downcase).first
  #       return render_error("User not found", :unauthorized) unless user
  #   
  #       credential_res = nuid_api.credential_get(user.nuid)
  #       unless credential_res.code == 200
  #         return render_error("Credential not found", :unauthorized)
  #       end
  #   
  #       credential = credential_res.parsed_response["nuid/credential"]
  #       challenge_res = nuid_api.challenge_get(credential)
  #       unless challenge_res.code == 201
  #         return render_error("Cannot create a challenge", 500)
  #       end
  #   
  #       challenge_jwt = challenge_res.parsed_response["nuid.credential.challenge/jwt"]
  #       render(json: { challengeJwt: challenge_jwt }, status: :ok)
  #     rescue => exception
  #       render_error(exception.message, 500)
  #     end
  #   
  #     # Verify is the second part of the login process. The params
  #     # provided here include the user identification param (email or
  #     # username), the unaltered challenge_jwt retrieved in phase 1 of login
  #     # (see #login_challenge above), and the proof that was generated from
  #     # the challenge_jwt claims and the user secret.
  #     #
  #     # The "proof" is generated by your client application using
  #     # `Zk.proofFromSecretAndChallenge(password, challenge_jwt)` from the
  #     # `@nuid/zk` npm package.
  #     def login_verify
  #       user = User.where(email: params[:email].strip.downcase).first
  #       return render_error("User not found", :unauthorized) unless user
  #   
  #       challenge_res = nuid_api.challenge_verify(params[:challengeJwt], params[:proof])
  #       unless challenge_res.code == 200
  #         return render_error("Verification failed", :unauthorized)
  #       end
  #   
  #       render(json: { user: user }, status: :ok)
  #     rescue => exception
  #       render_error(exception.message, 500)
  #     end
  #   end
  #
  # @see https://www.npmjs.com/package/@nuid/zk
  # @see https://www.npmjs.com/package/@nuid/cli
  class Auth
    include HTTParty
    base_uri "https://auth.nuid.io"

    attr_reader :api_key

    # Create an HTTParty instance for dispatching HTTP requests.
    #
    # All endpoints return the HTTParty Response object, with
    # `HTTParty::Response#parsed_response` containing the JSON body converted to
    # a hash.
    #
    # @param api_key [string] The Auth API Key
    # @see https://portal.nuid.io
    def initialize(api_key)
      @api_key = api_key
      self.class.headers({
        "X-API-Key" => @api_key,
        "Accept"    => "application/json"
      })
    end

    # Get a credential `challenge` from the API, usually during login flow.
    # The returned `challenge` can be used to generate a proof from the user's
    # secret. Used in conjunction with #challenge_verify.
    #
    # @see https://www.npmjs.com/package/@nuid/zk
    # @see https://www.npmjs.com/package/@nuid/cli
    #
    # @param credential [Hash] A `credential` is usually returned by the
    #   #credential_get method
    # @return [HTTParty::Response] use
    #   HTTParty::Response#parsed_response["nuid.credential.challenge/jwt"] to
    #   get the challenge JWT from the parsed response
    def challenge_get(credential)
      _post("/challenge", {"nuid/credential" => credential})
    end

    # Verify a credential challenge with a proof generated from the challenge
    # claims and the user's secret. Generated proof from the claims contained in
    # the `challenge_jwt` and the user's secret. This proof is generated by
    # `Zk.proofFromSecretAndChallenge(secret, challenge)` available in the npm
    # package `@nuid/zk`.
    #
    # @see https://www.npmjs.com/package/@nuid/zk
    # @see https://www.npmjs.com/package/@nuid/cli
    #
    # @param [String] challenge_jwt the `nuid.credential.challenge/jwt` returned
    #   by #challenge_get
    # @param [Hash] proof the generated proof from the challenge jwt claims and
    #   user secret
    # @return [HTTParty::Response] use `Response#parsed_response["nu/id"]` to get the
    #   parsed JSON body
    def challenge_verify(challenge_jwt, proof)
      _post("/challenge/verify", {
        "nuid.credential.challenge/jwt" => challenge_jwt,
        "nuid.credential/proof"         => proof
      })
    end

    # Create a credential from a verified credential (meaning a credential
    # generated from the user's secret). Usually used during user registration.
    # The parsed response body contains the new credential and the user's unique
    # "nu/id" which should be used as a reference to the user's credential for
    # later authentication attempts.
    #
    # @see #credential_get
    # @see https://www.npmjs.com/package/@nuid/zk
    # @see https://www.npmjs.com/package/@nuid/cli
    #
    # @param verified_credential [Hash] The hash returned by calling `Zk.verifiableFromSecret(secret)`
    # @return [HTTParty::Response] use `Response#parsed_response` to get the
    #   parsed JSON body
    def credential_create(verified_credential)
      _post("/credential", {"nuid.credential/verified" => verified_credential})
    end

    # Fetch a credential by it's unique `nuid`. The `nu/id` paramter is extracted
    # from the `#parsed_response` of #credential_create.
    #
    # Generally you will end up storing the nuid with your user record during
    # registration. Later during login use the nuid to fetch the credential
    # using this method, and pass the Response#parsed_response directly to
    # #challenge_get.
    #
    # @param [String] nuid unique key for the credential
    # @return [HTTParty::Response] use `Response#parsed_response` to get the
    #   parsed JSON body
    def credential_get(nuid)
      self.class.get("/credential/#{nuid}")
    end

    private

    def _post(path, body)
      self.class.post(path, {
        headers: {"Content-Type" => "application/json"},
        body: body.to_json
      })
    end
    
  end
end
