import groovy.xml.XmlSlurper
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span

int getRequestedLevel(String authnContextClassRef, def roleList){
  if (!authnContextClassRef) {
    return 100
  }
  if (authnContextClassRef && authnContextClassRef.startsWith('urn:qa.agov.ch:names:tc:ac:classes:')) {
    def requestedLevel = authnContextClassRef.substring(35)
    LOG.debug('authnContextClassRef agov found: ' + requestedLevel)
    if (requestedLevel.isNumber()) {
      int requestedLevelNumber = Integer.parseInt(requestedLevel)
      LOG.debug('contains ' + roleList.contains(requestedLevelNumber))
      if (requestedLevel.isNumber() && roleList.contains(requestedLevelNumber)) {
        LOG.debug('Requested role number: ' + requestedLevel)
        return requestedLevelNumber
      }
    }
    else return 0
  }
  else {
         return 0
  }
}

def session = request.getAuthSession(true)
def context = session.get('ch.nevis.auth.saml.request.authnContextClassRef')
if (!context || context == '' || context == 'null') {
  // EPD call, we set a default of aq300
  session.setAttribute('ch.nevis.auth.saml.request.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:300')
  conext = 'urn:qa.agov.ch:names:tc:ac:classes:300'
}

def roleLevels = [100,200,300,400,500,600]
def requestedRoleLevelNumber = getRequestedLevel(context, roleLevels)

//set attribute Requested Role Level
session.setAttribute('agov.requestedRoleLevel', '' + requestedRoleLevelNumber)
LOG.debug('Requested role level (agov) '+ requestedRoleLevelNumber)

// SAML finisherstate is now available, we can backup it
session.setAttribute('agov.backup.finishers', '' + session.getAttribute('ch.nevis.session.finishers'))

// Accounting
def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
def replacedRequestId = session['agov.replacedRequestId'] ?: '-'
def requestedAq = session['agov.requestedRoleLevel'] ?: 'unknown'
def sourceIp = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown'

def bestTokenAddressWhitelist = ',' + (parameters.get('bestTokenAddressWhitelist') ?: '').replaceAll('\\s','') + ','
def appRequiresBestTokenWithAddress = bestTokenAddressWhitelist.contains(','+requester+',')

def bestTokenSvnrWhitelist = ',' + (parameters.get('bestTokenSvnrWhitelist') ?: '').replaceAll('\\s','') + ','
def appRequiresBestTokenWithSvnr = bestTokenSvnrWhitelist.contains(','+requester+',')

LOG.info("Event='AUTHREQUEST', Requester='${requester}', RequestId='${requestId}', ReplacedRequestId='${replacedRequestId}', RequestedAq=${requestedAq}, BestTokenRequired='svnr: ${appRequiresBestTokenWithSvnr}; address: ${appRequiresBestTokenWithAddress}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")


if (requestedRoleLevelNumber == 0 || session.get('ch.nevis.auth.saml.request.scoping.requesterId') == null) {
  response.setResult('error');
  return
}

def eidEnabled = parameters.get('eidEnabled') == "true"
// TODO/aca/2025-06-05: add a condition to check if the client actually allows eid
def eidAllowed = eidEnabled
// set session variable to later decide to which loginmethods we can switch
session.setAttribute('agov.eidAllowed', eidAllowed.toString())


// if aq400 or less is requested then we need to decide which login method to show first
// The default login method is eid. If eid is not allowed we prefer fido uaf.
def ok_transition = eidAllowed ? 'exit.1' : 'ok'

// if there is a login method cookie set form a previous login -> use that instead of the default
def lastLoginMethod = session.get('agov.lastLoginMethod')
if(lastLoginMethod != null || lastLoginMethod != ""){
    if(lastLoginMethod == "accessApp" || lastLoginMethod == "securityKey"){
        ok_transition = 'ok'
    }
}else{
    session.setAttribute('agov.lastLoginMethod', eidAllowed ? "accessApp" : "eid")
}
// NOTE: if the last login method was eid, but eid is not allowed, we will default to fido uaf

try {
      def spanCtxt = Span.current().getSpanContext()
      def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
      def jsonSlurper = new JsonSlurper()
      def url = parameters.get('url') + '?entity-id=' + session.get('ch.nevis.auth.saml.request.scoping.requesterId')
      LOG.debug('Request url: ' + url)
      def httpClient = HttpClients.create(parameters)
      def httpResponse = Http.get().url(url).header('traceparent', traceparent).build().send(httpClient)
      LOG.debug('Response Message: ' + httpResponse.reasonPhrase())
      LOG.debug('Response Status Code: ' + httpResponse.code())
      LOG.debug('Response: ' + httpResponse.bodyAsString())

      if (httpResponse.code() == 200) {
        def json = jsonSlurper.parseText(httpResponse.bodyAsString())

        session.setAttribute('agov.appDisplayNameDE', '' + json.displayNameDe)
        session.setAttribute('agov.appDisplayNameFR', '' + json.displayNameFr)
        session.setAttribute('agov.appDisplayNameIT', '' + json.displayNameIt)
        session.setAttribute('agov.appDisplayNameEN', '' + json.displayNameEn)
        session.setAttribute('agov.appDisplayNameRM', '' + ((json.appDisplayNameRM) ? json.appDisplayNameRM : json.appDisplayNameDE))

        // if aq500 or 600 is requested -> the only available login method is eid -> continue directly there
        // if eid is disabled -> show an error page
        if (requestedRoleLevelNumber == 600 || requestedRoleLevelNumber == 500) {
            if(eidEnabled){
                session.setAttribute('agov.appSvnrAllowed', 'true')
                response.setResult('exit.1')
                return
            }else{
                response.setResult('error')
                response.setError(9073, "LoA 600 not supported")
                return
            }

        }

        LOG.debug('AdressRequired: ' + json.addrRequired)
        LOG.debug('SvnrAllowed: ' + json.svnrAllowed)
        LOG.debug('appRequiresBestTokenWithAddress: ' + appRequiresBestTokenWithAddress)
        LOG.debug('appRequiresBestTokenWithSvnr: ' + appRequiresBestTokenWithSvnr)

        // address will be returned to the application if allowed by connect (json.addrRequired)
        // and the authRequest was done with at least AGOVaq 200
        // BUNDBITBK-4307: or best token for address is enabled
        session.setAttribute('agov.appAddressRequired', '' + (json.addrRequired && ((requestedRoleLevelNumber >= 200) || appRequiresBestTokenWithAddress)))

        // address will be returned to the application if allowed by connect (json.svnrAllowed)
        // and the authRequest was done with at least AGOVaq 300
        // BUNDBITBK-4307: or best token for svnr is enabled
        session.setAttribute('agov.appSvnrAllowed', '' + (json.svnrAllowed && ((requestedRoleLevelNumber >= 300) || appRequiresBestTokenWithSvnr)))

        response.setResult(ok_transition)
        return
      } else {
        LOG.warn("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'")
        LOG.warn('Unexcpected HTTP response code: ' + httpResponse.code())

        if ( requestedRoleLevelNumber == 100) {
           session.setAttribute('agov.appAddressRequired', '' + appRequiresBestTokenWithAddress)
           session.setAttribute('agov.appSvnrAllowed', 'false')
           response.setResult(ok_transition)
        }
        else if ( requestedRoleLevelNumber == 200) {
           session.setAttribute('agov.appAddressRequired', 'true')
           session.setAttribute('agov.appSvnrAllowed', 'false')
           response.setResult(ok_transition)
        }
        else {
          response.setResult('error')
          response.setError(9071, "Missing meta data for relying party, can't process request")
        }
        return
      }

} catch (Exception e) {
  LOG.error("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'", e)
  session.setAttribute('agov.eidAllowed', 'false')
  if ( requestedRoleLevelNumber == 100) {
     session.setAttribute('agov.appAddressRequired', '' + appRequiresBestTokenWithAddress)
     session.setAttribute('agov.appSvnrAllowed', 'false')
     response.setResult(ok_transition)
  }
  else if ( requestedRoleLevelNumber == 200) {
     session.setAttribute('agov.appAddressRequired', 'true')
     session.setAttribute('agov.appSvnrAllowed', 'false')
     response.setResult(ok_transition)
  }
  else {
    response.setResult('error')
    response.setError(9072, "Failure while processing meta data for relying party, can't continue processing request")
  }
  return
}