module OpenIDConnect
  module Discovery
    module Provider
      class Config
        class Response
          include ActiveModel::Validations, AttrRequired, AttrOptional

          cattr_accessor :metadata_attributes
          attr_reader :raw
          attr_accessor :expected_issuer
          uri_attributes = {
            required: [
              :issuer,
              :authorization_endpoint,
              :jwks_uri
            ],
            optional: [
              :token_endpoint,
              :userinfo_endpoint,
              :registration_endpoint,
              :end_session_endpoint,
              :service_documentation,
              :check_session_iframe,
              :op_policy_uri,
              :op_tos_uri
            ]
          }
          attr_required(*(uri_attributes[:required] + [
            :response_types_supported,
            :subject_types_supported,
            :id_token_signing_alg_values_supported
          ]))
          attr_optional(*(uri_attributes[:optional] + [
            :scopes_supported,
            :response_modes_supported,
            :grant_types_supported,
            :acr_values_supported,
            :id_token_encryption_alg_values_supported,
            :id_token_encryption_enc_values_supported,
            :userinfo_signing_alg_values_supported,
            :userinfo_encryption_alg_values_supported,
            :userinfo_encryption_enc_values_supported,
            :request_object_signing_alg_values_supported,
            :request_object_encryption_alg_values_supported,
            :request_object_encryption_enc_values_supported,
            :token_endpoint_auth_methods_supported,
            :token_endpoint_auth_signing_alg_values_supported,
            :display_values_supported,
            :claim_types_supported,
            :claims_supported,
            :claims_locales_supported,
            :ui_locales_supported,
            :claims_parameter_supported,
            :request_parameter_supported,
            :request_uri_parameter_supported,
            :require_request_uri_registration
          ]))

          validates(*required_attributes, presence: true)
          validates(*uri_attributes.values.flatten, url: true, allow_nil: true)
          validates :issuer, with: :validate_issuer_matching

          def initialize(hash)
            (required_attributes + optional_attributes).each do |key|
              self.send "#{key}=", hash[key]
            end
            @raw = hash
          end

          def as_json(options = {})
            validate!
            (required_attributes + optional_attributes).inject({}) do |hash, _attr_|
              value = self.send _attr_
              hash.merge! _attr_ => value unless value.nil?
              hash
            end
          end

          def validate!
            valid? or raise ValidationFailed.new(self)
          end

          def jwks
            @jwks ||= OpenIDConnect.http_client.get(jwks_uri).body.with_indifferent_access
            JSON::JWK::Set.new @jwks[:keys]
          end

          def jwk(kid)
            @jwks ||= {}
            @jwks[kid] ||= JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid)
          end

          def public_keys
            @public_keys ||= jwks.collect(&:to_key)
          end

          private

          def validate_issuer_matching
            if expected_issuer.present? && issuer != expected_issuer
              if OpenIDConnect.validate_discovery_issuer
                errors.add :issuer, 'mismatch'
              else
                OpenIDConnect.logger.warn 'ignoring issuer mismach.'
              end
            end
          end
        end
      end
    end
  end
end
