module SMARTAppLaunch::TokenPayloadValidation

def check_fhir_context_canonical(canonical)

def check_fhir_context_canonical(canonical)
  assert canonical.is_a?(String), "`#{canonical.inspect}` is not a String"
  assert canonical.start_with?('http'), "`#{canonical}` is not a canonical reference"
  split_canonical = canonical.split('/')
  if split_canonical.last.start_with?(/&|\|/)
    resource_type = split_canonical[-3]
    id = split_canonical[-2]
  else
    resource_type = split_canonical[-2]
    id = split_canonical.last.split(/&|\|/).first
  end
  assert FHIR_RESOURCE_TYPES.include?(resource_type),
         "`#{resource_type}` in `canonical` is not a valid FHIR resource type"
  assert id.match?(FHIR_ID_REGEX), "`#{id}` in `canonical` is not a valid FHIR id"
end

def check_fhir_context_identifier(identifier)

def check_fhir_context_identifier(identifier)
  assert identifier.is_a?(Hash), "`#{identifier.inspect}` is not an Object"
end

def check_fhir_context_reference(reference)

def check_fhir_context_reference(reference)
  assert reference.is_a?(String), "`#{reference.inspect}` is not a String"
  assert !reference.start_with?('http'), "`#{reference}` is not a relative reference"
  resource_type, id = reference.split('/')
  assert FHIR_RESOURCE_TYPES.include?(resource_type),
         "`#{resource_type}` in `reference` is not a valid FHIR resource type"
  assert id.match?(FHIR_ID_REGEX), "`#{id}` in `reference` is not a valid FHIR id"
end

def check_for_missing_scopes(requested_scopes, body)

def check_for_missing_scopes(requested_scopes, body)
  expected_scopes = requested_scopes&.split || []
  new_scopes = body['scope']&.split || []
  missing_scopes = expected_scopes - new_scopes
  warning do
    missing_scopes_string = missing_scopes.map { |scope| "`#{scope}`" }.join(', ')
    assert missing_scopes.empty?, %(
      Token exchange response did not include all requested scopes.
      These may have been denied by user: #{missing_scopes_string}.
    )
  end
end

def validate_fhir_context(fhir_context)

def validate_fhir_context(fhir_context)
  return if fhir_context.nil?
  assert fhir_context.is_a?(Array), "`fhirContext` field is a #{fhir_context.class.name}, but should be an Array"
  fhir_context.each do |reference|
    assert reference.is_a?(String), "`#{reference.inspect}` is not a string"
  end
  fhir_context.each do |reference|
    assert !reference.start_with?('http'), "`#{reference}` is not a relative reference"
    resource_type, id = reference.split('/')
    assert FHIR_RESOURCE_TYPES.include?(resource_type),
           "`#{resource_type}` is not a valid FHIR resource type"
    assert id.match?(FHIR_ID_REGEX), "`#{id}` is not a valid FHIR id"
  end
end

def validate_fhir_context_stu2_2(fhir_context)

def validate_fhir_context_stu2_2(fhir_context)
  return if fhir_context.nil?
  assert fhir_context.is_a?(Array), "`fhirContext` field is a #{fhir_context.class.name}, but should be an Array"
  fhir_context.each do |reference|
    assert reference.is_a?(Hash), "`#{reference.inspect}` is not an Object"
  end
  fhir_context.each do |context|
    reference = context['reference']
    canonical = context['canonical']
    identifier = context['identifier']
    type = context['type']
    assert reference.present? || canonical.present? || identifier.present?,
           '`fhirContext` array SHALL include at least one of "reference", "canonical", or "identifier"'
    check_fhir_context_reference(reference) if reference.present?
    check_fhir_context_canonical(canonical) if canonical.present?
    check_fhir_context_identifier(identifier) if identifier.present?
    if (canonical.present? || identifier.present?) && type.blank?
      info 'The `type` field is recommended when "canonical" or "identifier" is present in `fhirContext` object'
    end
    next unless type.present?
    assert FHIR_RESOURCE_TYPES.include?(type),
           "`#{type}` in `type` is not a valid FHIR resource type"
  end
end

def validate_required_fields_present(body, required_fields)

def validate_required_fields_present(body, required_fields)
  missing_fields = required_fields.select { |field| body[field].blank? }
  missing_fields_string = missing_fields.map { |field| "`#{field}`" }.join(', ')
  assert missing_fields.empty?,
         "Token exchange response did not include all required fields: #{missing_fields_string}."
end

def validate_scope_subset(received_scopes, original_scopes)

def validate_scope_subset(received_scopes, original_scopes)
  extra_scopes = received_scopes.split - original_scopes.split
  assert extra_scopes.empty?, 'Token response contained scopes which are not a subset of the scope granted to the ' \
                              "original access token: #{extra_scopes.join(', ')}"
end

def validate_token_field_types(body)

def validate_token_field_types(body)
  STRING_FIELDS
    .select { |field| body[field].present? }
    .each do |field|
    assert body[field].is_a?(String),
           "Expected `#{field}` to be a String, but found #{body[field].class.name}"
  end
  NUMERIC_FIELDS
    .select { |field| body[field].present? }
    .each do |field|
      assert body[field].is_a?(Numeric),
             "Expected `#{field}` to be a Numeric, but found #{body[field].class.name}"
    end
end

def validate_token_type(body)

def validate_token_type(body)
  assert body['token_type'].casecmp('bearer').zero?, '`token_type` must be `bearer`'
end