package kr.or.bok.rtgs.louisvuitton

import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import java.nio.charset.StandardCharsets
import java.util.Base64
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

/**
 * 관리자 API 보호 — Gucci가 발급한 JWT를 검증해 role=ADMIN만 admin 경로 실행 허용(A1).
 * 검증만 수행하므로 Gucci와 동일한 서명키(gucci.jwt-secret)만 공유하면 된다(발급 코드 불필요).
 */
@Component
class AdminJwt(
    private val mapper: ObjectMapper,
    @Value("\${gucci.jwt-secret}") private val secret: String,
) {
    private val dec = Base64.getUrlDecoder()
    private val enc = Base64.getUrlEncoder().withoutPadding()

    /** 유효하고 role=ADMIN이면 사용자명 반환, 아니면 null. */
    fun adminSubjectOrNull(authz: String?, nowMs: Long): String? {
        val token = authz?.removePrefix("Bearer ")?.trim() ?: return null
        val p = token.split(".")
        if (p.size != 3) return null
        val expected = sign("${p[0]}.${p[1]}")
        if (!constantTimeEquals(expected, p[2])) return null
        val claims: Map<*, *> = runCatching { mapper.readValue(dec.decode(p[1]), Map::class.java) }.getOrNull() ?: return null
        val exp = (claims["exp"] as? Number)?.toLong() ?: return null
        if (nowMs / 1000 > exp) return null
        if ((claims["role"] as? String) != "ADMIN") return null
        return claims["sub"] as? String
    }

    private fun sign(input: String): String {
        val mac = Mac.getInstance("HmacSHA256")
        mac.init(SecretKeySpec(secret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256"))
        return enc.encodeToString(mac.doFinal(input.toByteArray(StandardCharsets.UTF_8)))
    }

    private fun constantTimeEquals(a: String, b: String): Boolean {
        val x = a.toByteArray(); val y = b.toByteArray()
        if (x.size != y.size) return false
        var r = 0; for (i in x.indices) r = r or (x[i].toInt() xor y[i].toInt()); return r == 0
    }
}

@Component
class AdminAuthInterceptor(private val adminJwt: AdminJwt) : HandlerInterceptor {
    private val log = LoggerFactory.getLogger(javaClass)
    override fun preHandle(req: HttpServletRequest, res: HttpServletResponse, handler: Any): Boolean {
        val subject = adminJwt.adminSubjectOrNull(req.getHeader("Authorization"), System.currentTimeMillis())
        if (subject == null) {
            log.warn("ADMIN deny path={} (no/invalid ADMIN token)", req.requestURI)
            res.status = HttpServletResponse.SC_UNAUTHORIZED
            res.contentType = "application/json;charset=UTF-8"
            res.writer.write("""{"error":"admin authentication required"}""")
            return false
        }
        return true
    }
}

/** admin 경로에 관리자 인증 인터셉터 적용. */
@Configuration
class AdminWebConfig(private val interceptor: AdminAuthInterceptor) : WebMvcConfigurer {
    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(interceptor).addPathPatterns("/admin/**")
    }
}
