import groovy.json.JsonBuilder
import java.security.MessageDigest
import java.util.HashSet
import ch.nevis.esauth.auth.engine.AuthResponse

def getHeader(String name) {
    def inctx = request.getLoginContext()
    // case-insensitive lookup of HTTP headers
    def map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
    map.putAll(inctx)
    return map['connection.HttpHeader.' + name]
}

def sha256(String input) {
  // we do not catch NoSuchAlgorithmException, as every implementation of the Java platform is required to support SHA-256
  def digestBytes = MessageDigest.getInstance('SHA-256').digest(input.getBytes())
  return digestBytes.encodeBase64().toString()
}


def clearCurrentAuthenticationSession() {

  // clean up session attributes
  def s = request.getAuthSession(true)
  def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'

  // we backup the replaced requestId
  if (requestId != 'unknown') {
    s.setAttribute('agov.replacedRequestId', '' + requestId)
  }

  // fido
  s.removeAttribute('ch.nevis.auth.fido.uaf.fidouafsessionid')
  // SAML
  s.removeAttribute('finisherState-DeferredResponse')
  s.removeAttribute('saml.idp.result')
  s.removeAttribute('saml.inbound.issuer')

  def sessionKeySet = new HashSet(session.keySet()) 
  sessionKeySet.each { key -> 
    if ( key ==~ /ch.nevis.auth.saml.request.*/ ) {
      s.removeAttribute(key)
    }
  }
  // agov
  s.removeAttribute('agov.requestedRoleLevel')

}


// context: script is executed, thus we are in the initial dispatching of the state engine
// due to the resetAuthenticationCondition it will be called for sure after each SAMLRequest received
if (inargs['SAMLRequest'] != null) {

  if (session['ch.nevis.auth.saml.request.id'] != null) {

    // Accounting
    def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
    def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
    def requestedAq = session['agov.requestedRoleLevel'] ?: 'unknown'
    def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
    def credentialType = session['authenticatedWith'] ?: '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'

    // check if we receive a repost of the ongoing request
    if (session['agov.currentSamlRequestHash'] != null && session['agov.currentSamlRequestHash'] == sha256(inargs['SAMLRequest'])) {
      LOG.info("Event='AUTHCONTINUE', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")

      request.getInArgs().remove('SAMLRequest')
      request.getInArgs().remove('RelayState')

      // restore the finisher again (was removed by resetAuthenticationCondition) 
      def s = request.getAuthSession(true)
      s.setAttribute('ch.nevis.session.finishers', '' + session['agov.backup.finishers'])

      // process it the same way, as if frontend triggered a reload
      request.getInArgs().setProperty('onReload', 'now')

      response.setResult('continueAfterRepost')
      return
    }
    // else, the new replaces the on-going one
    LOG.info("Event='AUTHREPL', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
    clearCurrentAuthenticationSession()
  }

  // we track the SAML Request we received
  def s = request.getAuthSession(true)
  s.setAttribute('agov.currentSamlRequestHash', '' + sha256(inargs['SAMLRequest']))

  // we set/update a login Cookie
  def agovLoginCookie = "agovLogin=${System.currentTimeMillis()}; Domain=${parameters.get('cookie.domain')}; Path=/; SameSite=Strict; Secure; HttpOnly"
  response.setHeader('Set-Cookie', agovLoginCookie)
  response.setResult('ok')
  return
}


// from here on, corner cases //
// =============================
def json = new JsonBuilder()

if (inargs.containsKey('o.fidoUafSessionId.v')) {

    // timeout, and script in login page is still polling -> send fake response
    LOG.debug('authentication timeout reached, login script is still polling access app status')
    json {
        "status" "unknown"
        "timestamp" org.joda.time.DateTime.now().toString()
    }
    String body = json.toString()

    response.setContent(body)
    response.setContentType('application/json')
    response.setHttpStatusCode(200)
    response.setIsDirectResponse(true)
    response.setStatus(AuthResponse.AUTH_CONTINUE)
    return
}
else {     
    // authentication timeout reached, or SSO-Endpoint bookmarked -> return a 404
    def agovLoginCookie = 'missing'

    if (getHeader('cookie') != null) {
        def cookies = getHeader('cookie')
        if (cookies.matches('^.*agovLogin=([^;]+).*$')) {
            agovLoginCookie = cookies.replaceAll('^.*agovLogin=([^;]+).*$', '$1')
        }
    }
    LOG.debug("agovLoginCookie: ${agovLoginCookie}") 
    if (agovLoginCookie == 'missing' || agovLoginCookie == 'deleted') {
        LOG.debug('SSO-Endpoint bookmarked -> return a 404')
        response.setHttpStatusCode(404)
        response.setIsDirectResponse(true)
        response.setStatus(AuthResponse.AUTH_ERROR)
    }
    else {
        LOG.debug('authentication timeout reached -> return a 408')
        response.setHttpStatusCode(408)
        response.setIsDirectResponse(true)
        response.setStatus(AuthResponse.AUTH_ERROR)
    }
    return
}