// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
// SPDX-FileCopyrightText: 2022 Lars Wirzenius <liw@liw.fi>
// SPDX-FileCopyrightText: 2022 Nora Widdecke <mail@nora.pink>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::path::PathBuf;

use anyhow::Result;
use clap::{Parser, ValueEnum};
use openpgp_card::ocard::KeyType;

use crate::{
    ENTER_USER_PIN,
    output,
    pick_card_for_reading,
    util,
    versioned_output::{OutputBuilder, OutputFormat, OutputVersion},
};

#[derive(Parser, Debug)]
pub struct AttestationCommand {
    #[command(subcommand)]
    pub cmd: AttSubCommand,
}

#[derive(Parser, Debug)]
pub enum AttSubCommand {
    /// Print the card's attestation certificate
    ///
    /// New YubiKeys are preloaded with an attestation certificate issued by the Yubico CA.
    Cert {
        #[arg(
            name = "card ident",
            short = 'c',
            long = "card",
            help = "Identifier of the card to use"
        )]
        ident: Option<String>,
    },

    /// Generate attestation statement for one of the key slots on the card
    ///
    /// An attestation statement can only be generated for key slots that contain keys that were
    /// generated by the card. See 'oct admin generate' and 'oct status -v'.
    Generate {
        #[arg(
            name = "card ident",
            short = 'c',
            long = "card",
            help = "Identifier of the card to use"
        )]
        ident: String,

        /// Key slot to use
        #[arg(name = "Key slot", short = 'k', long = "key", value_enum)]
        key: BaseKeySlot,

        #[arg(
            name = "User PIN file",
            short = 'p',
            long = "user-pin",
            help = "Optionally, get User PIN from a file"
        )]
        user_pin: Option<PathBuf>,
    },

    /// Print the attestation statement for one of the key slots on the card
    ///
    /// An attestation statement can only be printed after generating it. See 'oct attestation
    /// generate'.
    Statement {
        #[arg(
            name = "card ident",
            short = 'c',
            long = "card",
            help = "Identifier of the card to reset"
        )]
        ident: Option<String>,

        /// Key slot to use
        #[arg(name = "Key slot", short = 'k', long = "key", value_enum)]
        key: BaseKeySlot,
    },
}

#[derive(ValueEnum, Debug, Clone)]
#[value(rename_all = "UPPER")]
pub enum BaseKeySlot {
    Sig,
    Dec,
    Aut,
}

impl From<BaseKeySlot> for KeyType {
    fn from(ks: BaseKeySlot) -> Self {
        match ks {
            BaseKeySlot::Sig => KeyType::Signing,
            BaseKeySlot::Dec => KeyType::Decryption,
            BaseKeySlot::Aut => KeyType::Authentication,
        }
    }
}

pub fn attestation(
    output_format: OutputFormat,
    output_version: OutputVersion,
    command: AttestationCommand,
) -> Result<(), Box<dyn std::error::Error>> {
    match command.cmd {
        AttSubCommand::Cert { ident } => cert(output_format, output_version, ident),
        AttSubCommand::Generate {
            ident,
            key,
            user_pin,
        } => generate(&ident, key, user_pin),
        AttSubCommand::Statement { ident, key } => statement(ident, key),
    }
}

fn cert(
    output_format: OutputFormat,
    output_version: OutputVersion,
    ident: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut output = output::AttestationCert::default();

    let mut open = pick_card_for_reading(ident)?;
    let mut card = open.transaction()?;

    output.ident(card.application_identifier()?.ident());

    if let Ok(ac) = card.attestation_certificate() {
        let pem = util::pem_encode(ac);
        output.attestation_cert(pem);
    }

    println!("{}", output.print(output_format, output_version)?);
    Ok(())
}

fn generate(
    ident: &str,
    key: BaseKeySlot,
    user_pin: Option<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut open = util::open_card(ident)?;
    let mut card = open.transaction()?;

    let user_pin = util::get_pin(&mut card, user_pin, ENTER_USER_PIN)?;

    let mut sign = util::verify_to_sign(&mut card, user_pin)?;

    let kt = KeyType::from(key);
    sign.generate_attestation(kt, &|| {
        eprintln!("Touch confirmation needed to generate an attestation")
    })?;
    Ok(())
}

fn statement(ident: Option<String>, key: BaseKeySlot) -> Result<(), Box<dyn std::error::Error>> {
    let mut open = pick_card_for_reading(ident)?;
    let mut card = open.transaction()?;

    // Get cardholder certificate from card.

    // Select cardholder certificate
    match key {
        BaseKeySlot::Aut => card.select_data(0, &[0x7F, 0x21])?,
        BaseKeySlot::Dec => card.select_data(1, &[0x7F, 0x21])?,
        BaseKeySlot::Sig => card.select_data(2, &[0x7F, 0x21])?,
    };

    // Get DO "cardholder certificate" (returns the slot that was previously selected)
    let cert = card.cardholder_certificate()?;

    if !cert.is_empty() {
        let pem = util::pem_encode(cert);
        println!("{pem}");
    } else {
        eprintln!("Cardholder certificate slot is empty");
    }
    Ok(())
}
