//! macOS platform implementation for fontlift
//!
//! This module provides macOS-specific font management using Core Text APIs,
//! implementing the same functionality as the Swift CLI but in Rust.

use fontlift_core::{
    journal::{self, JournalAction},
    protection, validation,
    validation_ext::{self, ValidatorConfig},
    FontError, FontManager, FontResult, FontScope, FontliftFontFaceInfo, FontliftFontSource,
};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use objc2_core_foundation::{
    CFDictionary, CFError, CFIndex, CFNumber, CFRetained, CFString, CFType, CFURLPathStyle, CFURL,
};
use objc2_core_text::{
    kCTFontDisplayNameAttribute, kCTFontFamilyNameAttribute, kCTFontFormatAttribute,
    kCTFontNameAttribute, kCTFontStyleNameAttribute, kCTFontSymbolicTrait, kCTFontTraitsAttribute,
    kCTFontURLAttribute, kCTFontWeightTrait, CTFontDescriptor, CTFontFormat,
    CTFontManagerRegisterFontsForURL, CTFontManagerScope, CTFontManagerUnregisterFontsForURL,
};

const K_CT_FONT_MANAGER_ERROR_ALREADY_REGISTERED: isize = 105;
const K_CT_FONT_MANAGER_ERROR_DUPLICATED_NAME: isize = 305;

fn test_cache_root() -> Option<PathBuf> {
    env::var_os("FONTLIFT_TEST_CACHE_ROOT").map(PathBuf::from)
}

fn user_home(test_root: &Option<PathBuf>) -> FontResult<PathBuf> {
    if let Some(root) = test_root.clone() {
        return Ok(root);
    }

    env::var("HOME").map(PathBuf::from).map_err(|_| {
        FontError::UnsupportedOperation(
            "Unable to resolve HOME directory for cache cleanup".to_string(),
        )
    })
}

fn fake_registry_root() -> Option<PathBuf> {
    env::var_os("FONTLIFT_FAKE_REGISTRY_ROOT").map(PathBuf::from)
}

#[allow(dead_code)]
fn fake_registry_target(root: &Path, scope: FontScope, source: &Path) -> FontResult<PathBuf> {
    let file_name = source.file_name().ok_or_else(|| {
        FontError::InvalidFormat("Font path must include a file name".to_string())
    })?;

    let base = match scope {
        FontScope::User => root.join("Library/Fonts"),
        FontScope::System => root.join("System/Library/Fonts"),
    };

    Ok(base.join(file_name))
}

fn delete_matching_files(root: &Path, predicate: impl Fn(&Path) -> bool) -> FontResult<usize> {
    if !root.exists() {
        return Ok(0);
    }

    let mut removed = 0usize;
    let mut stack = vec![root.to_path_buf()];

    while let Some(dir) = stack.pop() {
        let entries = match fs::read_dir(&dir) {
            Ok(entries) => entries,
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue,
            Err(err) => return Err(FontError::IoError(err)),
        };

        for entry in entries {
            let entry = entry.map_err(FontError::IoError)?;
            let path = entry.path();

            if path.is_dir() {
                stack.push(path);
                continue;
            }

            if predicate(&path) {
                match fs::remove_file(&path) {
                    Ok(_) => removed += 1,
                    Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
                    Err(err) => return Err(FontError::IoError(err)),
                }
            }
        }
    }

    Ok(removed)
}

fn purge_directory_contents(root: &Path) -> FontResult<usize> {
    if !root.exists() {
        return Ok(0);
    }

    let mut removed = 0usize;
    let entries = fs::read_dir(root).map_err(FontError::IoError)?;

    for entry in entries {
        let entry = entry.map_err(FontError::IoError)?;
        let path = entry.path();

        if path.is_dir() {
            removed += purge_directory_contents(&path)?;
            match fs::remove_dir(&path) {
                Ok(_) => {}
                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
                Err(err) => return Err(FontError::IoError(err)),
            }
        } else {
            fs::remove_file(&path).map_err(FontError::IoError)?;
            removed += 1;
        }
    }

    Ok(removed)
}

fn clear_adobe_font_caches(home: &Path) -> FontResult<usize> {
    // Adobe Font cache manifests (AdobeFnt*.lst) live under TypeSupport; remove them recursively
    let type_support = home.join("Library/Application Support/Adobe/TypeSupport");
    let removed_lists = delete_matching_files(&type_support, |path| {
        path.file_name()
            .and_then(|n| n.to_str())
            .map(|name| name.starts_with("AdobeFnt") && name.ends_with(".lst"))
            .unwrap_or(false)
    })?;

    // Adobe font cache files under Caches/Adobe/Fonts
    let fonts_cache = home.join("Library/Caches/Adobe/Fonts");
    let removed_cache = purge_directory_contents(&fonts_cache)?;

    Ok(removed_lists + removed_cache)
}

fn clear_office_font_cache(home: &Path) -> FontResult<usize> {
    // Microsoft Office font cache storage used by Office apps
    let office_cache = home.join("Library/Group Containers/UBF8T346G9.Office/FontCache");
    purge_directory_contents(&office_cache)
}

fn ct_scope(scope: FontScope) -> CTFontManagerScope {
    match scope {
        FontScope::User => CTFontManagerScope::User,
        FontScope::System => CTFontManagerScope::Persistent,
    }
}

fn cf_error_to_string(err: Option<&CFError>) -> String {
    match err {
        None => "unknown CoreText error".to_string(),
        Some(cf_err) => {
            // Get the error description
            match cf_err.description() {
                Some(desc) => cf_string_to_rust(&desc),
                None => "unknown CoreText error".to_string(),
            }
        }
    }
}

fn cf_string_to_rust(cf_str: &CFString) -> String {
    use objc2_core_foundation::CFStringBuiltInEncodings;

    // Get the length of the string
    let len = cf_str.length();
    if len == 0 {
        return String::new();
    }

    // Try to get a direct pointer to the UTF-8 representation
    let ptr = cf_str.c_string_ptr(CFStringBuiltInEncodings::EncodingUTF8.0);
    if !ptr.is_null() {
        // Safety: ptr is valid UTF-8 C string
        let c_str = unsafe { std::ffi::CStr::from_ptr(ptr) };
        return c_str.to_string_lossy().into_owned();
    }

    // Fallback: allocate a buffer and copy the string
    let max_size = (len as usize) * 4 + 1; // UTF-8 can be up to 4 bytes per character
    let mut buffer = vec![0u8; max_size];
    let success = unsafe {
        cf_str.c_string(
            buffer.as_mut_ptr() as *mut i8,
            max_size as CFIndex,
            CFStringBuiltInEncodings::EncodingUTF8.0,
        )
    };

    if success {
        // Find the null terminator
        let null_pos = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
        String::from_utf8_lossy(&buffer[..null_pos]).into_owned()
    } else {
        String::new()
    }
}

fn rust_string_to_cf(s: &str) -> CFRetained<CFString> {
    use objc2_core_foundation::CFStringBuiltInEncodings;

    let c_str = std::ffi::CString::new(s).unwrap_or_default();
    unsafe {
        CFString::with_c_string(
            None,
            c_str.as_ptr(),
            CFStringBuiltInEncodings::EncodingUTF8.0,
        )
        .expect("Failed to create CFString")
    }
}

fn path_to_cfurl(path: &Path) -> Option<CFRetained<CFURL>> {
    let path_str = path.to_str()?;
    let cf_path = rust_string_to_cf(path_str);
    CFURL::with_file_system_path(
        None,
        Some(&cf_path),
        CFURLPathStyle::CFURLPOSIXPathStyle,
        path.is_dir(),
    )
}

fn cfurl_to_path(url: &CFURL) -> Option<PathBuf> {
    let cf_str = url.file_system_path(CFURLPathStyle::CFURLPOSIXPathStyle);
    cf_str.map(|s| PathBuf::from(cf_string_to_rust(&s)))
}

fn scope_from_path(path: &Path) -> FontScope {
    if let Some(fake_root) = fake_registry_root() {
        let user_fonts = fake_root.join("Library/Fonts");
        let system_fonts = fake_root.join("System/Library/Fonts");

        if path.starts_with(&user_fonts) {
            return FontScope::User;
        }

        if path.starts_with(&system_fonts) {
            return FontScope::System;
        }
    }

    if let Ok(home) = std::env::var("HOME") {
        let user_fonts = PathBuf::from(home).join("Library/Fonts");
        if path.starts_with(&user_fonts) {
            return FontScope::User;
        }
    }

    if path.starts_with("/System/Library/Fonts") || path.starts_with("/Library/Fonts") {
        FontScope::System
    } else {
        // Default to user to avoid over-reporting system scope for custom paths
        FontScope::User
    }
}

fn normalize_path(path: &Path) -> String {
    let mut normalized = path.to_string_lossy().replace('\\', "/").to_lowercase();

    while normalized.contains("//") {
        normalized = normalized.replace("//", "/");
    }

    normalized
}

fn font_format_to_string(format: CTFontFormat) -> Option<String> {
    if format == CTFontFormat::OpenTypePostScript {
        Some("OpenTypePostScript".to_string())
    } else if format == CTFontFormat::OpenTypeTrueType {
        Some("OpenTypeTrueType".to_string())
    } else if format == CTFontFormat::TrueType {
        Some("TrueType".to_string())
    } else if format == CTFontFormat::PostScript {
        Some("PostScript".to_string())
    } else if format == CTFontFormat::Bitmap {
        Some("Bitmap".to_string())
    } else {
        None
    }
}

fn is_conflict_error(err: &CFError) -> bool {
    let domain = match err.domain() {
        Some(d) => d,
        None => return false,
    };
    let domain_str = cf_string_to_rust(&domain);
    if !domain_str.contains("CTFontManagerErrorDomain") {
        return false;
    }

    let code = err.code();
    matches!(
        code,
        K_CT_FONT_MANAGER_ERROR_ALREADY_REGISTERED | K_CT_FONT_MANAGER_ERROR_DUPLICATED_NAME
    )
}

// Helper to check CF type - use ConcreteType trait
use objc2_core_foundation::ConcreteType;

fn get_descriptor_string_attribute(
    descriptor: &CTFontDescriptor,
    attr: &CFString,
) -> Option<String> {
    let value = unsafe { descriptor.attribute(attr) };
    value.and_then(|v| {
        // Check if it's a CFString using ConcreteType trait
        let type_id = objc2_core_foundation::CFGetTypeID(Some(v.as_ref()));
        let string_type_id = CFString::type_id();
        if type_id == string_type_id {
            // Cast to CFString - the value is a CFType, we need to reinterpret
            let cf_str: &CFString = unsafe { &*(v.as_ref() as *const CFType as *const CFString) };
            Some(cf_string_to_rust(cf_str))
        } else {
            None
        }
    })
}

fn get_descriptor_url_attribute(descriptor: &CTFontDescriptor) -> Option<PathBuf> {
    let value = unsafe { descriptor.attribute(kCTFontURLAttribute) };
    value.and_then(|v| {
        let type_id = objc2_core_foundation::CFGetTypeID(Some(v.as_ref()));
        let url_type_id = CFURL::type_id();
        if type_id == url_type_id {
            let cf_url: &CFURL = unsafe { &*(v.as_ref() as *const CFType as *const CFURL) };
            cfurl_to_path(cf_url)
        } else {
            None
        }
    })
}

fn get_descriptor_format_attribute(descriptor: &CTFontDescriptor) -> Option<CTFontFormat> {
    let value = unsafe { descriptor.attribute(kCTFontFormatAttribute) };
    value.and_then(|v| {
        let type_id = objc2_core_foundation::CFGetTypeID(Some(v.as_ref()));
        let number_type_id = CFNumber::type_id();
        if type_id == number_type_id {
            let cf_num: &CFNumber = unsafe { &*(v.as_ref() as *const CFType as *const CFNumber) };
            let mut format_value: u32 = 0;
            let success = unsafe {
                cf_num.value(
                    objc2_core_foundation::CFNumberType::SInt32Type,
                    (&mut format_value) as *mut u32 as *mut std::ffi::c_void,
                )
            };
            if success {
                Some(CTFontFormat(format_value))
            } else {
                None
            }
        } else {
            None
        }
    })
}

fn descriptor_to_font_face_info(descriptor: &CTFontDescriptor) -> Option<FontliftFontFaceInfo> {
    let path = get_descriptor_url_attribute(descriptor)?;
    let postscript_name =
        get_descriptor_string_attribute(descriptor, unsafe { kCTFontNameAttribute })?;
    let display_name =
        get_descriptor_string_attribute(descriptor, unsafe { kCTFontDisplayNameAttribute })
            .unwrap_or_else(|| postscript_name.clone());
    let family_name =
        get_descriptor_string_attribute(descriptor, unsafe { kCTFontFamilyNameAttribute })
            .unwrap_or_else(|| postscript_name.clone());
    let style_name =
        get_descriptor_string_attribute(descriptor, unsafe { kCTFontStyleNameAttribute })
            .unwrap_or_else(|| "Regular".to_string());

    let mut source = FontliftFontSource::new(path.clone()).with_scope(Some(scope_from_path(&path)));

    if let Some(format) = get_descriptor_format_attribute(descriptor) {
        source = source.with_format(font_format_to_string(format));
    }

    let mut info = FontliftFontFaceInfo::new(
        source,
        postscript_name,
        display_name,
        family_name,
        style_name,
    );

    // Try to get traits
    let traits_value = unsafe { descriptor.attribute(kCTFontTraitsAttribute) };
    if let Some(traits_cf) = traits_value {
        let type_id = objc2_core_foundation::CFGetTypeID(Some(traits_cf.as_ref()));
        let dict_type_id = CFDictionary::type_id();
        if type_id == dict_type_id {
            let traits_dict: &CFDictionary =
                unsafe { &*(traits_cf.as_ref() as *const CFType as *const CFDictionary) };

            // Get symbolic traits (for italic)
            let symbolic_key = unsafe { kCTFontSymbolicTrait };
            let symbolic_value =
                unsafe { traits_dict.value(symbolic_key as *const _ as *const std::ffi::c_void) };
            if !symbolic_value.is_null() {
                let cf_num: &CFNumber = unsafe { &*(symbolic_value as *const CFNumber) };
                let mut symbolic: u32 = 0;
                let success = unsafe {
                    cf_num.value(
                        objc2_core_foundation::CFNumberType::SInt32Type,
                        (&mut symbolic) as *mut u32 as *mut std::ffi::c_void,
                    )
                };
                if success {
                    // kCTFontItalicTrait = 1 << 0
                    info.italic = Some((symbolic & 1) != 0);
                }
            }

            // Get weight trait
            let weight_key = unsafe { kCTFontWeightTrait };
            let weight_value =
                unsafe { traits_dict.value(weight_key as *const _ as *const std::ffi::c_void) };
            if !weight_value.is_null() {
                let cf_num: &CFNumber = unsafe { &*(weight_value as *const CFNumber) };
                let mut weight: f64 = 0.0;
                let success = unsafe {
                    cf_num.value(
                        objc2_core_foundation::CFNumberType::Float64Type,
                        (&mut weight) as *mut f64 as *mut std::ffi::c_void,
                    )
                };
                if success {
                    let weight_int = (weight * 400.0 + 500.0).round();
                    if weight_int.is_finite() {
                        let clamped = weight_int.clamp(1.0, 1000.0) as u16;
                        info.weight = Some(clamped);
                    }
                }
            }
        }
    }

    Some(info)
}

/// macOS font manager using Core Text APIs
pub struct MacFontManager {
    fake_root: Option<PathBuf>,
    /// Optional validation config for pre-install validation
    validation_config: Option<ValidatorConfig>,
}

impl MacFontManager {
    /// Create a new macOS font manager
    pub fn new() -> Self {
        let fake_root = std::env::var_os("FONTLIFT_FAKE_REGISTRY_ROOT").map(PathBuf::from);
        Self {
            fake_root,
            validation_config: None,
        }
    }

    /// Create a manager with validation enabled
    pub fn with_validation(config: ValidatorConfig) -> Self {
        let fake_root = std::env::var_os("FONTLIFT_FAKE_REGISTRY_ROOT").map(PathBuf::from);
        Self {
            fake_root,
            validation_config: Some(config),
        }
    }

    /// Enable validation on this manager
    pub fn set_validation_config(&mut self, config: Option<ValidatorConfig>) {
        self.validation_config = config;
    }

    /// Whether the manager should avoid CoreText and operate against a fake registry root
    pub fn is_fake_registry_enabled(&self) -> bool {
        self.fake_root.is_some()
    }

    fn target_directory(&self, scope: FontScope) -> FontResult<PathBuf> {
        if let Some(root) = &self.fake_root {
            let dir = match scope {
                FontScope::User => root.join("Library/Fonts"),
                FontScope::System => root.join("System/Library/Fonts"),
            };
            return Ok(dir);
        }

        let target_dir = match scope {
            FontScope::User => {
                let home_dir = std::env::var("HOME").map_err(|_| {
                    FontError::PermissionDenied("Cannot determine home directory".to_string())
                })?;
                PathBuf::from(home_dir).join("Library/Fonts")
            }
            FontScope::System => PathBuf::from("/Library/Fonts"),
        };

        Ok(target_dir)
    }

    fn installed_target_path(
        &self,
        source: &FontliftFontSource,
        scope: FontScope,
    ) -> FontResult<PathBuf> {
        let file_name = source.path.file_name().ok_or_else(|| {
            FontError::InvalidFormat("Font path must include a file name".to_string())
        })?;

        Ok(self.target_directory(scope)?.join(file_name))
    }

    /// Extract font information using basic filename parsing as fallback
    fn get_font_info_from_path(&self, path: &Path) -> FontResult<FontliftFontFaceInfo> {
        validation::validate_font_file(path)?;

        let mut info = validation::extract_basic_info_from_path(path);
        info.source.scope = Some(scope_from_path(path));
        Ok(info)
    }

    /// Check if path is in system font directory
    fn is_system_font_path(&self, path: &Path) -> bool {
        protection::is_protected_system_font_path(path)
    }

    /// Check if current user has admin privileges
    fn has_admin_privileges(&self) -> bool {
        unsafe { libc::geteuid() == 0 }
    }

    /// Copy font to target directory based on scope
    fn copy_font_to_target_directory(
        &self,
        source_path: &Path,
        scope: FontScope,
        replace_existing: bool,
    ) -> FontResult<PathBuf> {
        let target_dir = self.target_directory(scope)?;
        let file_name = source_path.file_name().ok_or_else(|| {
            FontError::InvalidFormat("Font path must include a file name".to_string())
        })?;

        if scope == FontScope::System
            && !self.is_fake_registry_enabled()
            && !self.has_admin_privileges()
        {
            return Err(FontError::PermissionDenied(
                "System-level font installation requires administrator privileges. Run with --admin or use sudo.".to_string(),
            ));
        }

        // Create target directory if it doesn't exist
        if !target_dir.exists() {
            fs::create_dir_all(&target_dir).map_err(FontError::IoError)?;
        }

        // Check if font already exists in target location
        let target_path = target_dir.join(file_name);
        if target_path.exists() {
            if replace_existing {
                fs::remove_file(&target_path).map_err(FontError::IoError)?;
            } else {
                return Err(FontError::AlreadyInstalled(target_path));
            }
        }

        // Copy font file
        fs::copy(source_path, &target_path).map_err(FontError::IoError)?;

        Ok(target_path)
    }

    fn install_font_core_text(&self, path: &Path, scope: FontScope) -> FontResult<()> {
        // Validate the font prior to registration
        validation::validate_font_file(path)?;

        // Convert path to CFURL for Core Text
        let cf_url = match path_to_cfurl(path) {
            Some(url) => url,
            None => {
                return Err(FontError::InvalidFormat(format!(
                    "Cannot create CFURL from path: {}",
                    path.display()
                )))
            }
        };

        let mut error: *mut CFError = std::ptr::null_mut();
        let result =
            unsafe { CTFontManagerRegisterFontsForURL(&cf_url, ct_scope(scope), &mut error) };

        if result {
            return Ok(());
        }

        if error.is_null() {
            return Err(FontError::RegistrationFailed(format!(
                "Core Text failed to register font {}",
                path.display()
            )));
        }

        let error_ref = unsafe { &*error };
        if is_conflict_error(error_ref) {
            let mut unregister_error: *mut CFError = std::ptr::null_mut();
            let unregistered = unsafe {
                CTFontManagerUnregisterFontsForURL(&cf_url, ct_scope(scope), &mut unregister_error)
            };

            if !unregistered {
                let unregister_err = if unregister_error.is_null() {
                    None
                } else {
                    Some(unsafe { &*unregister_error })
                };
                return Err(FontError::RegistrationFailed(format!(
                    "Existing font conflict could not be resolved for {}: {}",
                    path.display(),
                    cf_error_to_string(unregister_err)
                )));
            }

            let mut retry_error: *mut CFError = std::ptr::null_mut();
            let retry = unsafe {
                CTFontManagerRegisterFontsForURL(&cf_url, ct_scope(scope), &mut retry_error)
            };

            if retry {
                return Ok(());
            }

            let retry_err = if retry_error.is_null() {
                None
            } else {
                Some(unsafe { &*retry_error })
            };
            return Err(FontError::RegistrationFailed(format!(
                "Core Text failed to register font {} after resolving conflict: {}",
                path.display(),
                cf_error_to_string(retry_err)
            )));
        }

        Err(FontError::RegistrationFailed(format!(
            "Core Text failed to register font {}: {}",
            path.display(),
            cf_error_to_string(Some(error_ref))
        )))
    }

    fn install_font_fake(&self, source: &FontliftFontSource, scope: FontScope) -> FontResult<()> {
        let path = &source.path;
        self.copy_font_to_target_directory(path, scope, true)?;
        Ok(())
    }

    fn uninstall_font_fake(&self, source: &FontliftFontSource, scope: FontScope) -> FontResult<()> {
        let target_path = self.installed_target_path(source, scope)?;
        if target_path.exists() {
            std::fs::remove_file(&target_path).map_err(FontError::IoError)?;
            Ok(())
        } else {
            Err(FontError::FontNotFound(target_path))
        }
    }

    #[allow(dead_code)]
    fn list_installed_fonts_fake(&self) -> FontResult<Vec<FontliftFontFaceInfo>> {
        let mut fonts = Vec::new();

        for scope in [FontScope::User, FontScope::System] {
            let dir = self.target_directory(scope)?;
            if !dir.exists() {
                continue;
            }

            for entry in fs::read_dir(&dir).map_err(FontError::IoError)? {
                let entry = entry.map_err(FontError::IoError)?;
                let path = entry.path();

                if !validation::is_valid_font_extension(&path) {
                    continue;
                }

                match self.get_font_info_from_path(&path) {
                    Ok(font) => fonts.push(font.with_scope(Some(scope))),
                    Err(_) => continue,
                }
            }
        }

        Ok(protection::dedupe_fonts(fonts))
    }

    /// Validate system operation permissions
    fn validate_system_operation(&self, scope: FontScope) -> FontResult<()> {
        if scope == FontScope::System
            && !self.is_fake_registry_enabled()
            && !self.has_admin_privileges()
        {
            return Err(FontError::PermissionDenied(
                "System-level font operations require administrator privileges. Run with --admin or use sudo.".to_string()
            ));
        }
        Ok(())
    }
}

impl Default for MacFontManager {
    fn default() -> Self {
        Self::new()
    }
}

impl FontManager for MacFontManager {
    fn install_font(&self, source: &FontliftFontSource) -> FontResult<()> {
        let scope = source.scope.unwrap_or(FontScope::User);
        let path = &source.path;
        // Validate inputs
        validation::validate_font_file(path)?;
        self.validate_system_operation(scope)?;

        // Out-of-process validation if configured
        if let Some(ref config) = self.validation_config {
            validation_ext::validate_single(path, config)?;
        }

        if self.is_system_font_path(path) && !self.is_fake_registry_enabled() {
            return Err(FontError::SystemFontProtection(path.to_path_buf()));
        }

        let target_path = self.installed_target_path(source, scope)?;
        let replace_existing = self.is_fake_registry_enabled() || scope == FontScope::User;

        if self.is_fake_registry_enabled() {
            return self.install_font_fake(source, scope);
        }

        // Build journal actions
        let needs_copy = target_path != *path;
        let mut actions = Vec::new();
        if needs_copy {
            actions.push(JournalAction::CopyFile {
                from: path.clone(),
                to: target_path.clone(),
            });
        }
        actions.push(JournalAction::RegisterFont {
            path: target_path.clone(),
            scope,
        });

        // Record operation in journal
        let mut journal = journal::load_journal().unwrap_or_default();
        let entry_id =
            journal.record_operation(actions, Some(format!("Install {}", path.display())));
        journal::save_journal(&journal)?;

        // Step 0: Copy file (if needed)
        let (target_path, created_copy) = if needs_copy {
            let result = self.copy_font_to_target_directory(path, scope, replace_existing);
            match result {
                Ok(copied_path) => {
                    // Mark step 0 complete
                    let mut j = journal::load_journal().unwrap_or_default();
                    let _ = j.mark_step(entry_id, 1);
                    let _ = journal::save_journal(&j);
                    (copied_path, true)
                }
                Err(e) => {
                    // Cleanup journal entry on failure
                    let mut j = journal::load_journal().unwrap_or_default();
                    let _ = j.mark_completed(entry_id);
                    let _ = journal::save_journal(&j);
                    return Err(e);
                }
            }
        } else {
            (target_path, false)
        };

        // Step 1 (or 0 if no copy): Register font
        let result = self.install_font_core_text(&target_path, scope);

        // Update journal
        let mut j = journal::load_journal().unwrap_or_default();
        if result.is_ok() {
            let _ = j.mark_completed(entry_id);
        } else {
            // Rollback: delete copied file on registration failure
            if created_copy {
                let _ = fs::remove_file(&target_path);
            }
            // Mark as completed (failed, but nothing to recover)
            let _ = j.mark_completed(entry_id);
        }
        let _ = journal::save_journal(&j);

        result
    }

    fn uninstall_font(&self, source: &FontliftFontSource) -> FontResult<()> {
        let scope = source.scope.unwrap_or(FontScope::User);
        self.validate_system_operation(scope)?;

        let target_path = self.installed_target_path(source, scope)?;

        if self.is_fake_registry_enabled() {
            return self.uninstall_font_fake(source, scope);
        }

        if !target_path.exists() {
            return Err(FontError::FontNotFound(target_path));
        }

        // Convert path to CFURL for Core Text
        let cf_url = match path_to_cfurl(&target_path) {
            Some(url) => url,
            None => {
                return Err(FontError::InvalidFormat(format!(
                    "Cannot create CFURL from path: {}",
                    target_path.display()
                )))
            }
        };

        let mut error: *mut CFError = std::ptr::null_mut();
        let result =
            unsafe { CTFontManagerUnregisterFontsForURL(&cf_url, ct_scope(scope), &mut error) };

        if result {
            Ok(())
        } else {
            let err = if error.is_null() {
                None
            } else {
                Some(unsafe { &*error })
            };
            let message = cf_error_to_string(err);
            Err(FontError::RegistrationFailed(format!(
                "Core Text failed to unregister font {}: {}",
                target_path.display(),
                message
            )))
        }
    }

    fn remove_font(&self, source: &FontliftFontSource) -> FontResult<()> {
        let scope = source.scope.unwrap_or(FontScope::User);
        let target_path = self.installed_target_path(source, scope)?;
        let installed_source = FontliftFontSource::new(target_path.clone()).with_scope(Some(scope));

        if self.is_system_font_path(&target_path) && !self.is_fake_registry_enabled() {
            return Err(FontError::SystemFontProtection(target_path));
        }

        // Skip journaling for fake registry
        if self.is_fake_registry_enabled() {
            self.uninstall_font(&installed_source)?;
            if target_path.exists() {
                std::fs::remove_file(&target_path).map_err(FontError::IoError)?;
            }
            return Ok(());
        }

        // Build journal actions: UnregisterFont → DeleteFile
        let actions = vec![
            JournalAction::UnregisterFont {
                path: target_path.clone(),
                scope,
            },
            JournalAction::DeleteFile {
                path: target_path.clone(),
            },
        ];

        // Record operation in journal
        let mut journal = journal::load_journal().unwrap_or_default();
        let entry_id =
            journal.record_operation(actions, Some(format!("Remove {}", target_path.display())));
        journal::save_journal(&journal)?;

        // Step 0: Unregister font
        let unregister_result = self.uninstall_font(&installed_source);
        if let Err(e) = unregister_result {
            // Mark completed (nothing to recover from unregister failure)
            let mut j = journal::load_journal().unwrap_or_default();
            let _ = j.mark_completed(entry_id);
            let _ = journal::save_journal(&j);
            return Err(e);
        }

        // Mark step 0 complete
        {
            let mut j = journal::load_journal().unwrap_or_default();
            let _ = j.mark_step(entry_id, 1);
            let _ = journal::save_journal(&j);
        }

        // Step 1: Delete file
        if target_path.exists() {
            std::fs::remove_file(&target_path).map_err(FontError::IoError)?;
        }

        // Mark operation completed
        let mut j = journal::load_journal().unwrap_or_default();
        let _ = j.mark_completed(entry_id);
        let _ = journal::save_journal(&j);

        Ok(())
    }

    fn is_font_installed(&self, source: &FontliftFontSource) -> FontResult<bool> {
        let scope = source.scope.unwrap_or(FontScope::User);
        let target_path = self.installed_target_path(source, scope)?;

        if self.is_fake_registry_enabled() {
            return Ok(target_path.exists());
        }

        if target_path.exists() {
            return Ok(true);
        }

        let font_array = unsafe { objc2_core_text::CTFontManagerCopyAvailableFontURLs() };

        let normalized_target = normalize_path(&target_path);
        let count = font_array.count();

        for i in 0..count {
            let value = unsafe { font_array.value_at_index(i) };
            if value.is_null() {
                continue;
            }

            // Check if it's a CFURL
            let cf_type: &CFType = unsafe { &*(value as *const CFType) };
            let type_id = objc2_core_foundation::CFGetTypeID(Some(cf_type));
            let url_type_id = CFURL::type_id();
            if type_id != url_type_id {
                continue;
            }

            let cf_url: &CFURL = unsafe { &*(value as *const CFURL) };
            if let Some(path) = cfurl_to_path(cf_url) {
                if normalize_path(&path) == normalized_target {
                    return Ok(true);
                }
            }
        }

        Ok(false)
    }

    fn list_installed_fonts(&self) -> FontResult<Vec<FontliftFontFaceInfo>> {
        if self.is_fake_registry_enabled() {
            return self.list_installed_fonts_fake();
        }

        // Get all available font URLs from Core Text
        let font_array = unsafe { objc2_core_text::CTFontManagerCopyAvailableFontURLs() };

        let mut fonts = Vec::new();
        let count = font_array.count();

        for i in 0..count {
            let value = unsafe { font_array.value_at_index(i) };
            if value.is_null() {
                continue;
            }

            // Check if it's a CFURL
            let cf_type: &CFType = unsafe { &*(value as *const CFType) };
            let type_id = objc2_core_foundation::CFGetTypeID(Some(cf_type));
            let url_type_id = CFURL::type_id();
            if type_id != url_type_id {
                continue;
            }

            let cf_url: &CFURL = unsafe { &*(value as *const CFURL) };

            // Try to pull rich metadata via font descriptors first
            let descriptors =
                unsafe { objc2_core_text::CTFontManagerCreateFontDescriptorsFromURL(cf_url) };

            if let Some(descriptor_array) = descriptors {
                let desc_count = descriptor_array.count();

                for idx in 0..desc_count {
                    let desc_value = unsafe { descriptor_array.value_at_index(idx) };
                    if desc_value.is_null() {
                        continue;
                    }

                    let descriptor: &CTFontDescriptor =
                        unsafe { &*(desc_value as *const CTFontDescriptor) };
                    if let Some(info) = descriptor_to_font_face_info(descriptor) {
                        fonts.push(info);
                        continue;
                    }
                }
            }

            // Fallback: basic info from path
            if let Some(path) = cfurl_to_path(cf_url) {
                if !path.exists() || !validation::is_valid_font_extension(&path) {
                    continue;
                }

                match self.get_font_info_from_path(&path) {
                    Ok(mut font_info) => {
                        font_info.source.scope = Some(scope_from_path(&path));
                        fonts.push(font_info);
                    }
                    Err(_) => {
                        // Skip fonts we can't read, but don't fail the entire operation
                        continue;
                    }
                }
            }
        }

        Ok(protection::dedupe_fonts(fonts))
    }

    fn prune_missing_fonts(&self, scope: FontScope) -> FontResult<usize> {
        if self.is_fake_registry_enabled() {
            return Ok(0);
        }

        let font_array = unsafe { objc2_core_text::CTFontManagerCopyAvailableFontURLs() };

        let mut pruned = 0usize;
        let mut failures = Vec::new();
        let count = font_array.count();

        for i in 0..count {
            let value = unsafe { font_array.value_at_index(i) };
            if value.is_null() {
                continue;
            }

            let cf_type: &CFType = unsafe { &*(value as *const CFType) };
            let type_id = objc2_core_foundation::CFGetTypeID(Some(cf_type));
            let url_type_id = CFURL::type_id();
            if type_id != url_type_id {
                continue;
            }

            let cf_url: &CFURL = unsafe { &*(value as *const CFURL) };
            let path = cfurl_to_path(cf_url);

            if let Some(ref existing_path) = path {
                if scope_from_path(existing_path) != scope {
                    continue;
                }

                // Skip registrations that still have a backing file
                if existing_path.exists() {
                    continue;
                }
            } else if scope == FontScope::System && !self.has_admin_privileges() {
                // Don't attempt system pruning without privileges
                continue;
            }

            let mut error: *mut CFError = std::ptr::null_mut();
            let ok =
                unsafe { CTFontManagerUnregisterFontsForURL(cf_url, ct_scope(scope), &mut error) };

            if ok {
                pruned += 1;
            } else {
                let err = if error.is_null() {
                    None
                } else {
                    Some(unsafe { &*error })
                };
                failures.push(cf_error_to_string(err));
            }
        }

        if failures.is_empty() {
            Ok(pruned)
        } else {
            Err(FontError::RegistrationFailed(format!(
                "Failed to prune some font registrations: {}",
                failures.join("; ")
            )))
        }
    }

    fn clear_font_caches(&self, scope: FontScope) -> FontResult<()> {
        if self.is_fake_registry_enabled() {
            return Ok(());
        }

        let test_root = test_cache_root();
        let home = user_home(&test_root)?;
        let should_touch_system = test_root.is_none();

        match scope {
            FontScope::User => {
                if should_touch_system {
                    // Clear user font cache using atsutil
                    let output = std::process::Command::new("atsutil")
                        .args(["databases", "-removeUser"])
                        .output()
                        .map_err(FontError::IoError)?;

                    if !output.status.success() {
                        let stderr = String::from_utf8_lossy(&output.stderr);
                        return Err(FontError::RegistrationFailed(format!(
                            "Failed to clear user font cache: {}",
                            stderr
                        )));
                    }

                    // Restart ATS server for user session
                    let _ = std::process::Command::new("atsutil")
                        .args(["server", "-shutdown"])
                        .output();

                    let _ = std::process::Command::new("atsutil")
                        .args(["server", "-ping"])
                        .output();
                }

                // Vendor caches (Adobe/Microsoft) are per-user; remove safely under the resolved home dir
                clear_adobe_font_caches(&home)?;
                clear_office_font_cache(&home)?;
            }
            FontScope::System => {
                if should_touch_system {
                    // System cache clearing requires admin privileges
                    if !self.has_admin_privileges() {
                        return Err(FontError::PermissionDenied(
                            "System cache clearing requires administrator privileges".to_string(),
                        ));
                    }

                    // Clear system font cache using atsutil
                    let output = std::process::Command::new("atsutil")
                        .args(["databases", "-remove"])
                        .output()
                        .map_err(FontError::IoError)?;

                    if !output.status.success() {
                        let stderr = String::from_utf8_lossy(&output.stderr);
                        return Err(FontError::RegistrationFailed(format!(
                            "Failed to clear system font cache: {}",
                            stderr
                        )));
                    }

                    // Restart ATS server for system
                    let _ = std::process::Command::new("atsutil")
                        .args(["server", "-shutdown"])
                        .output();

                    let _ = std::process::Command::new("atsutil")
                        .args(["server", "-ping"])
                        .output();
                }
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::path::PathBuf;
    use std::sync::{Mutex, OnceLock};

    fn fake_env_lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    #[test]
    fn test_mac_font_manager_creation() {
        std::env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
        let manager = MacFontManager::new();
        assert!(!manager.is_fake_registry_enabled());
    }

    #[test]
    fn test_system_font_detection() {
        let manager = MacFontManager::new();

        // Test system font paths
        let system_path = PathBuf::from("/System/Library/Fonts/Arial.ttf");
        assert!(manager.is_system_font_path(&system_path));

        let library_path = PathBuf::from("/Library/Fonts/Helvetica.ttc");
        assert!(manager.is_system_font_path(&library_path));

        // Test user font paths
        let user_path = PathBuf::from("/Users/test/Library/Fonts/Custom.otf");
        assert!(!manager.is_system_font_path(&user_path));

        let temp_path = PathBuf::from("/tmp/test.ttf");
        assert!(!manager.is_system_font_path(&temp_path));
    }

    #[test]
    fn test_admin_detection() {
        let manager = MacFontManager::new();
        // This test will typically fail unless run as root
        let is_admin = manager.has_admin_privileges();
        // We can't assert a specific value as it depends on execution context
        println!("Running as admin: {}", is_admin);
    }

    #[test]
    fn test_font_validation() {
        let _manager = MacFontManager::new();

        // Test valid font file paths (these may not exist, just testing validation logic)
        let valid_path = PathBuf::from("/tmp/test.ttf");
        if validation::is_valid_font_extension(&valid_path) {
            // Test would pass if file existed
        }

        let invalid_path = PathBuf::from("/tmp/test.txt");
        assert!(!validation::is_valid_font_extension(&invalid_path));
    }

    #[test]
    fn scope_detection_maps_user_and_system_paths() {
        let user_path = PathBuf::from("/Users/demo/Library/Fonts/Custom.otf");
        let system_path = PathBuf::from("/Library/Fonts/SystemFont.ttf");
        let other_path = PathBuf::from("/tmp/random-font.ttf");

        assert_eq!(scope_from_path(&user_path), FontScope::User);
        assert_eq!(scope_from_path(&system_path), FontScope::System);
        assert_eq!(scope_from_path(&other_path), FontScope::User);
    }

    #[test]
    fn fake_registry_install_list_uninstall_round_trip() {
        let _env_lock = fake_env_lock().lock().expect("env lock");
        let temp = tempfile::tempdir().expect("tempdir");
        let fake_root = temp.path().join("fake-root");
        std::env::set_var("FONTLIFT_FAKE_REGISTRY_ROOT", &fake_root);

        let manager = MacFontManager::new();
        let source_font = temp.path().join("DemoFake.ttf");
        fs::write(&source_font, b"dummy font").expect("write font");

        let source = FontliftFontSource::new(source_font.clone()).with_scope(Some(FontScope::User));

        manager
            .install_font(&source)
            .expect("install in fake registry");

        let installed_path = fake_root.join("Library/Fonts/DemoFake.ttf");
        assert!(
            installed_path.exists(),
            "font should be copied into fake registry"
        );

        let listed = manager.list_installed_fonts().expect("list");
        assert_eq!(listed.len(), 1);
        assert_eq!(listed[0].source.scope, Some(FontScope::User));

        let installed_source =
            FontliftFontSource::new(installed_path.clone()).with_scope(Some(FontScope::User));

        manager
            .uninstall_font(&installed_source)
            .expect("uninstall from fake registry");
        assert!(
            !installed_path.exists(),
            "font file should be removed in fake registry"
        );

        std::env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
    }

    #[test]
    fn fake_registry_allows_system_scope_without_admin() {
        let _env_lock = fake_env_lock().lock().expect("env lock");
        let temp = tempfile::tempdir().expect("tempdir");
        let fake_root = temp.path().join("fake-root");
        std::env::set_var("FONTLIFT_FAKE_REGISTRY_ROOT", &fake_root);

        let manager = MacFontManager::new();
        let source_font = temp.path().join("DemoSystem.ttf");
        fs::write(&source_font, b"dummy font").expect("write font");

        let source =
            FontliftFontSource::new(source_font.clone()).with_scope(Some(FontScope::System));

        manager
            .install_font(&source)
            .expect("system install should bypass admin in fake mode");

        let installed_path = fake_root.join("System/Library/Fonts/DemoSystem.ttf");
        assert!(installed_path.exists());

        std::env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
    }

    #[test]
    fn is_font_installed_tracks_fake_registry_state() {
        struct EnvGuard;
        impl Drop for EnvGuard {
            fn drop(&mut self) {
                std::env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
            }
        }

        let _lock = fake_env_lock().lock().expect("env lock");
        let _guard = EnvGuard;
        let temp = tempfile::tempdir().expect("tempdir");
        let fake_root = temp.path().join("fake-root");
        std::env::set_var("FONTLIFT_FAKE_REGISTRY_ROOT", &fake_root);

        let manager = MacFontManager::new();
        let source_font = temp.path().join("DemoFake.ttf");
        fs::write(&source_font, b"dummy font").expect("write font");

        let source = FontliftFontSource::new(source_font.clone()).with_scope(Some(FontScope::User));

        assert!(
            !manager
                .is_font_installed(&source)
                .expect("check before install"),
            "font should not be marked installed before copying"
        );

        manager
            .install_font(&source)
            .expect("install in fake registry");
        assert!(
            manager
                .is_font_installed(&source)
                .expect("check after install"),
            "font should be marked installed after copy"
        );

        let installed_source =
            FontliftFontSource::new(fake_root.join("Library/Fonts/DemoFake.ttf"))
                .with_scope(Some(FontScope::User));

        manager
            .uninstall_font(&installed_source)
            .expect("uninstall from fake registry");

        assert!(
            !manager
                .is_font_installed(&source)
                .expect("check after uninstall"),
            "font should be absent after uninstall"
        );
    }

    #[test]
    fn reinstall_overwrites_existing_file_in_fake_registry() {
        struct EnvGuard;
        impl Drop for EnvGuard {
            fn drop(&mut self) {
                std::env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
            }
        }

        let _lock = fake_env_lock().lock().expect("env lock");
        let _guard = EnvGuard;
        let temp = tempfile::tempdir().expect("tempdir");
        let fake_root = temp.path().join("fake-root");
        std::env::set_var("FONTLIFT_FAKE_REGISTRY_ROOT", &fake_root);

        let manager = MacFontManager::new();
        let source_font = temp.path().join("DemoFake.ttf");

        fs::write(&source_font, b"version-one").expect("write v1");
        let source = FontliftFontSource::new(source_font.clone()).with_scope(Some(FontScope::User));
        manager.install_font(&source).expect("initial install");

        fs::write(&source_font, b"version-two").expect("write v2");
        manager
            .install_font(&source)
            .expect("reinstall should replace");

        let installed_path = fake_root.join("Library/Fonts/DemoFake.ttf");
        let contents = fs::read(&installed_path).expect("read installed copy");
        assert_eq!(
            contents, b"version-two",
            "reinstall should replace the existing file in fake registry"
        );
    }

    #[test]
    fn clear_font_caches_removes_vendor_caches_under_override_root() {
        use std::env;

        struct EnvGuard;
        impl Drop for EnvGuard {
            fn drop(&mut self) {
                env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");
                env::remove_var("FONTLIFT_TEST_CACHE_ROOT");
            }
        }

        let _lock = fake_env_lock().lock().expect("env lock");
        let _guard = EnvGuard;
        env::remove_var("FONTLIFT_FAKE_REGISTRY_ROOT");

        let tmp = tempfile::tempdir().expect("tempdir");
        let root = tmp.path();

        let adobe_type_support = root.join("Library/Application Support/Adobe/TypeSupport/More");
        fs::create_dir_all(&adobe_type_support).expect("adobe type support dir");
        let adobe_list = adobe_type_support.join("AdobeFnt11.lst");
        fs::write(&adobe_list, b"cache").expect("adobe list");

        let adobe_cache_dir = root.join("Library/Caches/Adobe/Fonts");
        fs::create_dir_all(&adobe_cache_dir).expect("adobe cache dir");
        let adobe_cache_file = adobe_cache_dir.join("fonts.bin");
        fs::write(&adobe_cache_file, b"cache").expect("adobe cache");

        let office_cache_dir = root.join("Library/Group Containers/UBF8T346G9.Office/FontCache");
        fs::create_dir_all(&office_cache_dir).expect("office cache dir");
        let office_cache_file = office_cache_dir.join("fontcache.dat");
        fs::write(&office_cache_file, b"cache").expect("office cache");

        env::set_var("FONTLIFT_TEST_CACHE_ROOT", root);
        let manager = MacFontManager::new();
        manager
            .clear_font_caches(FontScope::User)
            .expect("clear caches");

        assert!(
            !adobe_list.exists(),
            "Adobe font list cache should be removed"
        );
        assert!(
            !adobe_cache_file.exists(),
            "Adobe font cache file should be removed"
        );
        assert!(
            fs::read_dir(&office_cache_dir)
                .expect("office dir")
                .next()
                .is_none(),
            "Office font cache directory should be emptied"
        );
    }
}
