# frozen_string_literal: true
module Masks
  module OpenID
    # Manages authorizations for OpenID/OAuth2 requests.
    class Authorization
      attr_accessor :client, :scopes, :response, :response_type

      class << self
        def perform(env, **opts)
          authorization = new(env, **opts)
          authorization.perform
          authorization
        end
      end

      def initialize(env, **opts)
        @env = env
        @app =
          Rack::OAuth2::Server::Authorize.new do |req, res|
            @client =
              session.config.model(:openid_client).find_by(key: req.client_id)

            req.bad_request!(:client_id, "not found") unless @client

            unless req.redirect_uri
              req.invalid_request!('"redirect_uri" missing')
            end

            unless @client.redirect_uris.any?
              @client.redirect_uris = [req.redirect_uri.to_s]
              @client.valid? || req.invalid_request!('"redirect_uri" invalid')
            end

            res.redirect_uri = req.verify_redirect_uri!(@client.redirect_uris)

            @scopes = req.scope & @client.scopes

            if res.protocol_params_location == :fragment && req.nonce.blank?
              req.invalid_request! "nonce required"
            end

            if @client.response_types.include?(
                 Array(req.response_type).collect(&:to_s).join(" ")
               )
              if actor
                if opts[:approved] || client.auto_consent?
                  @client.save if @client.redirect_uris_changed?

                  approved! req, res
                elsif opts.key?(:approved)
                  req.access_denied!
                end
              end
            else
              req.unsupported_response_type!
            end
          end
      end

      def session
        @session ||= @env[Masks::Middleware::SESSION_KEY]
      end

      def actor
        @actor ||= (session.actor if session.passed?)
      end

      def perform
        @response = @app.call(@env)
      end

      def approved!(req, res)
        response_types = Array(req.response_type)

        if response_types.include? :code
          authorization =
            actor.openid_authorizations.create!(
              openid_client: client,
              redirect_uri: res.redirect_uri,
              nonce: req.nonce,
              scopes: @scopes
            )

          res.code = authorization.code
        end

        if response_types.include? :token
          access_token =
            actor.openid_access_tokens.create!(
              openid_client: client,
              scopes: @scopes
            )

          res.access_token = access_token.to_bearer_token
        end

        if response_types.include? :id_token
          id_token =
            actor.openid_id_tokens.create!(
              openid_client: @client,
              nonce: req.nonce
            )

          res.id_token =
            id_token.to_jwt(
              code: (res.respond_to?(:code) ? res.code : nil),
              access_token:
                (res.respond_to?(:access_token) ? res.access_token : nil)
            )
        end

        res.approve!
      end
    end
  end
end