package com.betriebsratkanzlei.legalconsenthub_middleware.smartcard

import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.CertificateDto
import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.SmartCardInfoDto
import jakarta.annotation.PostConstruct
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.core.io.ResourceLoader
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.io.File
import java.time.LocalDateTime
import java.util.*
import java.nio.file.Files
import java.nio.file.StandardCopyOption

@Service
@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "false", matchIfMissing = true)
class SmartCardService(
    @Value("\${opensc.pkcs11.library.path}") private val openscPkcs11LibPath: String,
    private val resourceLoader: ResourceLoader
) {

    private lateinit var resolvedLibraryPath: String

    @PostConstruct
    fun init() {
        resolvedLibraryPath = resolveLibraryPath()
    }


    // macOS doesn't allow loading native libraries using classpath URLs. The library needs to be loaded from an absolute file path.
    private fun resolveLibraryPath(): String {
        val resource = resourceLoader.getResource(openscPkcs11LibPath)
        if (!resource.exists()) {
            throw IllegalStateException("PKCS11 library not found: $openscPkcs11LibPath")
        }

        val tempFile = Files.createTempFile("opensc-pkcs11", ".so").toFile()
        tempFile.deleteOnExit()

        resource.inputStream.use { input ->
            Files.copy(input, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
        }

        tempFile.setExecutable(true)
        tempFile.setReadable(true)

        return tempFile.absolutePath
    }

    fun getSmartCardInfo(): SmartCardInfoDto {
        val commandLine = CommandLine.parse("pkcs11-tool --module $resolvedLibraryPath --show-info")
        val executor = DefaultExecutor()
        val outputStream = ByteArrayOutputStream()
        val errorStream = ByteArrayOutputStream()

        executor.streamHandler = PumpStreamHandler(outputStream, errorStream)

        return try {
            val exitCode = executor.execute(commandLine)

            if (exitCode == 0) {
                val output = outputStream.toString()
                parseSmartCardInfo(output)
            } else {
                val errorOutput = errorStream.toString()
                throw SmartCardNotFoundException("No smart card detected: $errorOutput")
            }
        } catch (e: SmartCardNotFoundException) {
            throw e
        } catch (e: Exception) {
            throw SmartCardReadException("Error reading smart card: ${e.message}", e)
        }
    }

    fun getSmartCardCertificates(): List<CertificateDto> {
        val commandLine = CommandLine.parse("pkcs11-tool --module $resolvedLibraryPath --list-objects --type cert")
        val executor = DefaultExecutor()
        val outputStream = ByteArrayOutputStream()
        val errorStream = ByteArrayOutputStream()

        executor.streamHandler = PumpStreamHandler(outputStream, errorStream)

        return try {
            val exitCode = executor.execute(commandLine)

            if (exitCode == 0) {
                val output = outputStream.toString()
                parseCertificates(output)
            } else {
                val errorOutput = errorStream.toString()
                throw SmartCardNotFoundException("No smart card detected or no certificates found: $errorOutput")
            }
        } catch (e: SmartCardNotFoundException) {
            throw e
        } catch (e: Exception) {
            throw SmartCardReadException("Error reading smart card certificates: ${e.message}", e)
        }
    }

    fun signHash(hash: String, certificateId: String): String {
        val hashBytes = Base64.getDecoder().decode(hash)

        val tempFile = File.createTempFile("hash_to_sign", ".bin")

        return try {
            tempFile.writeBytes(hashBytes)

            // Build pkcs11-tool command to sign the hash (PIN will be prompted on device)
            val commandLine =
                CommandLine.parse("pkcs11-tool --module $resolvedLibraryPath --sign --mechanism RSA-PKCS --id $certificateId --input-file ${tempFile.absolutePath}")

            val executor = DefaultExecutor()
            val outputStream = ByteArrayOutputStream()
            val errorStream = ByteArrayOutputStream()

            executor.streamHandler = PumpStreamHandler(outputStream, errorStream)

            val exitCode = executor.execute(commandLine)

            if (exitCode == 0) {
                val signature = outputStream.toByteArray()
                Base64.getEncoder().encodeToString(signature)
            } else {
                val errorOutput = errorStream.toString()
                throw RuntimeException("Failed to sign hash: $errorOutput")
            }
        } catch (e: Exception) {
            throw RuntimeException("Error signing hash: ${e.message}", e)
        } finally {
            if (tempFile.exists()) {
                tempFile.delete()
            }
        }
    }

    fun calculateHash(document: ByteArray, hashAlgorithm: String): String {
        return try {
            val javaHashAlgorithm = when (hashAlgorithm.uppercase()) {
                "SHA1" -> "SHA-1"
                "SHA256" -> "SHA-256"
                "SHA384" -> "SHA-384"
                "SHA512" -> "SHA-512"
                else -> "SHA-256"
            }

            val messageDigest = java.security.MessageDigest.getInstance(javaHashAlgorithm)
            val hashBytes = messageDigest.digest(document)

            Base64.getEncoder().encodeToString(hashBytes)
        } catch (e: Exception) {
            throw RuntimeException("Error calculating hash: ${e.message}", e)
        }
    }

    fun verifySignature(originalData: ByteArray, signature: String, certificateId: String, hashAlgorithm: String = "SHA256"): Boolean {
        return try {
            val signatureBytes = Base64.getDecoder().decode(signature)

            // Calculate hash of the original data using the same algorithm that was used for signing
            val hashBase64 = calculateHash(originalData, hashAlgorithm)
            val hashBytes = Base64.getDecoder().decode(hashBase64)

            val hashFile = File.createTempFile("hash_to_verify", ".bin")
            val signatureFile = File.createTempFile("signature_to_verify", ".bin")

            try {
                hashFile.writeBytes(hashBytes)
                signatureFile.writeBytes(signatureBytes)

                // Verify against the hash, not the original document, since the signature was created from the hash
                val verifyCommand = CommandLine.parse("pkcs11-tool --module $resolvedLibraryPath -m RSA-PKCS --verify --id $certificateId --input-file ${hashFile.absolutePath} --signature-file ${signatureFile.absolutePath}")

                val executor = DefaultExecutor()
                val outputStream = ByteArrayOutputStream()
                val errorStream = ByteArrayOutputStream()

                executor.streamHandler = PumpStreamHandler(outputStream, errorStream)
                val exitCode = executor.execute(verifyCommand)

                if (exitCode == 0) {
                    println("Signature verification successful")
                    true
                } else {
                    println("Signature verification failed: ${errorStream.toString()}")
                    false
                }

            } finally {
                if (hashFile.exists()) hashFile.delete()
                if (signatureFile.exists()) signatureFile.delete()
            }
        } catch (e: Exception) {
            println("Error during signature verification: ${e.message}")
            // If any error occurs during verification, consider it invalid
            false
        }
    }

    private fun parseSmartCardInfo(output: String): SmartCardInfoDto {
        val lines = output.split("\n")
        var label = "Unknown"
        var serialNumber: String? = null
        var manufacturer: String? = null
        var model: String? = null

        for (line in lines) {
            when {
                line.contains("Token label") -> {
                    label = line.split(":").getOrNull(1)?.trim() ?: "Unknown"
                }

                line.contains("Serial number") -> {
                    serialNumber = line.split(":").getOrNull(1)?.trim()
                }

                line.contains("Manufacturer") -> {
                    manufacturer = line.split(":").getOrNull(1)?.trim()
                }

                line.contains("Model") -> {
                    model = line.split(":").getOrNull(1)?.trim()
                }
            }
        }

        return SmartCardInfoDto(
            isPresent = true,
            label = label,
            serialNumber = serialNumber,
            manufacturer = manufacturer,
            model = model
        )
    }

    private fun parseCertificates(output: String): List<CertificateDto> {
        val certificates = mutableListOf<CertificateDto>()
        val lines = output.split("\n")

        // This is a simplified parser - in reality, you'd need more sophisticated parsing
        var currentId: String? = null
        var currentLabel: String? = null

        for (line in lines) {
            when {
                line.contains("Certificate Object") -> {
                    currentId = UUID.randomUUID().toString()
                    currentLabel = null
                }

                line.contains("label:") -> {
                    currentLabel = line.split(":").getOrNull(1)?.trim()
                }

                line.contains("ID:") -> {
                    val id = line.split(":").getOrNull(1)?.trim()
                    if (currentId != null && currentLabel != null) {
                        certificates.add(createCertificateDto(currentLabel, id))
                    }
                }
            }
        }

        return certificates
    }

    // TODO: Replace with actual certificate parsing logic
    private fun createCertificateDto(label: String, keyId: String?): CertificateDto {
        return CertificateDto(
            id = keyId ?: "",
            subject = label,
            issuer = "Unknown Issuer",
            validFrom = LocalDateTime.now().minusYears(1),
            validTo = LocalDateTime.now().plusYears(3),
            keyUsage = listOf("digitalSignature", "keyEncipherment"),
            fingerprint = "Unknown"
        )
    }
}
