//! Certificate-based PDF encryption
//!
//! Supports PKCS#7/CMS EnvelopedData structures for encrypting PDFs with
//! X.509 certificates. Each recipient's public key encrypts a shared
//! content encryption key (CEK); only holders of the corresponding
//! private key can decrypt.
//!
//! Per PDF 2.0 spec: /CF (CryptFilter) and /Recipients in the encryption dictionary.

use crate::fitz::error::{Error, Result};
use std::collections::HashMap;

// ============================================================================
// Types
// ============================================================================

/// Content encryption algorithm for certificate-based encryption
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionMethod {
    /// AES-128-CBC
    AES128,
    /// AES-256-CBC
    AES256,
}

impl EncryptionMethod {
    /// Key length in bytes
    pub fn key_length(&self) -> usize {
        match self {
            Self::AES128 => 16,
            Self::AES256 => 32,
        }
    }

    /// PDF crypt filter name
    pub fn filter_name(&self) -> &'static str {
        match self {
            Self::AES128 => "AESV2",
            Self::AES256 => "AESV3",
        }
    }
}

/// A single certificate recipient with per-recipient permissions
#[derive(Debug, Clone)]
pub struct CertRecipient {
    /// X.509 certificate in DER encoding
    pub certificate_der: Vec<u8>,
    /// PDF permission flags (bits 2–12 per PDF spec)
    pub permissions: u32,
}

/// Configuration for certificate-based encryption
#[derive(Debug, Clone)]
pub struct CertEncryptionConfig {
    /// Recipients (certificate + permissions each)
    pub recipients: Vec<CertRecipient>,
    /// Default permissions (used when building envelope; per-recipient overrides)
    pub permissions: u32,
    /// Content encryption method
    pub encryption_method: EncryptionMethod,
}

/// Parsed recipient info from a PKCS#7 EnvelopedData
#[derive(Debug, Clone)]
pub struct RecipientInfo {
    /// Issuer and serial (for KeyTransRecipientInfo)
    pub issuer_serial: Option<Vec<u8>>,
    /// Subject key identifier (alternative identifier)
    pub subject_key_id: Option<Vec<u8>>,
    /// Encrypted content encryption key
    pub encrypted_key: Vec<u8>,
}

/// Parsed PKCS#7 EnvelopedData
#[derive(Debug, Clone)]
pub struct Pkcs7Envelope {
    /// Version (0, 2, 3, or 4)
    pub version: u32,
    /// Recipient infos (one per recipient)
    pub recipient_infos: Vec<RecipientInfo>,
    /// Content encryption algorithm OID
    pub content_encryption_oid: Vec<u8>,
    /// Encrypted content (if present)
    pub encrypted_content: Option<Vec<u8>>,
}

/// PDF encryption dictionary entries for certificate-based security
#[derive(Debug, Clone)]
pub struct CertEncryptDict {
    /// /CF dictionary: name -> CryptFilter dictionary
    pub cf: HashMap<String, CryptFilterEntry>,
    /// /Recipients: array of hex-encoded recipient strings (from EnvelopedData)
    pub recipients: Vec<Vec<u8>>,
}

/// A single crypt filter entry in /CF
#[derive(Debug, Clone)]
pub struct CryptFilterEntry {
    /// /CFM: e.g. /AESV2, /AESV3
    pub cfm: String,
    /// /AuthEvent: /DocOpen or /EFOpen
    pub auth_event: String,
    /// /Length: key length in bits
    pub length: u32,
}

// ============================================================================
// PKCS#7 EnvelopedData
// ============================================================================

/// OID for PKCS#7 envelopedData (RFC 2315)
const OID_PKCS7_ENVELOPED_DATA: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x03];

/// Build a PKCS#7 EnvelopedData structure.
///
/// Generates a random CEK and would encrypt it for each recipient's public key.
/// **Stub:** RSA encryption is not implemented; returns a clear error when
/// recipients are present. A crypto library (e.g. `rsa`, `ring`) is required
/// for actual certificate-based encryption.
pub fn build_pkcs7_envelope(config: &CertEncryptionConfig) -> Result<Vec<u8>> {
    if config.recipients.is_empty() {
        return Err(Error::Encryption(
            "Certificate-based encryption requires at least one recipient".into(),
        ));
    }

    // Stub: we would generate CEK and encrypt with each recipient's public key.
    // Without a crypto library we cannot perform RSA-OAEP or RSA-PKCS1v15.
    Err(Error::Encryption(
        "RSA encryption of content encryption key requires a crypto library (e.g. rsa, ring). \
         Certificate-based encryption is not yet implemented. \
         Add the rsa crate and implement encrypt_cek_for_recipient."
            .into(),
    ))
}

/// Parse a PKCS#7 EnvelopedData structure from DER bytes.
///
/// Extracts recipient infos and encrypted CEKs. Does not perform RSA decryption.
pub fn parse_pkcs7_envelope(data: &[u8]) -> Result<Pkcs7Envelope> {
    let mut offset = 0;

    // ContentInfo ::= SEQUENCE { contentType, content [0] EXPLICIT }
    let (tag, len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(len);
    if tag != 0x30 {
        return Err(Error::Format("Expected SEQUENCE for ContentInfo".into()));
    }

    let content_info_end = offset + len as usize;
    if content_info_end > data.len() {
        return Err(Error::Format("ContentInfo truncated".into()));
    }

    // contentType OBJECT IDENTIFIER
    let (_, oid_len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(oid_len);
    let oid_start = offset;
    offset += oid_len as usize;
    let _content_type_oid = &data[oid_start..offset];

    // content [0] EXPLICIT EnvelopedData
    let (tag, expl_len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(expl_len);
    if tag != 0xa0 {
        return Err(Error::Format(
            "Expected [0] EXPLICIT for EnvelopedData".into(),
        ));
    }

    let env_start = offset;
    let env_end = offset + expl_len as usize;
    if env_end > data.len() {
        return Err(Error::Format("EnvelopedData truncated".into()));
    }

    // EnvelopedData ::= SEQUENCE { version, recipientInfos, encryptedContentInfo }
    let (env_tag, env_len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(env_len);
    if env_tag != 0x30 {
        return Err(Error::Format("Expected SEQUENCE for EnvelopedData".into()));
    }

    // version INTEGER
    let (ver_tag, ver_len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(ver_len);
    if ver_tag != 0x02 {
        return Err(Error::Format("Expected INTEGER for version".into()));
    }
    let version = read_int(data, offset, ver_len as usize)?;
    offset += ver_len as usize;

    // recipientInfos SET OF RecipientInfo
    let (ri_tag, ri_len) = read_tl(data, offset)?;
    offset += 1 + der_len_size(ri_len);
    let ri_end = offset + ri_len as usize;

    let mut recipient_infos = Vec::new();
    while offset < ri_end && offset < data.len() {
        let (seq_tag, seq_len) = read_tl(data, offset)?;
        let seq_start = offset;
        offset += 1 + der_len_size(seq_len);
        let seq_end = offset + seq_len as usize;

        if seq_tag == 0x31 || seq_tag == 0x30 {
            // SET or SEQUENCE (KeyTransRecipientInfo)
            if let Ok(ri) = parse_recipient_info(data, offset, seq_end) {
                recipient_infos.push(ri);
            }
        }
        offset = seq_end;
    }

    if recipient_infos.is_empty() {
        return Err(Error::Format(
            "No RecipientInfo found in EnvelopedData".into(),
        ));
    }

    // encryptedContentInfo EncryptedContentInfo
    let mut content_encryption_oid = Vec::new();
    let mut encrypted_content = None;

    if offset < env_start + env_len as usize + 1 + der_len_size(env_len) {
        // EncryptedContentInfo ::= SEQUENCE { contentType, contentEncryptionAlgorithm, encryptedContent [0] }
        let (eci_tag, eci_len) = read_tl(data, offset)?;
        offset += 1 + der_len_size(eci_len);
        if eci_tag == 0x30 {
            let eci_end = offset + eci_len as usize;
            // contentType
            let (_, ct_len) = read_tl(data, offset)?;
            offset += 1 + der_len_size(ct_len) + ct_len as usize;
            // contentEncryptionAlgorithm SEQUENCE { algorithm OID, parameters }
            let (_, cea_len) = read_tl(data, offset)?;
            offset += 1 + der_len_size(cea_len);
            let (_, oid_len) = read_tl(data, offset)?;
            offset += 1 + der_len_size(oid_len);
            content_encryption_oid = data[offset..offset + oid_len as usize].to_vec();
            offset += oid_len as usize;
            // encryptedContent [0] IMPLICIT
            if offset < eci_end {
                let (imp_tag, imp_len) = read_tl(data, offset)?;
                if imp_tag == 0x80 || imp_tag == 0xa0 {
                    offset += 2 + der_len_size(imp_len);
                    encrypted_content = Some(data[offset..offset + imp_len as usize].to_vec());
                }
            }
        }
    }

    Ok(Pkcs7Envelope {
        version,
        recipient_infos,
        content_encryption_oid,
        encrypted_content,
    })
}

fn parse_recipient_info(data: &[u8], start: usize, end: usize) -> Result<RecipientInfo> {
    let mut offset = start;
    let mut issuer_serial = None;
    let mut subject_key_id = None;
    let mut encrypted_key = Vec::new();

    // KeyTransRecipientInfo: version, rid, keyEncryptionAlgorithm, encryptedKey
    let mut elem_idx = 0;
    while offset < end && offset < data.len() {
        let (tag, len) = read_tl(data, offset)?;
        let len_size = der_len_size(len);
        let tl_size = 1 + len_size;
        let elem_start = offset;
        offset += tl_size;
        let elem_end = offset + len as usize;

        if elem_end > data.len() {
            break;
        }

        match (elem_idx, tag) {
            (0, 0x02) => {
                // version - skip
            }
            (1, 0x30) => {
                issuer_serial = Some(data[elem_start..elem_end].to_vec());
            }
            (1, 0x04) => {
                subject_key_id = Some(data[offset..elem_end].to_vec());
            }
            (2, 0x30) => {
                // keyEncryptionAlgorithm - skip
            }
            _ => {}
        }

        if tag == 0x04 {
            // encryptedKey is the OCTET STRING in KeyTransRecipientInfo (4th element)
            encrypted_key = data[offset..elem_end].to_vec();
        }

        elem_idx += 1;
        offset = elem_end;
    }

    Ok(RecipientInfo {
        issuer_serial,
        subject_key_id,
        encrypted_key,
    })
}

fn read_tl(data: &[u8], offset: usize) -> Result<(u8, u32)> {
    if offset + 2 > data.len() {
        return Err(Error::Format("Truncated DER".into()));
    }
    let tag = data[offset];
    let len_byte = data[offset + 1];
    let (len, len_size) = if len_byte & 0x80 == 0 {
        (len_byte as u32, 1)
    } else {
        let n = (len_byte & 0x7f) as usize;
        if offset + 2 + n > data.len() {
            return Err(Error::Format("Truncated DER length".into()));
        }
        let mut v: u32 = 0;
        for i in 0..n {
            v = (v << 8) | data[offset + 2 + i] as u32;
        }
        (v, 1 + n)
    };
    Ok((tag, len))
}

fn der_len_size(len: u32) -> usize {
    if len < 0x80 {
        1
    } else if len < 0x100 {
        2
    } else if len < 0x10000 {
        3
    } else {
        4
    }
}

fn read_int(data: &[u8], offset: usize, len: usize) -> Result<u32> {
    if offset + len > data.len() {
        return Err(Error::Format("Truncated integer".into()));
    }
    let mut v: u32 = 0;
    for i in 0..len {
        v = (v << 8) | data[offset + i] as u32;
    }
    Ok(v)
}

// ============================================================================
// PDF Encryption Dictionary
// ============================================================================

/// Build PDF encryption dictionary entries for certificate-based security.
///
/// Produces /CF and /Recipients suitable for the /Encrypt dictionary.
pub fn build_cert_encrypt_dict(
    config: &CertEncryptionConfig,
    recipients_hex_strings: &[Vec<u8>],
) -> CertEncryptDict {
    let mut cf = HashMap::new();
    let cfm = config.encryption_method.filter_name();
    let length_bits = (config.encryption_method.key_length() * 8) as u32;

    cf.insert(
        "DefaultCryptFilter".to_string(),
        CryptFilterEntry {
            cfm: cfm.to_string(),
            auth_event: "DocOpen".to_string(),
            length: length_bits,
        },
    );

    CertEncryptDict {
        cf,
        recipients: recipients_hex_strings.to_vec(),
    }
}

/// Extract per-recipient permissions from a PKCS#7 EnvelopedData.
///
/// In PDF certificate encryption, permissions can be embedded in the
/// RecipientInfo (e.g. in a custom attribute). This parses such structures.
/// For standard KeyTransRecipientInfo without embedded permissions,
/// returns None for each recipient.
pub fn extract_permissions(envelope: &Pkcs7Envelope) -> Vec<Option<u32>> {
    envelope
        .recipient_infos
        .iter()
        .map(|ri| {
            // Standard RecipientInfo does not carry permissions.
            // Adobe's format may use attributes; we return None for now.
            let _ = ri;
            None
        })
        .collect()
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encryption_method() {
        assert_eq!(EncryptionMethod::AES128.key_length(), 16);
        assert_eq!(EncryptionMethod::AES256.key_length(), 32);
        assert_eq!(EncryptionMethod::AES128.filter_name(), "AESV2");
        assert_eq!(EncryptionMethod::AES256.filter_name(), "AESV3");
    }

    #[test]
    fn test_cert_recipient() {
        let r = CertRecipient {
            certificate_der: vec![0x30, 0x82, 0x01, 0x00],
            permissions: 0xFFFFF0C0,
        };
        assert_eq!(r.certificate_der.len(), 4);
        assert_eq!(r.permissions, 0xFFFFF0C0);
    }

    #[test]
    fn test_cert_encryption_config() {
        let config = CertEncryptionConfig {
            recipients: vec![CertRecipient {
                certificate_der: vec![1, 2, 3],
                permissions: 0xFFFFF0C0,
            }],
            permissions: 0xFFFFF0C0,
            encryption_method: EncryptionMethod::AES256,
        };
        assert_eq!(config.recipients.len(), 1);
        assert_eq!(config.encryption_method, EncryptionMethod::AES256);
    }

    #[test]
    fn test_build_pkcs7_envelope_requires_recipients() {
        let config = CertEncryptionConfig {
            recipients: vec![],
            permissions: 0,
            encryption_method: EncryptionMethod::AES128,
        };
        let r = build_pkcs7_envelope(&config);
        assert!(r.is_err());
        assert!(
            r.unwrap_err()
                .to_string()
                .contains("at least one recipient")
        );
    }

    #[test]
    fn test_build_pkcs7_envelope_stubs_rsa() {
        let config = CertEncryptionConfig {
            recipients: vec![CertRecipient {
                certificate_der: vec![0x30, 0x82, 0x01, 0x00],
                permissions: 0xFFFFF0C0,
            }],
            permissions: 0xFFFFF0C0,
            encryption_method: EncryptionMethod::AES128,
        };
        let r = build_pkcs7_envelope(&config);
        assert!(r.is_err());
        let err_msg = r.unwrap_err().to_string();
        assert!(err_msg.contains("RSA"));
        assert!(err_msg.contains("crypto library"));
    }

    #[test]
    fn test_parse_pkcs7_envelope_invalid() {
        let empty: &[u8] = &[];
        assert!(parse_pkcs7_envelope(empty).is_err());

        let short = &[0x30, 0x00];
        assert!(parse_pkcs7_envelope(short).is_err());
    }

    #[test]
    fn test_build_cert_encrypt_dict() {
        let config = CertEncryptionConfig {
            recipients: vec![],
            permissions: 0xFFFFF0C0,
            encryption_method: EncryptionMethod::AES256,
        };
        let recipients_hex = vec![vec![0x30, 0x31, 0x32, 0x33], vec![0x41, 0x42, 0x43, 0x44]];
        let dict = build_cert_encrypt_dict(&config, &recipients_hex);
        assert_eq!(dict.recipients.len(), 2);
        assert!(dict.cf.contains_key("DefaultCryptFilter"));
        let entry = dict.cf.get("DefaultCryptFilter").unwrap();
        assert_eq!(entry.cfm, "AESV3");
        assert_eq!(entry.length, 256);
    }

    #[test]
    fn test_permission_flags() {
        // PDF permission bits (from crypt.rs)
        let print = 1 << 2;
        let modify = 1 << 3;
        let copy = 1 << 4;
        let perms = print | modify | copy;
        assert_eq!(perms, 0x1C);
    }

    #[test]
    fn test_extract_permissions_empty() {
        let envelope = Pkcs7Envelope {
            version: 0,
            recipient_infos: vec![],
            content_encryption_oid: vec![],
            encrypted_content: None,
        };
        let perms = extract_permissions(&envelope);
        assert!(perms.is_empty());
    }

    #[test]
    fn test_extract_permissions_returns_none() {
        let envelope = Pkcs7Envelope {
            version: 0,
            recipient_infos: vec![RecipientInfo {
                issuer_serial: None,
                subject_key_id: None,
                encrypted_key: vec![0x01, 0x02, 0x03],
            }],
            content_encryption_oid: vec![],
            encrypted_content: None,
        };
        let perms = extract_permissions(&envelope);
        assert_eq!(perms.len(), 1);
        assert_eq!(perms[0], None);
    }
}
