//! PoC: SecKeychainItemSetAccess で ACL だけ更新できるか検証
//!
//! 目的:
//!   ACL ブロック状態のアイテムに対して、値を読まずに ACL だけ差し替える
//!   → harden の chicken-and-egg 問題の解決策検証
//!
//! 実行:
//!   cargo run --example poc_harden_setaccess
//!
//! 検証項目:
//!   1. ACLブロック中でも item_ref が取れるか
//!   2. SecKeychainItemSetAccess が custom keychain で動くか
//!   3. GUI ダイアログが出るか（disable_user_interaction あり/なし）
//!   4. 更新後に lkr get で値が読めるか（手動確認）

use core_foundation::base::TCFType;
use security_framework::os::macos::keychain::SecKeychain;
use std::ffi::c_void;

// Security.framework FFI
unsafe extern "C" {
    fn SecKeychainFindGenericPassword(
        keychain: *const c_void,
        service_name_length: u32,
        service_name: *const u8,
        account_name_length: u32,
        account_name: *const u8,
        password_length: *mut u32,
        password_data: *mut *mut c_void,
        item_ref: *mut *mut c_void,
    ) -> i32;

    fn SecKeychainItemSetAccess(item_ref: *const c_void, access: *const c_void) -> i32;

    fn SecTrustedApplicationCreateFromPath(path: *const i8, app_out: *mut *mut c_void) -> i32;

    fn SecAccessCreate(
        descriptor: *const c_void,
        trusted_list: *const c_void,
        access_out: *mut *mut c_void,
    ) -> i32;

    fn CFArrayCreateMutable(
        allocator: *const c_void,
        capacity: isize,
        callbacks: *const c_void,
    ) -> *mut c_void;
    fn CFArrayAppendValue(array: *mut c_void, value: *const c_void);
    fn CFRelease(cf: *const c_void);
    fn CFStringCreateWithBytes(
        alloc: *const c_void,
        bytes: *const u8,
        num_bytes: isize,
        encoding: u32,
        is_external: bool,
    ) -> *mut c_void;
    static kCFTypeArrayCallBacks: c_void;
}

const SERVICE: &str = "com.llm-key-ring";
const TARGET_ACCOUNT: &str = "anthropic:prod"; // 1キーだけ対象
const K_CF_STRING_ENCODING_UTF8: u32 = 0x0800_0100;

fn main() {
    println!("=== PoC: SecKeychainItemSetAccess on ACL-blocked item ===\n");

    // Step 1: Open + unlock custom keychain
    let kc_path = {
        let home = std::env::var("HOME").expect("$HOME not set");
        format!("{}/Library/Keychains/lkr.keychain-db", home)
    };
    println!("[1] Opening keychain: {}", kc_path);

    let mut kc = SecKeychain::open(&kc_path).expect("Failed to open keychain");

    // Prompt for password (no masking — this is a PoC)
    eprint!("Keychain password: ");
    let mut password = String::new();
    std::io::stdin()
        .read_line(&mut password)
        .expect("Failed to read password");
    let password = password.trim_end().to_string();
    kc.unlock(Some(&password))
        .expect("Failed to unlock keychain");
    println!("    Unlocked.\n");

    // Step 2: Find item_ref WITHOUT requesting password data
    // Pass null for pw_length and pw_data to skip value retrieval
    println!(
        "[2] Finding item_ref for '{}' (NO value request)...",
        TARGET_ACCOUNT
    );

    let svc = SERVICE.as_bytes();
    let acct = TARGET_ACCOUNT.as_bytes();
    let mut item_ref: *mut c_void = std::ptr::null_mut();

    let status = unsafe {
        SecKeychainFindGenericPassword(
            kc.as_concrete_TypeRef() as _,
            svc.len() as u32,
            svc.as_ptr(),
            acct.len() as u32,
            acct.as_ptr(),
            std::ptr::null_mut(), // pw_length: null (don't need value)
            std::ptr::null_mut(), // pw_data: null (don't need value)
            &mut item_ref,
        )
    };

    println!("    FindGenericPassword status: {}", status);
    println!("    item_ref is null: {}", item_ref.is_null());

    if item_ref.is_null() {
        println!("\n    FAILED: item_ref is null. Cannot proceed.");
        if status == -25308 {
            println!("    → -25308: even item_ref is blocked without user interaction");
        }
        println!("    → May need SecItemCopyMatching + kSecReturnRef instead");
        return;
    }

    println!("    OK: got item_ref (no dialog expected)\n");

    // Step 3: Build new ACL for current binary
    println!("[3] Building new ACL for current binary...");

    let binary_path = std::env::current_exe()
        .and_then(|p| p.canonicalize())
        .expect("Failed to resolve binary path");
    println!("    Binary: {}", binary_path.display());

    let path_cstr =
        std::ffi::CString::new(binary_path.to_str().unwrap()).expect("path contains NUL");

    let access = unsafe {
        let mut trusted_app: *mut c_void = std::ptr::null_mut();
        let ta_status = SecTrustedApplicationCreateFromPath(path_cstr.as_ptr(), &mut trusted_app);
        if ta_status != 0 {
            println!(
                "    FAILED: SecTrustedApplicationCreateFromPath status: {}",
                ta_status
            );
            CFRelease(item_ref as _);
            return;
        }

        let trusted_list =
            CFArrayCreateMutable(std::ptr::null(), 1, &kCFTypeArrayCallBacks as *const c_void);
        CFArrayAppendValue(trusted_list, trusted_app as _);

        let desc = "LKR API Key Access";
        let cf_desc = CFStringCreateWithBytes(
            std::ptr::null(),
            desc.as_ptr(),
            desc.len() as isize,
            K_CF_STRING_ENCODING_UTF8,
            false,
        );

        let mut access: *mut c_void = std::ptr::null_mut();
        let access_status = SecAccessCreate(cf_desc, trusted_list as _, &mut access);

        CFRelease(cf_desc as _);
        CFRelease(trusted_list as _);
        CFRelease(trusted_app as _);

        if access_status != 0 {
            println!("    FAILED: SecAccessCreate status: {}", access_status);
            CFRelease(item_ref as _);
            return;
        }

        println!("    OK: built SecAccessRef\n");
        access
    };

    // Step 4: Try SecKeychainItemSetAccess
    // Test A: WITH disable_user_interaction (no GUI dialog)
    println!("[4a] SecKeychainItemSetAccess WITH disable_user_interaction...");
    {
        let _guard = SecKeychain::disable_user_interaction();
        let set_status = unsafe { SecKeychainItemSetAccess(item_ref as _, access as _) };
        println!("     Status: {}", set_status);
        match set_status {
            0 => println!("     SUCCESS (no dialog needed)"),
            -128 => println!("     -128 (errSecUserCanceled) → dialog was suppressed"),
            -25293 => println!("     -25293 (errSecAuthFailed) → ACL blocked the operation"),
            -25308 => {
                println!("     -25308 (errSecInteractionNotAllowed) → needs user interaction")
            }
            other => println!("     Unknown status: {}", other),
        }
    }

    // Test B: WITHOUT disable_user_interaction (allow GUI dialog)
    println!("\n[4b] SecKeychainItemSetAccess WITHOUT disable_user_interaction...");
    println!("     (GUI dialog may appear — click 'Always Allow' or 'Allow')");
    {
        let set_status = unsafe { SecKeychainItemSetAccess(item_ref as _, access as _) };
        println!("     Status: {}", set_status);
        match set_status {
            0 => println!("     SUCCESS"),
            -128 => println!("     -128 (errSecUserCanceled) → user denied or dialog suppressed"),
            -25293 => println!("     -25293 (errSecAuthFailed) → ACL blocked"),
            -25308 => println!("     -25308 (errSecInteractionNotAllowed)"),
            other => println!("     Unknown status: {}", other),
        }
    }

    // Cleanup
    unsafe {
        CFRelease(access as _);
        CFRelease(item_ref as _);
    }

    println!("\n=== Done ===");
    println!("If 4a or 4b succeeded, verify with:");
    println!("  lkr get {}", TARGET_ACCOUNT);
}
