OneTimeSetCommandHandler.kt

package de.pflugradts.passbird.application.commandhandling.handler.egg
import de.pflugradts.passbird.application.UserInterfaceAdapterPort
import de.pflugradts.passbird.application.commandhandling.CommandExecutionTracker
import de.pflugradts.passbird.application.commandhandling.command.OneTimeSetCommand
import de.pflugradts.passbird.application.commandhandling.handler.TypedCommandHandler
import de.pflugradts.passbird.application.configuration.ReadableConfiguration
import de.pflugradts.passbird.domain.model.egg.InvalidEggIdException
import de.pflugradts.passbird.domain.model.egg.PasswordRequirements
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import de.pflugradts.passbird.domain.model.transfer.Output.Companion.outputOf
import de.pflugradts.passbird.domain.model.transfer.OutputFormatting.OPERATION_ABORTED
import de.pflugradts.passbird.domain.service.password.PasswordService
import de.pflugradts.passbird.domain.service.password.PasswordService.EggNotExistsAction.DO_NOTHING
import de.pflugradts.passbird.domain.service.password.provider.PasswordProvider
class OneTimeSetCommandHandler constructor(
    private val configuration: ReadableConfiguration,
    private val passwordService: PasswordService,
    private val passwordProvider: PasswordProvider,
    private val userInterfaceAdapterPort: UserInterfaceAdapterPort,
    private val commandExecutionTracker: CommandExecutionTracker,
) : TypedCommandHandler<OneTimeSetCommand>(OneTimeSetCommand::class.java) {
    override fun handleCommand(command: OneTimeSetCommand) {
        try {
            passwordService.challengeEggId(command.argument)
            val passwordLengthInput = receivePasswordLength()
            when {
                passwordLengthInput.isAborted -> {
                    commandExecutionTracker.markAborted()
                    userInterfaceAdapterPort.send(outputOf(shellOf("Empty input - Operation aborted."), OPERATION_ABORTED))
                }

                passwordLengthInput.value == null -> {
                    commandExecutionTracker.markAborted()
                    userInterfaceAdapterPort.send(
                        outputOf(shellOf("Specified configuration is invalid - Operation aborted."), OPERATION_ABORTED),
                    )
                }

                else -> {
                    val passwordRequirements = receivePasswordRequirements(passwordLengthInput.value)
                    when {
                        !passwordRequirements.isValid() -> {
                            commandExecutionTracker.markAborted()
                            userInterfaceAdapterPort.send(
                                outputOf(shellOf("Specified configuration is invalid - Operation aborted."), OPERATION_ABORTED),
                            )
                        }

                        commandConfirmed(command) -> {
                            if (passwordService.putEgg(
                                    command.argument,
                                    passwordProvider.createNewPassword(passwordRequirements),
                                ).failure
                            ) {
                                commandExecutionTracker.markFailure()
                            }
                        }

                        else -> {
                            commandExecutionTracker.markAborted()
                            userInterfaceAdapterPort.send(outputOf(shellOf("Operation aborted."), OPERATION_ABORTED))
                        }
                    }
                }
            }
        } catch (ex: InvalidEggIdException) {
            commandExecutionTracker.markAborted()
            userInterfaceAdapterPort.send(outputOf(shellOf("${ex.message} - Operation aborted."), OPERATION_ABORTED))
        }
        command.invalidateInput()
        userInterfaceAdapterPort.sendLineBreak()
    }
    private fun receivePasswordLength() = userInterfaceAdapterPort.receive(
        outputOf(shellOf("Enter password length or just press enter to abort: ")),
    ).let {
        try {
            PasswordLengthInput(
                value = if (it.isEmpty) null else it.shell.asString().toIntOrNull(),
                isAborted = it.isEmpty,
            )
        } finally {
            it.invalidate()
        }
    }
    private fun receivePasswordRequirements(passwordLength: Int): PasswordRequirements {
        val hasNumbers = userInterfaceAdapterPort.receiveYes(outputOf(shellOf("Include numbers? Y/n ")))
        val hasLowercaseLetters = userInterfaceAdapterPort.receiveYes(outputOf(shellOf("Include lowercase letters? Y/n ")))
        val hasUppercaseLetters = userInterfaceAdapterPort.receiveYes(outputOf(shellOf("Include uppercase letters? Y/n ")))
        val hasSpecialCharacters = userInterfaceAdapterPort.receiveYes(outputOf(shellOf("Include special characters? Y/n ")))
        return PasswordRequirements(
            length = passwordLength,
            hasNumbers = hasNumbers,
            hasLowercaseLetters = hasLowercaseLetters,
            hasUppercaseLetters = hasUppercaseLetters,
            hasSpecialCharacters = hasSpecialCharacters,
            unusedSpecialCharacters = if (hasSpecialCharacters) receiveUnusedSpecialCharacters() else "",
        )
    }
    private fun receiveUnusedSpecialCharacters() = userInterfaceAdapterPort.receive(
        outputOf(shellOf("Enter unused special characters or just press enter to keep all: ")),
    ).let {
        try {
            it.shell.asString()
        } finally {
            it.invalidate()
        }
    }
    private fun commandConfirmed(command: OneTimeSetCommand) =
        if (configuration.application.password.promptOnRemoval && passwordService.eggExists(command.argument, DO_NOTHING)) {
            userInterfaceAdapterPort
                .receiveConfirmation(
                    outputOf(
                        shellOf(
                            "Existing Egg '${command.argument.asString()}' will be irrevocably overwritten.\n" +
                                "Input 'c' to confirm or anything else to abort.\nYour input: ",
                        ),
                    ),
                )
        } else {
            true
        }
}
private data class PasswordLengthInput(val value: Int?, val isAborted: Boolean)