import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.xml.slurpersupport.NodeChild

import java.util.zip.Inflater
import java.util.zip.InflaterInputStream

/**
 * Gets the value of the Referer header.
 * If the header is missing the fallback is returned
 *
 * This method is used when SAML IDP / Dispatch Error Redirect is not set
 *
 * @param fallback - value to return if the Referer header is missing
 * @return value of header or fallback
 */
def getReferer(String fallback) {
    return request.getHttpHeader('Referer') ?: fallback
}

def redirect(String url) {
    outargs.put('nevis.transfer.type', 'redirect')
    outargs.put('nevis.transfer.destination', url)
}

String getNormalisedSamlMessage(String parameter) {
    if (parameter == null) {
        return
    }
    String text
    byte[] decoded

    // if parameter is raw xml then continue otherwise try to parse the base64 encoding
    if (parameter.startsWith("<")) {
        text = new String(parameter)
    }
    else {
        decoded = parameter.decodeBase64()
        text = new String(decoded)
    }
    return text
}


String getNodeText(GPathResult xml, String nodeName) {
    return xml.depthFirst().find { GPathResult node -> {
        node.name().endsWith(":${nodeName}") || node.name().equalsIgnoreCase(nodeName)
      }
    }?.text()?.trim()
}

String getAttribute(GPathResult xml, String attributeName) {
    return xml.depthFirst().find { GPathResult node -> {
        node.attributes().containsKey(attributeName)
      }
    }?.attributes()?.get(attributeName)
}

String getNodeText(String parameter, String nodeName) {
    String samlMessage = getNormalisedSamlMessage(parameter)
    if (samlMessage == null) {
        return
    }
    def parser = new XmlSlurper()
    def xml = parser.parseText(samlMessage)
    return getNodeText(xml, nodeName)
}

String getAttribute(String parameter, String attributeName) {
    String samlMessage = getNormalisedSamlMessage(parameter)
    if (samlMessage == null) {
        return
    }
    def parser = new XmlSlurper()
    def xml = parser.parseText(samlMessage)
    return getAttribute(xml, attributeName)
}

String getIssuer(String value) {
  return getNodeText(value, 'Issuer')
}

String getAttributeConsumingServiceIndex(String value) {
  return getAttribute(value, 'AttributeConsumingServiceIndex')
}

String getProtocolBinding(String value) {
  return getAttribute(value, 'ProtocolBinding')
}

def dispatchIssuer(i2s, String issuer, boolean secureMode) {
    def result = i2s.get(issuer)
    if (result == null) {
        LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
    }
    
    // dispatch different idp if artifact binding is enabled
    if(parameters.get('epdMode') == 'artifact' && result == 'epd'){
        LOG.debug("EPD: Artifact mode")
        result = result + "_artifact"
    } else if (result == 'main' && secureMode) {
        LOG.debug("AGOV: Secure mode requested")
        result =  result + "_secure"
    } 
    response.setResult(result)
    session.put('saml.inbound.issuer', issuer)
    session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
    
}

def dispatchIssuer(i2s, String issuer) {
  dispatchIssuer(i2s, issuer, false)
}

def dispatchMessage(i2s, String message) {
    def issuer = getIssuer(message)
    def secureMode = (getAttributeConsumingServiceIndex(message) == '10101')
    def useArtifact = ('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' == getProtocolBinding(message))

    LOG.info("secureMode requested: ${secureMode}")

    if (issuer == null) {
        LOG.info("No issuer found in incoming SAML message. Giving up.")
    }
    session.put('saml.inbound.issuer', issuer)
    session.put('agov.idp.use.artifact', '' + useArtifact)
    dispatchIssuer(i2s, issuer, secureMode)
}

if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {
    String url = request.currentResource
    def path = new URL(url).getPath()
    if (path.endsWith("/logout")) {
        // next AuthState will show a logout confirmation GUI
        response.setResult('confirm')
        return
    }
}

// ensure session exists
if (request.getSession(false) == null) {
    session = request.getSession(true).getData()
}

// issuer (any case) -> ResultCond name
def i2s = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)


i2s.put(parameters.get('atb'), 'main')
i2s.put(parameters.get('epd_atb'), 'epd')

if (parameters.get('spInitiated') == 'true' && inargs.containsKey('SAMLRequest')) { // SP-initiated authentication
    LOG.debug("found SAMLRequest parameter for SP-initiated authentication")
    String message = inargs.get('SAMLRequest')
    dispatchMessage(i2s, message)
    return
}

if (inargs.containsKey('SAMLResponse')) { // response to IDP-initiated SAML Logout
    LOG.debug("found SAMLResponse parameter")
    String message = inargs.get('SAMLResponse')
    dispatchMessage(i2s, message)
    return
}

if (parameters.get('spInitiated') == 'true' && inargs.containsKey('soapheader')) { // SP-initiated SOAP with soapheader
    LOG.debug("found soapheader parameter for SP-initiated")
    String message = inargs.get('soapheader')
    dispatchMessage(i2s, message)
    return
}

if (parameters.get('spInitiated') == 'true' && inargs.containsKey('')) { // SP-initiated SOAP with empty
    LOG.debug("found empty parameter for SP-initiated SOAP message")
    String message = inargs.get('')
    dispatchMessage(i2s, message)
    return
}

String issuer = inargs['Issuer'] ?: inargs['issuer']
if (parameters.get('idpInitiated') == 'true' && issuer != null) { // IDP-initiated authentication
    LOG.debug("found Issuer parameter for IDP-initiated authentication")
    dispatchIssuer(i2s, issuer)
    return
}

// used as fallback in case of ?logout (we need an IdentityProviderState)
if (inargs.containsKey("logout") && session.containsKey('saml.idp.result')) {
    def result = session.get('saml.idp.result')
    LOG.debug("dispatching to last used ResultCond: $result")
    response.setResult(result)
    return
}

def location = getReferer('/')
LOG.info("Unable to dispatch request. Giving up and redirecting (back) to $location")
redirect(location)