//! End-to-end integration tests for the typed-item core.

use relicario_core::{
    crypto::KdfParams,
    derive_master_key, encrypt_item, decrypt_item,
    encrypt_manifest, decrypt_manifest,
    encrypt_settings, decrypt_settings,
    Field, FieldValue, Item, ItemCore, Manifest, Section, VaultSettings,
};
use relicario_core::item_types::{LoginCore, SecureNoteCore};
use url::Url;
use zeroize::Zeroizing;

fn fast_params() -> KdfParams {
    KdfParams { argon2_m: 256, argon2_t: 1, argon2_p: 1 }
}

#[test]
fn full_workflow_login_and_note() {
    let salt = [0xAAu8; 32];
    let img = [0xBBu8; 32];
    let key = derive_master_key(b"correct horse battery staple", &img, &salt, &fast_params()).unwrap();

    let mut manifest = Manifest::new();
    let settings = VaultSettings::default();

    // Add a Login
    let login = Item::new("GitHub".into(), ItemCore::Login(LoginCore {
        username: Some("alice".into()),
        password: Some(Zeroizing::new("hunter2".into())),
        url: Some(Url::parse("https://github.com").unwrap()),
        totp: None,
    }));
    manifest.upsert(&login);
    let login_blob = encrypt_item(&login, &key).unwrap();

    // Add a SecureNote
    let note = Item::new("recovery".into(), ItemCore::SecureNote(SecureNoteCore {
        body: Zeroizing::new("recovery codes go here".into()),
    }));
    manifest.upsert(&note);
    let note_blob = encrypt_item(&note, &key).unwrap();

    // Encrypt manifest + settings
    let manifest_blob = encrypt_manifest(&manifest, &key).unwrap();
    let settings_blob = encrypt_settings(&settings, &key).unwrap();

    // Decrypt + verify
    let m = decrypt_manifest(&manifest_blob, &key).unwrap();
    assert_eq!(m.items.len(), 2);

    let l: Item = decrypt_item(&login_blob, &key).unwrap();
    let n: Item = decrypt_item(&note_blob, &key).unwrap();
    let s: VaultSettings = decrypt_settings(&settings_blob, &key).unwrap();

    assert_eq!(l.title, "GitHub");
    assert_eq!(n.title, "recovery");
    assert_eq!(s.attachment_caps.per_attachment_max_bytes, 10 * 1024 * 1024);
}

#[test]
fn two_factor_independence() {
    // Same passphrase, different image_secret → different keys.
    let salt = [0u8; 32];
    let img_a = [0x01u8; 32];
    let img_b = [0x02u8; 32];

    let key_a = derive_master_key(b"same-passphrase", &img_a, &salt, &fast_params()).unwrap();
    let key_b = derive_master_key(b"same-passphrase", &img_b, &salt, &fast_params()).unwrap();
    assert_ne!(*key_a, *key_b);

    // Different passphrase, same image_secret → different keys.
    let key_c = derive_master_key(b"other-passphrase", &img_a, &salt, &fast_params()).unwrap();
    assert_ne!(*key_a, *key_c);
}

#[test]
fn field_history_persists_through_round_trip() {
    let salt = [0u8; 32];
    let img = [0u8; 32];
    let key = derive_master_key(b"x", &img, &salt, &fast_params()).unwrap();

    let mut item = Item::new("x".into(), ItemCore::Login(LoginCore::default()));
    let f = Field::new("p".into(), FieldValue::Password(Zeroizing::new("v0".into())));
    let fid = f.id.clone();
    item.sections.push(Section { name: None, fields: vec![f] });
    item.set_field_value(&fid, FieldValue::Password(Zeroizing::new("v1".into()))).unwrap();
    item.set_field_value(&fid, FieldValue::Password(Zeroizing::new("v2".into()))).unwrap();

    let blob = encrypt_item(&item, &key).unwrap();
    let decoded = decrypt_item(&blob, &key).unwrap();
    let hist = decoded.field_history.get(&fid).unwrap();
    assert_eq!(hist.len(), 2);
    assert_eq!(hist[0].value.as_str(), "v0");
    assert_eq!(hist[1].value.as_str(), "v1");
}

#[test]
fn wrong_key_fails_with_opaque_decrypt() {
    use relicario_core::RelicarioError;

    let salt = [0u8; 32];
    let img = [0u8; 32];
    let right = derive_master_key(b"correct", &img, &salt, &fast_params()).unwrap();
    let wrong = derive_master_key(b"wrong", &img, &salt, &fast_params()).unwrap();

    let item = Item::new("x".into(), ItemCore::SecureNote(SecureNoteCore::default()));
    let blob = encrypt_item(&item, &right).unwrap();
    let err = decrypt_item(&blob, &wrong);
    assert!(matches!(err, Err(RelicarioError::Decrypt)));
}
