//! C FFI for cpdf encryption operations.

use super::document::CPDF_DOCS;
use super::{clear_error, set_error};
use crate::cpdf::encryption::{self, EncryptionMethod};
use crate::ffi::Handle;
use std::ffi::{CStr, c_char, c_int};

macro_rules! get_doc_clone {
    ($handle:expr) => {
        match CPDF_DOCS.get($handle) {
            Some(arc) => arc.lock().unwrap().clone(),
            None => {
                set_error(1, &format!("invalid document handle: {}", $handle));
                return 0;
            }
        }
    };
}

macro_rules! get_doc_ref {
    ($handle:expr) => {
        match CPDF_DOCS.get($handle) {
            Some(arc) => arc,
            None => {
                set_error(1, &format!("invalid document handle: {}", $handle));
                return 0;
            }
        }
    };
}

macro_rules! ok_or_error {
    ($result:expr) => {
        match $result {
            Ok(doc) => CPDF_DOCS.insert(doc),
            Err(e) => {
                set_error(1, &e.to_string());
                0
            }
        }
    };
}

// ── Query ───────────────────────────────────────────────────────────

/// Returns 1 if the document is encrypted, 0 otherwise.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_encrypted(doc: Handle) -> c_int {
    clear_error();
    let d = get_doc_ref!(doc);
    let guard = d.lock().unwrap();
    if encryption::is_encrypted(&guard) {
        1
    } else {
        0
    }
}

/// Check whether `perm_bit` is granted. Returns 1=yes, 0=no.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_hasPermission(doc: Handle, password: *const c_char, perm_bit: u32) -> c_int {
    clear_error();
    let d = get_doc_ref!(doc);
    let guard = d.lock().unwrap();
    let pw = if password.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(password) }.to_str().unwrap_or("")
    };
    match encryption::has_permission(&guard, pw, perm_bit) {
        Ok(true) => 1,
        Ok(false) => 0,
        Err(e) => {
            set_error(1, &e.to_string());
            0
        }
    }
}

// ── Encrypt ─────────────────────────────────────────────────────────

/// Encrypt a document. Returns a new document handle, 0 on error.
///
/// `method_code`: 0=RC4-40, 1=RC4-128, 2=AES-128, 3=AES-256
/// `perms_bits`: OR of PDF permission flag bits.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_encrypt(
    doc: Handle,
    method_code: c_int,
    user_pw: *const c_char,
    owner_pw: *const c_char,
    perms_bits: u32,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);

    let method = match EncryptionMethod::from_code(method_code) {
        Ok(m) => m,
        Err(e) => {
            set_error(1, &e.to_string());
            return 0;
        }
    };

    let upw = if user_pw.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(user_pw) }.to_str().unwrap_or("")
    };
    let opw = if owner_pw.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(owner_pw) }.to_str().unwrap_or("")
    };

    ok_or_error!(encryption::encrypt(d, method, upw, opw, perms_bits))
}

// ── Decrypt ─────────────────────────────────────────────────────────

/// Decrypt a document. Returns a new document handle, 0 on error.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_decrypt(doc: Handle, password: *const c_char) -> Handle {
    clear_error();
    let d = get_doc_ref!(doc);
    let guard = d.lock().unwrap();

    let pw = if password.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(password) }.to_str().unwrap_or("")
    };

    ok_or_error!(encryption::decrypt(&guard, pw))
}

/// Decrypt only if encrypted; otherwise returns a clone handle.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_decryptIfNeeded(doc: Handle, password: *const c_char) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);

    let pw = if password.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(password) }.to_str().unwrap_or("")
    };

    ok_or_error!(encryption::decrypt_if_needed(d, pw))
}
