package com.tarkvaratehnika.demobackend.config

import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.core.session.SessionRegistryImpl
import org.webeid.security.exceptions.JceException
import org.webeid.security.nonce.NonceGenerator
import org.webeid.security.nonce.NonceGeneratorBuilder
import org.webeid.security.validator.AuthTokenValidator
import org.webeid.security.validator.AuthTokenValidatorBuilder
import java.io.IOException
import java.net.URI
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.time.ZonedDateTime
import java.util.concurrent.TimeUnit
import javax.cache.Cache
import javax.cache.CacheManager
import javax.cache.Caching
import javax.cache.configuration.CompleteConfiguration
import javax.cache.configuration.FactoryBuilder.factoryOf
import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.CreatedExpiryPolicy
import javax.cache.expiry.Duration


@Configuration
class ValidationConfiguration {

    private val LOG: Logger = LoggerFactory.getLogger(ValidationConfiguration::class.java)

    private val NONCE_TTL_MINUTES: Long = 5
    private val CACHE_NAME = "nonceCache"
    private val CERTS_RESOURCE_PATH = "/certs"
    private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
    private val TRUSTSTORE_PASSWORD = "changeit"



    init {
        LOG.warn("Creating new ValidationConfiguration.")
    }

    @Bean
    fun cacheManager(): CacheManager {
        return Caching.getCachingProvider(CaffeineCachingProvider::class.java.name).cacheManager
    }



    @Bean
    fun nonceCache(): Cache<String, ZonedDateTime>? {
        val cacheManager: CacheManager = cacheManager()
        var cache =
            cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME)

        if (cache == null) {
            LOG.warn("Creating new cache.")
            cache = createNonceCache(cacheManager)
        }

        return cache
    }

    @Bean
    fun generator(): NonceGenerator? {
        return NonceGeneratorBuilder()
            .withNonceTtl(java.time.Duration.ofMinutes(NONCE_TTL_MINUTES))
            .withNonceCache(nonceCache())
            .build()
    }

    private fun createNonceCache(cacheManager: CacheManager): Cache<String?, ZonedDateTime?>? {
        val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>()
            .setTypes(String::class.java, ZonedDateTime::class.java)
            .setExpiryPolicyFactory(
                factoryOf(
                    CreatedExpiryPolicy(
                        Duration(
                            TimeUnit.MINUTES,
                            NONCE_TTL_MINUTES + 1
                        )
                    )
                )
            )
        return cacheManager.createCache(CACHE_NAME, cacheConfig)
    }

    @Bean
    fun loadTrustedCACertificatesFromCerFiles() : Array<X509Certificate> {
        val caCertificates = ArrayList<X509Certificate>()

        try {
            val certFactory = CertificateFactory.getInstance("X.509")
            val resolver = PathMatchingResourcePatternResolver()
            val resources = resolver.getResources("$CERTS_RESOURCE_PATH/*.cer")

            resources.forEach { resource ->
                val caCertificate = certFactory.generateCertificate(resource.inputStream) as X509Certificate
                caCertificates.add(caCertificate)
            }
        } catch (e : Exception) {
            when (e){
                is CertificateException, is IOException -> {
                    throw RuntimeException("Error initializing trusted CA certificates. $e")
                }
            }
        }
        return caCertificates.toTypedArray()
    }

    @Bean
    fun loadTrustedCACertificatesFromTrustStore() : Array<X509Certificate> {
        val caCertificates = ArrayList<X509Certificate>()

        ValidationConfiguration::class.java.getResourceAsStream("$CERTS_RESOURCE_PATH/$TRUSTED_CERTIFICATES_JKS").use { inputStream ->
            try {
                if (inputStream == null) {
                    // No truststore files found.
                    return arrayOf()
                }

                val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
                keyStore.load(inputStream, TRUSTSTORE_PASSWORD.toCharArray())
                val aliases = keyStore.aliases()

                while (aliases.hasMoreElements()) {
                    val alias = aliases.nextElement()
                    val certificate = keyStore.getCertificate(alias) as X509Certificate
                    caCertificates.add(certificate)
                }


            } catch (e : Exception) {
                when (e) {
                    is IOException, is CertificateException, is KeyStoreException, is NoSuchAlgorithmException -> {
                        throw RuntimeException("Error initializing trusted CA certificates from trust store. $e")
                    }
                }
            }
        }

        return caCertificates.toTypedArray()
    }

    @Bean
    fun validator() : AuthTokenValidator {
        try {
            return AuthTokenValidatorBuilder()
                .withSiteOrigin(URI.create(ApplicationConfiguration.WEBSITE_ORIGIN_URL))
                .withNonceCache(nonceCache())
                .withTrustedCertificateAuthorities(*loadTrustedCACertificatesFromCerFiles())
                .withTrustedCertificateAuthorities(*loadTrustedCACertificatesFromTrustStore())
                .build()
        } catch (e : JceException) {
            throw RuntimeException("Error building the Web eID auth token validator.", e)
        }
    }


}