package de.walamana.plugins

import at.favre.lib.crypto.bcrypt.BCrypt
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.Payload
import de.walamana.models.Ministrant
import de.walamana.models.MinistrantenDao
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import java.util.*


const val SALT_ROUNDS = 10;

data class JWTEnvironment(
    val secret: String,
    val issuer: String,
    val audience: String,
)

fun Application.configureSecurity() {
    val jwtEnv = getJWTEnvironment()
    authentication {
        jwt() {
            verifier(
                JWT
                    .require(Algorithm.HMAC256(jwtEnv.secret))
                    .withAudience(jwtEnv.audience)
                    .withIssuer(jwtEnv.issuer)
                    .build()
            )
            validate { credential ->
                if (credential.payload.audience.contains(jwtEnv.audience)) JWTPrincipal(credential.payload) else null
            }
            challenge { _, _ ->
                call.respond(HttpStatusCode.Unauthorized, hashMapOf("msg" to "Token is not valid or has expired"))
            }
        }
    }

}

fun Application.getJWTEnvironment(): JWTEnvironment {

    val secret = environment.config.property("jwt.secret").getString()
    val issuer = environment.config.property("jwt.issuer").getString()
    val audience = environment.config.property("jwt.audience").getString()
    return JWTEnvironment(secret, issuer, audience)
}

fun Payload.username() = getClaim("username").asString()
fun Payload.mid() = getClaim("id").asInt()


object Security {
    fun DEFAULT_EXPIRY() = Date(System.currentTimeMillis() + 1000 * 60 * 60);

    suspend fun authenticateUser(application: Application, username: String, password: String): Ministrant? {
        println("Username $username password $password")

        if (username == "admin") {
            println("Test")
            val adminPw = application.environment.config.property("admin.password").getString()
            println("Test $adminPw")
            if (adminPw == password) {
                val allMinis = MinistrantenDao.allMinistranten().map { it.username }
                println(allMinis)
                return Ministrant(
                    0, "admin", "", "admin", "admin", Date(), allMinis
                )
            }
            return null
        }

        val ministrant = MinistrantenDao.getMinistrant(username, true)
            ?: return null

        if (!BCrypt.verifyer().verify(password.toCharArray(), ministrant.passwordHash).verified) {
            return null
        }

        return ministrant
    }

    suspend fun setPassword(username: String, password: String): Boolean {
        val ministrant = MinistrantenDao.getMinistrant(username) ?: return false

        val hash = BCrypt.withDefaults().hashToString(SALT_ROUNDS, password.toCharArray())

        print(hash)

        MinistrantenDao.updateMinistrant(ministrant.id, passwordHash = hash)

        return true
    }

    fun createToken(jwtEnv: JWTEnvironment, ministrant: Ministrant) = JWT.create()
        .withAudience(jwtEnv.audience)
        .withIssuer(jwtEnv.issuer)
        .withClaim("username", ministrant.username)
        .withClaim("id", ministrant.id)
        .withClaim("privileges", ministrant.privileges)
        .withExpiresAt(DEFAULT_EXPIRY())
        .sign(Algorithm.HMAC256(jwtEnv.secret))
}
