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

//! Read a transferable public key from stdin and show status information about it

use std::{
    io::{BufReader, stdin},
    time::SystemTime,
};

use chrono::{DateTime, Utc};
use minipgp6_armor::{DataType, read::AutoDearmorer};
use minipgp6_packet::{
    body::{public_key::PublicKey, secret_key::SecretKey},
    transferable::{KeyPacket, TransferableKey},
    types::time::Timestamp,
};
use minipgp6_validity::{Checked, Encrypt, KeyFlags, Status};

fn fmt_status(s: &Status) -> String {
    match s {
        Status::Valid { expires: None } => "✅ Active (no expiration)".to_string(),
        Status::Valid { expires: Some(exp) } => {
            format!(
                "✅ Active, expires {}",
                DateTime::<Utc>::from(SystemTime::from(*exp))
            )
        }

        Status::Expired { time } => {
            format!(
                "🚫 Expired {}",
                DateTime::<Utc>::from(SystemTime::from(*time))
            )
        }

        Status::RevokedSoft { time, reason: msg } => {
            format!(
                "🚫 Revoked (soft) {}: {msg}",
                DateTime::<Utc>::from(SystemTime::from(*time))
            )
        }
        Status::RevokedHard { time, reason: msg } => {
            format!(
                "🚫 Revoked (hard) {}: {msg}",
                DateTime::<Utc>::from(SystemTime::from(*time))
            )
        }

        Status::Invalid { context } => format!("🚫 Invalid: {context}"),
    }
}

fn fmt_key_flags(key_flags: &KeyFlags<'_>) -> String {
    let mut flags = Vec::new();
    if key_flags.certification_capable() {
        flags.push("Certify")
    }

    if let Some(enc) = key_flags.encryption_capable() {
        // FIXME: upstream this2
        match enc {
            Encrypt::Both => flags.push("Encrypt"),
            Encrypt::Comms => flags.push("Encrypt (comms)"),
            Encrypt::Storage => flags.push("Encrypt (storage)"),
        }
    }

    if key_flags.data_signing_capable() {
        flags.push("Sign")
    }

    flags.join(", ")
}

fn print_status<K: KeyPacket>(checked: Checked<'_, K>) {
    let (pri, status, kf) = checked.primary_status_at(Timestamp::now());

    println!(
        "🔐 {:?} {}",
        pri.component.public().public_key_algorithm(),
        pri.component.public().fingerprint()
    );
    println!(
        "  ⏱️ Created {}",
        DateTime::<Utc>::from(SystemTime::from(pri.component.public().creation_time))
    );
    println!("  {}", fmt_status(&status));

    if let Some(key_flags) = kf {
        println!("  🏴 Key flags: {}", fmt_key_flags(&key_flags));
    }

    println!();

    for (subkey, status, kf) in checked.subkeys_status() {
        println!(
            "  🔑 {:?} {}",
            subkey.component.public().public_key_algorithm(),
            subkey.component.public().fingerprint()
        );
        println!(
            "    ⏱️ Created {}",
            DateTime::<Utc>::from(SystemTime::from(subkey.component.public().creation_time))
        );
        println!("    {}", fmt_status(&status));

        if let Some(key_flags) = kf {
            println!("    🏴 Key flags: {}", fmt_key_flags(&key_flags));
        }

        println!();
    }

    for (user_id, status) in checked.user_ids_status() {
        println!("  🪪 ID {:?}", user_id.component.id());
        println!("    {}", fmt_status(&status));
        println!();
    }
}

fn print<K: KeyPacket>(transferable: Vec<TransferableKey<K>>) {
    if transferable.is_empty() {
        eprintln!("WARN: No certificates found");
    }

    for cert in transferable {
        // TODO: could detect and report AC2 shaped transferable keys

        let checked = Checked::from(&cert);
        print_status(checked);
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let binary = AutoDearmorer::new(BufReader::new(stdin()), None)?;

    // FIXME: this check only detects TSKs when they are armored
    match binary.data_type() {
        Some(DataType::Private) => {
            let tsks = TransferableKey::<SecretKey>::read(&mut BufReader::new(binary))?;

            println!("*** Transferable Secret Key(s) ***");
            println!();

            print(tsks);
        }
        _ => {
            let certs = TransferableKey::<PublicKey>::read(&mut BufReader::new(binary))?;
            print(certs);
        }
    }

    Ok(())
}