//! End-to-end integration tests
//!
//! Tests the complete write → read roundtrip pipeline for all 3 formats
//! using realistic database data with entries, groups, and attachments.

use std::io::Cursor;

use keepass_rs::model::db::composite_key::CompositeKey;
use keepass_rs::model::db::database::{Database, DatabaseVersion};
use keepass_rs::model::entry::Entry;
use keepass_rs::model::group::Group;
use keepass_rs::model::core::node::NodeId;
use keepass_rs::model::core::security::ProtectedString;
use keepass_rs::kdbx::kdf::kdf_parameters::KdfParameters;
use keepass_rs::kdbx::kdf::argon2_kdf::ARGON2ID_UUID;
use keepass_rs::kdbx::kdf::kdf_engine::KdfEngine;
use keepass_rs::kdbx::kdf::create_kdf;

/// Build a realistic test database with multiple groups and entries.
fn build_realistic_database() -> Database {
    let mut db = Database::new(DatabaseVersion::KDBX4);
    db.name = "Test Database".to_string();
    db.description = "A realistic test database".to_string();
    db.default_username = "testuser".to_string();

    // Root group
    let root_id = NodeId::new_uuid();
    let mut root = Group::new(root_id);
    root.title = "Root".to_string();

    // General group
    let general_id = NodeId::new_uuid();
    let mut general = Group::new(general_id);
    general.title = "General".to_string();
    root.add_child_group(general_id);

    // Email group
    let email_id = NodeId::new_uuid();
    let mut email = Group::new(email_id);
    email.title = "Email Accounts".to_string();
    root.add_child_group(email_id);

    // Nested group
    let work_id = NodeId::new_uuid();
    let mut work = Group::new(work_id);
    work.title = "Work".to_string();
    email.add_child_group(work_id);

    // Entry 1: Gmail
    let e1_id = NodeId::new_uuid();
    let mut e1 = Entry::new(e1_id);
    e1.title = "Gmail Account".to_string();
    e1.username = ProtectedString::new_protected("user@gmail.com");
    e1.password = ProtectedString::new_protected("super_secret_password");
    e1.url = "https://mail.google.com".to_string();
    e1.notes = ProtectedString::new_protected("Personal email with 2FA enabled");
    general.add_child_entry(e1_id);

    // Entry 2: GitHub
    let e2_id = NodeId::new_uuid();
    let mut e2 = Entry::new(e2_id);
    e2.title = "GitHub".to_string();
    e2.username = ProtectedString::new_protected("developer");
    e2.password = ProtectedString::new_protected("ghp_abc123def456");
    e2.url = "https://github.com".to_string();
    e2.tags = vec!["development".to_string(), "coding".to_string()];
    general.add_child_entry(e2_id);

    // Entry 3: AWS (with custom fields)
    let e3_id = NodeId::new_uuid();
    let mut e3 = Entry::new(e3_id);
    e3.title = "AWS Console".to_string();
    e3.username = ProtectedString::new_protected("admin@company.com");
    e3.password = ProtectedString::new_protected("aws_secret_key_123");
    e3.url = "https://console.aws.amazon.com".to_string();
    e3.notes = ProtectedString::new_protected("Root account — use IAM for daily work");
    email.add_child_entry(e3_id);

    // Entry 4: Work VPN
    let e4_id = NodeId::new_uuid();
    let mut e4 = Entry::new(e4_id);
    e4.title = "Work VPN".to_string();
    e4.username = ProtectedString::new_protected("jdoe");
    e4.password = ProtectedString::new_protected("vpn_password");
    e4.url = "vpn.company.com".to_string();
    work.add_child_entry(e4_id);

    // Insert everything
    db.groups.insert(root_id, root);
    db.groups.insert(general_id, general);
    db.groups.insert(email_id, email);
    db.groups.insert(work_id, work);
    db.entries.insert(e1_id, e1);
    db.entries.insert(e2_id, e2);
    db.entries.insert(e3_id, e3);
    db.entries.insert(e4_id, e4);

    db.root_group = Some(db.groups.get(&root_id).cloned().unwrap());
    db
}

fn make_key() -> CompositeKey {
    CompositeKey::new()
        .with_password(b"integration_test_password_2024")
}

/// Verify that a round-tripped database preserved key data.
fn verify_database(db: &Database) {
    assert!(db.root_group.is_some(), "Root group should exist");
    assert!(db.groups.len() >= 3, "Should have at least 3 groups, got {}", db.groups.len());
    assert!(db.entries.len() >= 3, "Should have at least 3 entries, got {}", db.entries.len());

    // Verify entries have content
    let mut found_gmail = false;
    let mut found_github = false;
    for entry in db.entries.values() {
        if entry.title.contains("Gmail") {
            found_gmail = true;
            assert!(!entry.username.as_str().is_empty());
            assert!(!entry.password.as_str().is_empty());
        }
        if entry.title.contains("GitHub") {
            found_github = true;
            assert_eq!(entry.tags.len(), 2);
        }
    }
    assert!(found_gmail, "Gmail entry should be preserved");
    assert!(found_github, "GitHub entry should be preserved");
}

// ── KDBX 3.1 end-to-end ──

#[test]
fn test_e2e_kdbx31_roundtrip() {
    let mut db = build_realistic_database();
    db.version = DatabaseVersion::KDBX31;
    db.encryption_algorithm = keepass_rs::crypto::encryption_algorithm::EncryptionAlgorithm::AesRijndael;
    db.compression = keepass_rs::crypto::compression::CompressionAlgorithm::Gzip;

    let key = make_key();

    // Write
    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx31_writer::write_kdbx31(&mut buffer, &db, &key)
        .expect("KDBX 3.1 write should succeed");

    assert!(buffer.len() > 100, "Written data should be substantial");

    // Read back
    let mut cursor = Cursor::new(buffer);
    let loaded = keepass_rs::kdbx::file::kdbx31_reader::read_kdbx31(&mut cursor, &key)
        .expect("KDBX 3.1 read should succeed");

    verify_database(&loaded);
}

// ── KDBX 3.1 with wrong password ──

#[test]
fn test_e2e_kdbx31_wrong_password() {
    let db = build_realistic_database();
    let mut db = build_realistic_database();
    db.version = DatabaseVersion::KDBX31;

    let key = make_key();

    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx31_writer::write_kdbx31(&mut buffer, &db, &key)
        .expect("Write should succeed");

    // Read with wrong password
    let wrong_key = CompositeKey::new().with_password(b"wrong_password");
    let mut cursor = Cursor::new(buffer);
    let result = keepass_rs::kdbx::file::kdbx31_reader::read_kdbx31(&mut cursor, &wrong_key);
    assert!(result.is_err(), "Wrong password should fail");
}

// ── KDBX 3.1 with ChaCha20 ──

#[test]
fn test_e2e_kdbx31_chacha20() {
    let db = build_realistic_database();
    let mut db = build_realistic_database();
    db.version = DatabaseVersion::KDBX31;
    db.encryption_algorithm = keepass_rs::crypto::encryption_algorithm::EncryptionAlgorithm::ChaCha20;

    let key = make_key();

    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx31_writer::write_kdbx31(&mut buffer, &db, &key)
        .expect("ChaCha20 write should succeed");

    let mut cursor = Cursor::new(buffer);
    let loaded = keepass_rs::kdbx::file::kdbx31_reader::read_kdbx31(&mut cursor, &key)
        .expect("ChaCha20 read should succeed");

    verify_database(&loaded);
}

// ── KDBX 4.0 end-to-end ──

#[test]
fn test_e2e_kdbx4_roundtrip() {
    let mut db = build_realistic_database();
    db.version = DatabaseVersion::KDBX4;
    db.encryption_algorithm = keepass_rs::crypto::encryption_algorithm::EncryptionAlgorithm::AesRijndael;

    // Use minimal Argon2 parameters for test speed
    let kdf = create_kdf(&ARGON2ID_UUID).expect("Argon2 KDF");
    let mut params = kdf.default_parameters();
    params.set_uint64("M", 64 * 1024); // 64 KB instead of 16 MB
    params.set_uint64("I", 1);         // 1 iteration
    db.kdf_parameters = Some(params);

    let key = make_key();

    // Write
    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx4_writer::write_kdbx4(&mut buffer, &db, &key)
        .expect("KDBX 4.0 write should succeed");

    assert!(buffer.len() > 100);

    // Read back
    let mut cursor = Cursor::new(buffer);
    let loaded = keepass_rs::kdbx::file::kdbx4_reader::read_kdbx4(&mut cursor, &key)
        .expect("KDBX 4.0 read should succeed");

    verify_database(&loaded);
}

// ── KDB end-to-end ──

#[test]
fn test_e2e_kdb_roundtrip() {
    let db = build_realistic_database();
    let mut db = build_realistic_database();
    db.version = DatabaseVersion::KDB;

    let key = make_key();

    // Write
    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdb_writer::write_kdb(&mut buffer, &db, &key)
        .expect("KDB write should succeed");

    assert!(buffer.len() > 100);

    // Read back — need to detect version first to consume signature
    let mut cursor = Cursor::new(buffer);
    let version = keepass_rs::kdbx::file::reader::DatabaseReader::detect_version(&mut cursor)
        .expect("Should detect KDB version");
    assert_eq!(version, DatabaseVersion::KDB);

    let loaded = keepass_rs::kdbx::file::kdb_reader::read_kdb(&mut cursor, &key)
        .expect("KDB read should succeed");

    // KDB has limited fields — just check groups and entries survived
    assert!(loaded.groups.len() >= 2, "Should have at least 2 groups");
    assert!(loaded.entries.len() >= 2, "Should have at least 2 entries");
}

// ── Empty database roundtrip ──

#[test]
fn test_e2e_kdbx31_empty_database() {
    let mut db = Database::new(DatabaseVersion::KDBX31);
    let root_id = NodeId::new_uuid();
    let mut root = Group::new(root_id);
    root.title = "Root".to_string();
    db.groups.insert(root_id, root);
    db.root_group = Some(db.groups.get(&root_id).cloned().unwrap());

    let key = make_key();

    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx31_writer::write_kdbx31(&mut buffer, &db, &key)
        .expect("Empty DB write should succeed");

    let mut cursor = Cursor::new(buffer);
    let loaded = keepass_rs::kdbx::file::kdbx31_reader::read_kdbx31(&mut cursor, &key)
        .expect("Empty DB read should succeed");

    assert!(loaded.root_group.is_some());
    assert_eq!(loaded.entries.len(), 0);
}

// ── Unicode content roundtrip ──

#[test]
fn test_e2e_kdbx31_unicode_content() {
    let mut db = Database::new(DatabaseVersion::KDBX31);
    let root_id = NodeId::new_uuid();
    let mut root = Group::new(root_id);
    root.title = "根目录".to_string();

    let entry_id = NodeId::new_uuid();
    let mut entry = Entry::new(entry_id);
    entry.title = "日本語エントリ".to_string();
    entry.username = ProtectedString::new_protected("用户名");
    entry.password = ProtectedString::new_protected("密码密码");
    entry.url = "https://例え.jp".to_string();
    entry.notes = ProtectedString::new_protected("备注信息 🔐");
    root.add_child_entry(entry_id);

    db.groups.insert(root_id, root);
    db.entries.insert(entry_id, entry);
    db.root_group = Some(db.groups.get(&root_id).cloned().unwrap());

    let key = make_key();

    let mut buffer = Vec::new();
    keepass_rs::kdbx::file::kdbx31_writer::write_kdbx31(&mut buffer, &db, &key)
        .expect("Unicode write should succeed");

    let mut cursor = Cursor::new(buffer);
    let loaded = keepass_rs::kdbx::file::kdbx31_reader::read_kdbx31(&mut cursor, &key)
        .expect("Unicode read should succeed");

    let loaded_entry = loaded.entries.values().next().unwrap();
    assert_eq!(loaded_entry.title, "日本語エントリ");
    assert_eq!(loaded_entry.username.as_str(), "用户名");
    assert!(loaded_entry.notes.as_str().contains("🔐"));
}
