# Base class for the two possible access tokens: Bearer and Mac.
#
# Use `#authenticate` to authenticate an `HTTP::Client`.
abstract class OAuth2::AccessToken
  def self.new(pull : JSON::PullParser) : self
    token_type = nil
    access_token = nil
    expires_in = nil
    refresh_token = nil
    scope = nil
    mac_algorithm = nil
    mac_key = nil
    extra = nil

    pull.read_object do |key|
      case key
      when "token_type"    then token_type = pull.read_string
      when "access_token"  then access_token = pull.read_string
      when "expires_in"    then expires_in = pull.read_int
      when "refresh_token" then refresh_token = pull.read_string_or_null
      when "scope"         then scope = pull.read_string_or_null
      when "mac_algorithm" then mac_algorithm = pull.read_string
      when "mac_key"       then mac_key = pull.read_string
      else
        extra ||= {} of String => String
        extra[key] = pull.read_raw
      end
    end

    unless access_token
      raise ::JSON::SerializableError.new("Missing access token", "OAuth2::AccessToken", "access_token", *pull.location, cause: nil)
    end
    access_token = access_token.not_nil!

    token_type ||= "bearer"

    case token_type.downcase
    when "bearer"
      Bearer.new(access_token, expires_in, refresh_token, scope, extra)
    when "mac"
      Mac.new(access_token, expires_in, mac_algorithm.not_nil!, mac_key.not_nil!, refresh_token, scope, Time.utc.to_unix, extra)
    else
      raise "Unknown token_type in access token json: #{token_type}"
    end
  end

  property access_token : String
  property expires_in : Int64?
  property refresh_token : String?
  property scope : String?

  # JSON key-value pairs that are outside of the OAuth2 spec are
  # stored in this property in case they are needed. Their value
  # is the raw JSON string found in the JSON value (with possible
  # changes in the string format, but preserving JSON semantic).
  # For example if the value was `[1, 2, 3]` then the value in this hash
  # will be the string "[1,2,3]".
  property extra : Hash(String, String)?

  def initialize(@access_token : String, expires_in : Int?, @refresh_token : String? = nil, @scope : String? = nil, @extra = nil)
    @expires_in = expires_in.try &.to_i64
  end

  abstract def authenticate(request : HTTP::Request, tls)

  def authenticate(client : HTTP::Client)
    client.before_request do |request|
      authenticate request, client.tls?
    end
  end
end
