package gaf3.core.gateway.filter.factory

import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.crypto.MacSigner
import io.jsonwebtoken.security.Keys
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.cloud.gateway.filter.GatewayFilter
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.util.StringUtils
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.util.*

/**
 * 用户认证Token过滤器，通过Authorization或Cookie读取认证Jwt
 * @author dyg
 */
class JwtParserGatewayFilterFactory : AbstractGatewayFilterFactory<JwtParserGatewayFilterFactory.Config>(Config::class.java) {

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

    override fun shortcutFieldOrder(): List<String> {
        return arrayListOf(ISSUER_KEY, IGNORE_KEY, REDIRECT_KEY, ORDER_KEY)
    }

    override fun apply(config: Config): GatewayFilter {
        return OrderedGatewayFilter( label@ { exchange, chain ->
            // 检查是否忽略uri
            val uri = exchange.request.uri
            if (config.ignore != null && uri.path.matches(config.ignore!!.toRegex())) {
                return@label chain.filter(exchange.mutate().build())
            }
            val url = exchange.request.uri.toString()
            val method = exchange.request.methodValue

            val accepts : List<MediaType>? = exchange.request.headers.accept
            var isHtml = false
            if (accepts!!.isNotEmpty()) {
                val accept = exchange.request.headers.accept[0]
                isHtml = accept.includes(MediaType.TEXT_HTML)
            }

            // 优先读取Head中的Authorization
            val values = exchange.request.headers[HEADER_AUTH]
            var token: String? = if (values != null && values.size > 0) values[0] else null
            if (token != null && token.startsWith("Bearer ")) {
                token = token.substring(7)
            }

            // 读取cookie中的token
            if (token == null) {
                val cookie = exchange.request.cookies.getFirst(COOKIE_TOKEN)
                token = cookie?.value
            }

            if (token == null || StringUtils.isEmpty(token) || "null".equals(token, ignoreCase = true)
                    || "undefined".equals(token, ignoreCase = true)) {
                log.debug("Jwt not found [{} {}]", method, url)
                token = null
            }

            if (token != null) {
                try {
                    val key = secret!!.toByteArray().copyOf(32)
                    val parser = Jwts.parser().setSigningKey(key)
                    if(config.issuer != null) {
                        parser.requireIssuer(config.issuer)
                    }
                    val jws = parser.parseClaimsJws(token)

                    val claims = jws.body
                    exchange.attributes[JWT_CLAIMS_ATTRIBUTE] = claims
                    log.debug("Jwt claims: {}", claims)
                    val issuer : String? = claims.issuer
                    val subject : String? = claims.subject
                    val tokens = subject!!.split("@".toRegex(), 2).toTypedArray()
                    // val userid = tokens[0]
                    val tenant = if (tokens.size > 1) tokens[1] else null

                    val request = exchange.request.mutate().headers { httpHeaders ->
                        httpHeaders.set(HEADER_PLATFORM, issuer)
                        httpHeaders.set(HEADER_USERID, subject)
                        if (tenant != null)
                            httpHeaders.set(HEADER_TENANT, tenant)
                        if (claims.get("role", String::class.java) != null)
                            httpHeaders.set(HEADER_ROLE, claims.get("role", String::class.java))
                        val tags = claims.get("tags", ArrayList::class.java)
                        if (tags != null) {
                            httpHeaders.set(HEADER_TAGS, tags.joinToString(","))
                        }
                    }.build()
                    return@label chain.filter(exchange.mutate().request(request).build())
                } catch (ex: ExpiredJwtException) {
                    log.warn("Jwt token expired [{} {}]", method, url)
                } catch (ex: MalformedJwtException) {
                    log.warn("Jwt token is invalid: MalformedJwtException [{} {}]", method, url)
                    if (log.isDebugEnabled) {
                        log.debug("token is: {}", token)
                        ex.printStackTrace()
                    }
                }

            }

            if (config.redirect != null && isHtml) {
                // 重定向到授权地址
                var redirect_uri = config.redirect
                redirect_uri += when ("?" in redirect_uri!!) {
                    true -> "&"
                    false -> "?"
                }
                redirect_uri += try {
                    println(uri.toString())
                    "redirect_uri=" + URLEncoder.encode(uri.toString(), "UTF-8")
                } catch (e: UnsupportedEncodingException) {
                    log.warn("URL编码错误", e)
                    "redirect_uri=$uri"
                }

                setResponseStatus(exchange, HttpStatus.TEMPORARY_REDIRECT)
                val response = exchange.response
                response.headers.set(HttpHeaders.LOCATION, redirect_uri)
            } else {
                setResponseStatus(exchange, HttpStatus.UNAUTHORIZED)
            }

            exchange.response.setComplete()
        }, config.order)

    }

    // @Validated
    class Config {
        // @NotEmpty
        var ignore: String? = null
        // @NotEmpty
        var redirect: String? = null
        var order = -1
        var issuer: String? = null

    }

    companion object {

        internal val log = LoggerFactory.getLogger(JwtParserGatewayFilterFactory::class.java)

        const val HEADER_AUTH = "Authorization"
        const val HEADER_TENANT = "X-Tenant"
        const val HEADER_USERID = "X-Userid"
        const val HEADER_ROLE = "X-Role"
        const val HEADER_TAGS = "X-Tags"
        const val HEADER_PLATFORM = "X-Platform"
        const val JWT_CLAIMS_ATTRIBUTE = "NafGateway.jwtClaims"
        const val COOKIE_TOKEN = "token"

        const val IGNORE_KEY = "ignore"
        const val REDIRECT_KEY = "redirect"
        const val ORDER_KEY = "order"
        const val ISSUER_KEY = "issuer"

        fun doInit() {
            @Suppress("SpellCheckingInspection")
            val secret = "12345678"
            val key = secret.toByteArray().copyOf(32)
            MacSigner(SignatureAlgorithm.HS256, key) // 这个操作比较耗时

            val cal = Calendar.getInstance()
            cal.add(Calendar.HOUR_OF_DAY, 1)
            val signed = Jwts.builder().setIssuer("master").setSubject("platform").setExpiration(cal.time)
                    .claim("userid", "admin").claim("name", "管理员").signWith(Keys.hmacShaKeyFor(key)).compact()
            Jwts.parser().setSigningKey(key).parseClaimsJws(signed)
        }
    }
}
