import ch.nevis.esauth.auth.engine.AuthResponse
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
import ch.nevis.idm.client.HTTPRequestWrapper

import groovy.json.JsonSlurper
import groovy.json.JsonBuilder



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 clearEidSession(){
    def s = request.getAuthSession(true)
    s.removeAttribute('agov.eid.verification')
    s.removeAttribute('agov.eid.verification.id')
    s.removeAttribute('agov.eid.verification.link')
    s.removeAttribute('agov.eid.linkedAccountsDto')
    s.removeAttribute('agov.eid.User.birthDate')
    s.removeAttribute('agov.eid.User.eIdNumber')
    s.removeAttribute('agov.eid.User.firstName')
    s.removeAttribute('agov.eid.User.lastName')
    s.removeAttribute('agov.eid.User.gender')
    s.removeAttribute('agov.eid.User.nationality')
    s.removeAttribute('agov.eid.User.placeOfBirth')
    s.removeAttribute('agov.eid.User.svnr')
    s.removeAttribute('agov.eid.User.origin')
}

def updateLoginHistory(idmRestClient, userExtId, credentialExtId) {
  try {
    def baseUrl = parameters.get("baseUrl")
    def clientExtId = parameters.get("clientExtId")
    def endpoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId/login-info"
	def dto = "{\"success\": true,\"credentialExtId\": \"${credentialExtId}\"}"

	def postRequest = new HTTPRequestWrapper()
	postRequest.addToHeaders('Content-Type',  ['application/json'])
	postRequest.setPayLoad(dto.getBytes('UTF-8'))
	postRequest.setPayLoad(dto.getBytes('UTF-8'))

	def result = idmRestClient.postWithResponse(endpoint, postRequest)
	if (result.getStatusCode() != 200) {
      // best effort, we log only
      // TODO/haburger/2025-06-24: context parameters are missing here (also in getAccounts)
      LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${userExtId}, CredentialType='E-ID Link', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to update login history for credential ${credentialExtId} (http status: ${result.getStatusCode()})'")
    }
  } catch (Exception e) {
    // best effort, we log only
    // TODO/haburger/2025-06-24: context parameters are missing here (also in getAccounts)
    LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${userExtId}, CredentialType='E-ID Link', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to update login history for credential ${credentialExtId} (${e})'")
  }
}

def getAccounts(json, String svnr) {
    def idm_users_dto = json["Resources"]
    def accounts = [:]
    def frontend_dto = []

    for(user in idm_users_dto){

        def credentials_dto = user["urn:nevis:idm:scim:schemas:v1:extension:User"]["credentials"]
        if(!credentials_dto){
            LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='AGOV account has no credentials'")
        }

        for(cred in credentials_dto){
			def foundCredential = false
            def extId = user["externalId"]
            //TODO/aca/2025/06/11: Can we have multiple email adresses? -> if yes search for primary
            String email = user["emails"][0]["value"]
			if(cred["type"] == "SAMLFEDERATION" && cred["issuerNameId"] == svnr){
                // we found a second federation credential in one AGOV account -> Throw data error
                if(foundCredential){
                    LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Multiple EId linking credentials found in one AGOV account'")
                    return [null,null]
                }

                // extract login info
                def firstLogin = true
                if(cred["credentialLoginInfo"]){
                    if(cred["credentialLoginInfo"]["lastLogin"] && cred["credentialLoginInfo"]["lastLogin"] != ""){
                        firstLogin = false
                    }
                }

                //NOTE/aca/2025/06/11: Assume that this is sanitized when registered.
				def accountName = cred['subjectNameId']
                def credentialExtId = cred['extId']
                 
                accounts.put(email, [ "extId": extId, "credentialExtId": cred['extId'], "firstLogin": firstLogin ] )
                frontend_dto.add(["email": email, "description": accountName])

                foundCredential=true
			}
        }
    }
    return [ accounts, [ "accounts": frontend_dto ] ]
}

def sess = request.getAuthSession(true)
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)

// 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'


if(inargs['submit'] && inargs['login'] && inargs['login'] != ''){
    LOG.debug("Account with email: ${inargs['login']} was selceted -> Continuing")
    
    def accounts = new JsonSlurper().parseText(session['agov.eid.linkedAccountsDto'])
    def account = accounts.get( inargs['login'].trim() )

    sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
    sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])

    // update login history
    updateLoginHistory(idmRestClient, account["extId"], account["credentialExtId"])

    if(account["firstLogin"]){
        response.setResult('firstLogin')
        return
    }

    response.setResult('ok')
    return
}

if(inargs['cancelEid'] && inargs['cancelEid'] == 'cancel'){
    LOG.debug("Account selection was canceled: back to initial login screen")
    clearEidSession()
    response.setResult('backToVerification')
    return
}


if(getHeader('Content-Type') == 'application/json'){
    String account_selection_dto = session['agov.eid.linkedAccountsFrontendDto']

    response.setContent(account_selection_dto.toString())
	response.setContentType('application/json')
	response.setHttpStatusCode(200)
	response.setIsDirectResponse(true)
	response.setStatus(AuthResponse.AUTH_CONTINUE)
	return
}


String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/scim/v1/$clientExtId/Users"

// Fetch account identifier
String svnr = sess.getAttribute("agov.eid.User.svnr")
LOG.debug("search for accounts with SVNR: $svnr")

// Pepare GET request
String attributes = "externalId,emails,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.subjectNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.extId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.credentialLoginInfo.lastLogin"
String filter = "urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type=='SAMLFEDERATION'%20AND%20urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId=='$svnr'"

String requestUrl = "$endPoint?count=20&attributes=$attributes&filter=$filter"

String scimResponse
try {

    scimResponse = idmRestClient.get(requestUrl)
    
    //TODO/aca/2025/06/11: Fetch more pages if more than 20 entries have been found

    LOG.debug("SCIM Response: $scimResponse")

    def json = new JsonSlurper().parseText(scimResponse)
    def (accounts, frontend_dto) = getAccounts(json, svnr)

    // unrecoverable DATA ERROR happend
    if(!accounts){
        response.setResult('error')
        return
    }

    def numAccounts = accounts.size()

    LOG.debug("Linked accounts found: " + frontend_dto.toString())

    if(numAccounts == 0){
        //TODO/aca/2025-06-10: Implement next step
        // Redirect to an error page or linking page when that's ready and decided
        sess.setAttribute("eid.placeholder.text", "EId: No AGOV Account found case not implemented yet")
        response.setResult('noAccount')
        return
    }else if(numAccounts == 1){
        // One account found -> continue with loading attributes from idm (+ notification if it is the first login)
        def account = accounts.values().first()
        sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
        sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])

        // update login history
        updateLoginHistory(idmRestClient, account["extId"], account["credentialExtId"])

        if(account["firstLogin"]){
            response.setResult('firstLogin')
            return
        }

        response.setResult('ok')
        return
    }else{
        // Multiple accounts found -> Dispatch the account selection screen
        sess.setAttribute('agov.eid.linkedAccountsDto', new JsonBuilder(accounts).toString())
        sess.setAttribute('agov.eid.linkedAccountsFrontendDto', new JsonBuilder(frontend_dto).toString())

        LOG.debug("Show GUI")
        response.setStatus(AuthResponse.AUTH_CONTINUE)
        return
    }
    
} catch(Exception e) {
    LOG.error("Fetching Agov Accounts Failed: ${e}")
    sess.setAttribute("eid.placeholder.text", "EId: An exception occured while fetching the AGOV accounts\n: ${e}")
    response.setResult('error')
    return
}
