package gaf3.core.gateway.handler.predicate

import io.jsonwebtoken.Claims
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory
import org.springframework.util.StringUtils
import org.springframework.web.server.ServerWebExchange
import java.util.function.Predicate

/**
 * @author dyg
 */
@Suppress("unused")
class JwtRoutePredicateFactory : AbstractRoutePredicateFactory<JwtRoutePredicateFactory.Config>(Config::class.java) {

    @Value("\${jwt.secret}")
    private val secret: String? = null

    override fun shortcutFieldOrder(): List<String> {
        return arrayListOf(NAME_KEY, VALUE_KEY)
    }

    override fun apply(config: Config): Predicate<ServerWebExchange> {
        return Predicate { exchange: ServerWebExchange ->

            val url = exchange.request.uri.toString()
            val method = exchange.request.methodValue
            var claims = exchange.attributes[JWT_CLAIMS_ATTRIBUTE] as Claims?
            val failure = exchange.attributes[JWT_PARSE_FAILURE] as Boolean?
            if(failure == true) {
                return@Predicate false
            }

            if (claims == null) {
                val values = exchange.request.headers[HEADER_KEY]
                if (values == null || values.size == 0) {
                    return@Predicate config.name == null
                }
                var token: String = values[0] ?: ""
                if (token.startsWith("Bearer ")) {
                    token = token.substring(7)
                }
                if (StringUtils.isEmpty(token) || "null".equals(token, ignoreCase = true) || "undefined".equals(token, ignoreCase = true)) {
                    log.debug("Jwt not found [{} {}]", method, url)
                    return@Predicate false
                }
                try {
                    val key = secret!!.toByteArray().copyOf(32)
                    val jws = Jwts.parser().setSigningKey(key)
                            .parseClaimsJws(token)

                    claims = jws.body
                } catch (ex: ExpiredJwtException) {
                    log.warn("Jwt token expired [{} {}]", method, url)
                    exchange.attributes[JWT_PARSE_FAILURE] = true
                    return@Predicate false
                } catch (ex: Throwable) {
                    log.warn("Jwt token is invalid: {} [{} {}]", ex.message, method, url)
                    if (log.isDebugEnabled) {
                        log.debug("token is: {}", token)
                        ex.printStackTrace()
                    }
                    exchange.attributes[JWT_PARSE_FAILURE] = true
                    return@Predicate false
                }
                exchange.attributes[JWT_CLAIMS_ATTRIBUTE] = claims
            }

            when {
                config.name == null || config.value == null -> false
                "issuer".equals(config.name!!, ignoreCase = true) -> config.value!!.equals(claims?.issuer
                        ?: "", ignoreCase = true)
                "subject".equals(config.name!!, ignoreCase = true) -> config.value!!.equals(claims?.subject
                        ?: "", ignoreCase = true)
                else -> {
                    val `val` = claims?.get(config.name) as String?
                    `val` != null && `val`.equals(config.value!!, ignoreCase = true)
                }
            }
        }
    }

    class Config {
        var name: String? = null
        var value: String? = null
    }

    companion object {
        internal val log = LoggerFactory.getLogger(JwtRoutePredicateFactory::class.java)

        const val NAME_KEY = "name"
        const val VALUE_KEY = "value"
        const val HEADER_KEY = "Authorization"
        const val JWT_CLAIMS_ATTRIBUTE = "NafGateway.jwtClaims"
        const val JWT_PARSE_FAILURE = "NafGateway.jwtFailure"
    }

}
