// 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::time::SystemTime;

use anyhow::Result;
use chrono::{DateTime, Utc};
use clap::Parser;
use openpgp_card::ocard::KeyType;

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

#[derive(Parser, Debug)]
pub struct StatusCommand {
    #[arg(
        name = "card ident",
        short = 'c',
        long = "card",
        help = "Identifier of the card to use"
    )]
    pub ident: Option<String>,

    #[arg(
        name = "verbose",
        short = 'v',
        long = "verbose",
        help = "Use verbose output"
    )]
    pub verbose: bool,

    /// Print public key material for each key slot
    #[arg(name = "pkm", short = 'K', long = "public-key-material")]
    pub pkm: bool,
}

pub fn print_status(
    format: OutputFormat,
    output_version: OutputVersion,
    command: StatusCommand,
) -> Result<()> {
    let mut output = output::Status::default();
    output.verbose(command.verbose);
    output.pkm(command.pkm);

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

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

    // Cardholder Name
    let name = card.cardholder_name()?;
    if !name.is_empty() {
        output.cardholder_name(name);
    }

    // We ignore the Cardholder "Sex" field, because it's silly and mostly unhelpful

    // Certificate URL
    let url = card.url()?;
    if !url.is_empty() {
        output.certificate_url(url);
    }

    let login_data = card.login_data()?;
    if !login_data.is_empty() {
        output.login_data(String::from_utf8_lossy(&login_data).into());
    }

    // Language Preference
    if let Some(lang) = card.cardholder_related_data()?.lang() {
        for lang in lang {
            output.language_preference(format!("{lang}"));
        }
    }

    // key information (imported vs. generated on card)
    let ki = card.key_information().ok().flatten();

    let pws = card.pw_status_bytes()?;

    // information about subkeys

    let fps = card.fingerprints()?;
    let kgt = card.key_generation_times()?;

    let mut signature_key = output::KeySlotInfo::default();
    if let Some(fp) = fps.signature() {
        signature_key.fingerprint(fp.to_hex());
    }
    signature_key.algorithm(format!("{}", card.algorithm_attributes(KeyType::Signing)?));
    if let Some(kgt) = kgt.signature() {
        signature_key.creation_time(format!("{}", DateTime::<Utc>::from(SystemTime::from(kgt))));
    }
    if let Some(uif) = card.user_interaction_flag(KeyType::Signing)? {
        signature_key.touch_policy(format!("{}", uif.touch_policy()));
        signature_key.touch_features(format!("{}", uif.features()));
    }
    if let Some(ks) = ki.as_ref().map(|ki| ki.sig_status()) {
        signature_key.status(format!("{ks}"));
    }

    if let Ok(pkm) = card.public_key_material(KeyType::Signing) {
        signature_key.public_key_material(pkm.to_string());
    }

    output.signature_key(signature_key);

    let dsc = card.digital_signature_count()?;
    output.signature_count(dsc);

    let mut decryption_key = output::KeySlotInfo::default();
    if let Some(fp) = fps.decryption() {
        decryption_key.fingerprint(fp.to_hex());
    }
    decryption_key.algorithm(format!(
        "{}",
        card.algorithm_attributes(KeyType::Decryption)?
    ));
    if let Some(kgt) = kgt.decryption() {
        decryption_key.creation_time(format!("{}", DateTime::<Utc>::from(SystemTime::from(kgt))));
    }
    if let Some(uif) = card.user_interaction_flag(KeyType::Decryption)? {
        decryption_key.touch_policy(format!("{}", uif.touch_policy()));
        decryption_key.touch_features(format!("{}", uif.features()));
    }
    if let Some(ks) = ki.as_ref().map(|ki| ki.dec_status()) {
        decryption_key.status(format!("{ks}"));
    }
    if let Ok(pkm) = card.public_key_material(KeyType::Decryption) {
        decryption_key.public_key_material(pkm.to_string());
    }
    output.decryption_key(decryption_key);

    let mut authentication_key = output::KeySlotInfo::default();
    if let Some(fp) = fps.authentication() {
        authentication_key.fingerprint(fp.to_hex());
    }
    authentication_key.algorithm(format!(
        "{}",
        card.algorithm_attributes(KeyType::Authentication)?
    ));
    if let Some(kgt) = kgt.authentication() {
        authentication_key
            .creation_time(format!("{}", DateTime::<Utc>::from(SystemTime::from(kgt))));
    }
    if let Some(uif) = card.user_interaction_flag(KeyType::Authentication)? {
        authentication_key.touch_policy(format!("{}", uif.touch_policy()));
        authentication_key.touch_features(format!("{}", uif.features()));
    }
    if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) {
        authentication_key.status(format!("{ks}"));
    }
    if let Ok(pkm) = card.public_key_material(KeyType::Authentication) {
        authentication_key.public_key_material(pkm.to_string());
    }
    output.authentication_key(authentication_key);

    let mut attestation_key = output::KeySlotInfo::default();
    if let Ok(Some(fp)) = card.fingerprint(KeyType::Attestation) {
        attestation_key.fingerprint(fp.to_hex());
    }
    if let Ok(algo) = card.algorithm_attributes(KeyType::Attestation) {
        attestation_key.algorithm(format!("{algo}"));
    }
    if let Ok(Some(kgt)) = card.key_generation_time(KeyType::Attestation) {
        attestation_key.creation_time(format!("{}", DateTime::<Utc>::from(SystemTime::from(&kgt))));
    }
    if let Some(uif) = card.user_interaction_flag(KeyType::Attestation)? {
        attestation_key.touch_policy(format!("{}", uif.touch_policy()));
        attestation_key.touch_features(format!("{}", uif.features()));
    }

    // "Key-Ref = 0x81 is reserved for the Attestation key of Yubico"
    // (see OpenPGP card spec 3.4.1 pg.43)
    if let Some(ki) = ki.as_ref()
        && let Some(n) = (0..ki.num_additional()).find(|&n| ki.additional_ref(n) == 0x81)
    {
        let ks = ki.additional_status(n);
        attestation_key.status(format!("{ks}"));
    };

    output.attestation_key(attestation_key);

    // technical details about the card's state
    output.user_pin_valid_for_only_one_signature(pws.pw1_cds_valid_once());

    output.user_pin_remaining_attempts(pws.err_count_pw1());
    output.admin_pin_remaining_attempts(pws.err_count_pw3());
    output.reset_code_remaining_attempts(pws.err_count_rc());

    if let Ok(kdf) = card.kdf_do()
        && kdf.kdf_algo() != 0
    {
        output.kdf_mode(true);
    }

    if let Some(ki) = ki {
        let num = ki.num_additional();
        for i in 0..num {
            // 0x81 is the Yubico attestation key, it has already been used above -> skip here
            if ki.additional_ref(i) != 0x81 {
                output.additional_key_status(
                    ki.additional_ref(i),
                    ki.additional_status(i).to_string(),
                );
            }
        }
    }

    if let Ok(fps) = card.ca_fingerprints() {
        for fp in fps.iter().flatten() {
            output.ca_fingerprint(fp.to_string());
        }
    }

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

    Ok(())
}
