// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0

package org.pgpainless.key.generation

import java.io.IOException
import java.security.KeyPairGenerator
import java.util.*
import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
import org.bouncycastle.openpgp.operator.PGPDigestCalculator
import org.bouncycastle.util.Strings
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.policy.Policy
import org.pgpainless.provider.ProviderFactory
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import org.pgpainless.util.Passphrase

class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {

    private var primaryKeySpec: KeySpec? = null
    private val subKeySpecs = mutableListOf<KeySpec>()
    private val userIds = mutableMapOf<String, SelfSignatureSubpackets.Callback?>()
    private var passphrase = Passphrase.emptyPassphrase()
    private var expirationDate: Date? = Date(System.currentTimeMillis() + (5 * MILLIS_IN_YEAR))

    override fun setPrimaryKey(keySpec: KeySpec): KeyRingBuilder = apply {
        verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy())
        verifyPrimaryKeyCanCertify(keySpec)
        this.primaryKeySpec = keySpec
    }

    override fun addSubkey(keySpec: KeySpec): KeyRingBuilder = apply {
        verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy())
        subKeySpecs.add(keySpec)
    }

    override fun addUserId(userId: CharSequence): KeyRingBuilder = apply {
        userIds[userId.toString().trim()] = null
    }

    override fun addUserId(userId: ByteArray): KeyRingBuilder =
        addUserId(Strings.fromUTF8ByteArray(userId))

    override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
        if (expirationDate == null) {
            this.expirationDate = null
            return@apply
        }
        this.expirationDate =
            expirationDate.let {
                require(Date() < expirationDate) { "Expiration date must be in the future." }
                expirationDate
            }
    }

    override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply {
        this.passphrase = passphrase
    }

    private fun verifyKeySpecCompliesToPolicy(keySpec: KeySpec, policy: Policy) {
        val algorithm = keySpec.keyType.algorithm
        val bitStrength = keySpec.keyType.bitStrength
        require(policy.publicKeyAlgorithmPolicy.isAcceptable(algorithm, bitStrength)) {
            "Public key algorithm policy violation: $algorithm with bit strength $bitStrength is not acceptable."
        }
    }

    private fun verifyPrimaryKeyCanCertify(spec: KeySpec) {
        require(keyIsCertificationCapable(spec)) {
            "Key algorithm ${spec.keyType.name} is not capable of creation certifications."
        }
    }

    private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify

    override fun build(): PGPSecretKeyRing {
        val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator
        val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
        val secretKeyDecryptor = buildSecretKeyDecryptor()

        passphrase.clear() // Passphrase was used above, so we can get rid of it

        // generate primary key
        requireNotNull(primaryKeySpec) { "Primary Key spec required." }
        val certKey = generateKeyPair(primaryKeySpec!!)
        val signer = buildContentSigner(certKey)
        val signatureGenerator = PGPSignatureGenerator(signer)

        val hashedSubPacketGenerator = primaryKeySpec!!.subpacketGenerator
        hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.publicKey)
        expirationDate?.let { hashedSubPacketGenerator.setKeyExpirationTime(certKey.publicKey, it) }
        if (userIds.isNotEmpty()) {
            hashedSubPacketGenerator.setPrimaryUserId()
        }

        val generator = PGPSignatureSubpacketGenerator()
        SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
        val hashedSubPackets = generator.generate()
        val ringGenerator =
            if (userIds.isEmpty()) {
                PGPKeyRingGenerator(
                    certKey,
                    keyFingerprintCalculator,
                    hashedSubPackets,
                    null,
                    signer,
                    secretKeyEncryptor)
            } else {
                userIds.keys.first().let { primaryUserId ->
                    PGPKeyRingGenerator(
                        SignatureType.POSITIVE_CERTIFICATION.code,
                        certKey,
                        primaryUserId,
                        keyFingerprintCalculator,
                        hashedSubPackets,
                        null,
                        signer,
                        secretKeyEncryptor)
                }
            }

        addSubKeys(certKey, ringGenerator)

        // Generate secret key ring with only primary userId
        val secretKeyRing = ringGenerator.generateSecretKeyRing()
        val secretKeys = secretKeyRing.secretKeys

        // Attempt to add additional user-ids to the primary public key
        var primaryPubKey = secretKeys.next().publicKey
        val privateKey = secretKeyRing.secretKey.unlock(secretKeyDecryptor)
        val userIdIterator = userIds.entries.iterator()
        if (userIdIterator.hasNext()) {
            userIdIterator.next() // Skip primary userId
        }
        while (userIdIterator.hasNext()) {
            val additionalUserId = userIdIterator.next()
            val userIdString = additionalUserId.key
            val callback = additionalUserId.value
            val subpackets =
                if (callback == null) {
                    hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
                } else {
                    SignatureSubpackets.createHashedSubpackets(primaryPubKey).also {
                        callback.modifyHashedSubpackets(it)
                    }
                }
            signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
            signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets))
            val additionalUserIdSignature =
                signatureGenerator.generateCertification(userIdString, primaryPubKey)
            primaryPubKey =
                PGPPublicKey.addCertification(
                    primaryPubKey, userIdString, additionalUserIdSignature)
        }

        // Reassemble secret key ring with modified primary key
        val primarySecretKey =
            PGPSecretKey(
                privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
        val secretKeyList = mutableListOf(primarySecretKey)
        while (secretKeys.hasNext()) {
            secretKeyList.add(secretKeys.next())
        }
        return PGPSecretKeyRing(secretKeyList)
    }

    private fun addSubKeys(primaryKey: PGPKeyPair, ringGenerator: PGPKeyRingGenerator) {
        for (subKeySpec in subKeySpecs) {
            val subKey = generateKeyPair(subKeySpec)
            if (subKeySpec.isInheritedSubPackets) {
                ringGenerator.addSubKey(subKey)
            } else {
                var hashedSubpackets = subKeySpec.subpackets
                try {
                    hashedSubpackets =
                        addPrimaryKeyBindingSignatureIfNecessary(
                            primaryKey, subKey, hashedSubpackets)
                } catch (e: IOException) {
                    throw PGPException(
                        "Exception while adding primary key binding signature to signing subkey.",
                        e)
                }
                ringGenerator.addSubKey(subKey, hashedSubpackets, null)
            }
        }
    }

    private fun addPrimaryKeyBindingSignatureIfNecessary(
        primaryKey: PGPKeyPair,
        subKey: PGPKeyPair,
        hashedSubpackets: PGPSignatureSubpacketVector
    ): PGPSignatureSubpacketVector {
        val keyFlagMask = hashedSubpackets.keyFlags
        if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
            !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
            return hashedSubpackets
        }

        val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
        bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
        val primaryKeyBindingSig =
            bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
        val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
        subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
        return subpacketGenerator.generate()
    }

    private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
        val hashAlgorithm =
            PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm
        return ImplementationFactory.getInstance()
            .getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
    }

    private fun buildSecretKeyEncryptor(
        keyFingerprintCalculator: PGPDigestCalculator
    ): PBESecretKeyEncryptor? {
        val keyEncryptionAlgorithm =
            PGPainless.getPolicy()
                .symmetricKeyEncryptionAlgorithmPolicy
                .defaultSymmetricKeyAlgorithm
        check(passphrase.isValid) { "Passphrase was cleared." }
        return if (passphrase.isEmpty) null
        else
            ImplementationFactory.getInstance()
                .getPBESecretKeyEncryptor(
                    keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
    }

    private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
        check(passphrase.isValid) { "Passphrase was cleared." }
        return if (passphrase.isEmpty) null
        else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase)
    }

    companion object {
        const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365

        @JvmStatic
        @JvmOverloads
        fun generateKeyPair(
            spec: KeySpec,
            creationTime: Date = spec.keyCreationDate ?: Date()
        ): PGPKeyPair {
            spec.keyType.let { type ->
                // Create raw Key Pair
                val keyPair =
                    KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
                        .also { it.initialize(type.algorithmSpec) }
                        .generateKeyPair()

                // Form PGP Key Pair
                return ImplementationFactory.getInstance()
                    .getPGPKeyPair(type.algorithm, keyPair, creationTime)
            }
        }
    }
}
