package gaf3.core.gateway.filter.factory

import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException
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

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

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

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

    override fun apply(config: JwtParserGatewayFilterFactory.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 != null && accepts.isNotEmpty()) {
                val accept = exchange.request.headers.accept[0]
                isHtml = accept.includes(MediaType.TEXT_HTML)
            }

            val cookie = exchange.request.cookies.getFirst(COOKIE_TOKEN)
            var token: String? = cookie?.value
            if (token == null || StringUtils.isEmpty(token)
                    || "null".equals(token, ignoreCase = true) || "undefined".equals(token, ignoreCase = true)) {
                log.warn("Weixin Jwt not found [{} {}]", method, url)
                token = null
                // throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED,
                // "用户未认证");
            }

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

                    val claims = jws.body
                    exchange.attributes[WEIXIN_CLAIMS_ATTRIBUTE] = claims
                    log.debug("Weixin claims: {}", claims)
                    val subject = claims.subject
                    val issuer = claims.issuer
                    if ("weixin".equals(issuer, ignoreCase = true)) {
                        val request = exchange.request.mutate().headers { httpHeaders -> httpHeaders.set(HEADER_OPENID, subject) }.build()
                        return@label chain.filter(exchange.mutate().request(request).build())
                    } else {
                        log.warn("Jwt issuer invalid: {} [{} {}]", issuer, method, url)
                    }
                } catch (ex: ExpiredJwtException) {
                    log.warn("Weixin Jwt expired [{} {}]", method, url)
                } catch (ex: MalformedJwtException) {
                    log.warn("Weixin Jwt is invalid: MalformedJwtException [{} {}]", method, url)
                    if (log.isDebugEnabled) {
                        log.debug(token)
                        ex.printStackTrace()
                    }
                }

            }

            if (config.redirect != null && isHtml) {
                // 重定向到授权地址
                var redirect_uri = config.redirect
                redirect_uri += when (redirect_uri!!.contains("?")) {
                    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)

    }

    companion object {

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

        const val COOKIE_TOKEN = "wxtoken"
        const val HEADER_OPENID = "X-OpenID"
        const val WEIXIN_CLAIMS_ATTRIBUTE = "NafGateway.weixinClaims"

        const val IGNORE_KEY = "ignore"
        const val REDIRECT_KEY = "redirect"
        const val ORDER_KEY = "order"
    }

}
