// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

use anyhow::{anyhow, Result};
use clap::{Parser, ValueEnum};
use openpgp_card_state::*;

#[derive(Parser, Debug)]
#[command(about, author, name = env!("CARGO_BIN_NAME"), version)]
pub struct Cli {
    #[command(subcommand)]
    pub cmd: Command,
}

#[derive(Parser, Debug)]
pub enum Command {
    /// Show state of one card
    Get(CardIdent),

    /// Store information for one card
    Put(PutCommand),

    /// Drop stored User PIN for one card
    DropPin(CardIdent),

    /// Delete information for one card
    Delete(CardIdent),
}

#[derive(Parser, Debug)]
pub struct CardIdent {
    #[arg(name = "card ident", help = "Identifier of a card")]
    pub ident: String,
}

#[derive(Parser, Debug)]
pub struct PutCommand {
    #[arg(name = "card ident", help = "Identifier of a card")]
    pub ident: String,

    #[arg(name = "An optional nickname for this card")]
    pub nickname: Option<String>,

    #[arg(
        name = "PIN storage backend",
        short = 'b',
        long = "pin-backend",
        help = "Storage backend for this User PIN"
    )]
    pin_backend: Option<PinBackend>,
}

#[derive(ValueEnum, Debug, Clone)]
pub enum PinBackend {
    Keyring,
    Direct,
}

impl From<PinBackend> for PinStorageType {
    fn from(value: PinBackend) -> Self {
        match value {
            PinBackend::Keyring => Self::Keyring,
            PinBackend::Direct => Self::Direct,
        }
    }
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.cmd {
        Command::Get(ident) => {
            show(&ident.ident)?;
        }
        Command::Put(put) => {
            let pin = rpassword::prompt_password("Enter User PIN: ")?;

            store(&put.ident, &pin, put.nickname, put.pin_backend)?;
        }
        Command::DropPin(ident) => {
            drop_pin(&ident.ident)?;
        }
        Command::Delete(ident) => {
            delete(&ident.ident)?;
        }
    }

    Ok(())
}

fn show(ident: &str) -> Result<()> {
    let pin =
        get_pin(ident).map_err(|e| anyhow!("Failed to get User PIN for card {}: {}", ident, e))?;
    let nickname = get_nickname(ident)
        .map_err(|e| anyhow!("Failed to get User PIN for card {}: {}", ident, e))?;

    if let Some(pin) = pin {
        println!("Persisted User PIN for OpenPGP card {}: '{}'", ident, pin);
    }
    if let Some(nickname) = nickname {
        println!("Persisted nickname: '{}'", nickname);
    }

    Ok(())
}

fn store(
    ident: &str,
    pin: &str,
    nickname: Option<String>,
    pin_backend: Option<PinBackend>,
) -> Result<()> {
    set_pin(ident, pin, pin_backend.map(Into::into))
        .map_err(|e| anyhow!("Failed to set User PIN for card {}: {}", ident, e))?;

    set_nickname(ident, nickname.as_deref())
        .map_err(|e| anyhow!("Failed to set nickname for card {}: {}", ident, e))?;

    println!("stored.");
    Ok(())
}

fn delete(ident: &str) -> Result<()> {
    openpgp_card_state::delete(ident)
        .map_err(|e| anyhow!("Failed to delete User PIN for card {}: {}", ident, e))?;

    println!("deleted.");
    Ok(())
}
