package dp.kotul.jwtapi.service

import dp.kotul.jwt.JwtParser
import dp.kotul.jwt.model.User
import io.jsonwebtoken.Claims
import io.jsonwebtoken.CompressionCodecs
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Header
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.slf4j.Logger
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import java.nio.file.Files
import java.nio.file.Paths
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.Base64
import java.util.Date


interface JwtService {
    fun generateJwt(user: User): String
    fun parseJwt(jwtToken: String): ResponseEntity<Map<String, Any>>
}

const val USER_CLAIM = "user"

@Service
class JwtServiceImpl(
    val log: Logger,
    val jwtParser: JwtParser,

    @Value("\${jwt.private-key-file}")
    private val privateKeyFile: String
) : JwtService {

    private val privateKey = getPrivateKey()

    override fun generateJwt(user: User): String {
        log.info("Generating JWT started for user with ID: ", user.usrId)

        return Jwts.builder()
            .setIssuer("dp-kotul")
            .setSubject(user.usrId.toString())
            .setIssuedAt(Date.from(Instant.now()))
            .setExpiration(Date.from(Instant.now().plus(1, ChronoUnit.HOURS)))
            .claim(USER_CLAIM, user)
            .compressWith(CompressionCodecs.DEFLATE)
            .signWith(privateKey, SignatureAlgorithm.ES256)
            .compact()
    }

    override fun parseJwt(jwtToken: String): ResponseEntity<Map<String, Any>> {
        var header: Header<*>
        var body: Claims
        var responseStatus: HttpStatus
        try {
            val claims = jwtParser.verifyAndGetJwtClaims(jwtToken)
            header = claims.header
            body = claims.body
            responseStatus = HttpStatus.OK
        } catch (e: ExpiredJwtException) {
            header = e.header
            body = e.claims
            responseStatus = HttpStatus.UNAUTHORIZED
        }
        val result = mutableMapOf<String, Any>()
        result["expiration"] = body.expiration
        result["issuedAt"] = body.issuedAt
        result["jwt"] = mapOf("header" to header, "body" to body)

        return ResponseEntity.status(responseStatus).body(result)
    }

    private fun getPrivateKey(): PrivateKey {
        val spec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(Files.readAllBytes(Paths.get(privateKeyFile))))
        val factory = KeyFactory.getInstance("EC")
        return factory.generatePrivate(spec)
    }
}
