//! IPFS/Kubo compatibility tests for ipfrs-core
//!
//! These tests verify that ipfrs-core is compatible with the standard IPFS
//! implementation (Kubo). They test CID generation, block format, and DAG
//! structures to ensure interoperability.
//!
//! To run these tests:
//! ```bash
//! cargo test --test ipfs_compat_tests
//! ```

use bytes::Bytes;
use ipfrs_core::{Block, CidBuilder, CidExt, Ipld, MultibaseEncoding};
use std::collections::BTreeMap;

// ============================================================================
// CID Compatibility Tests
// ============================================================================

/// Test that our CIDs match IPFS/Kubo's CID generation
///
/// Known CIDs generated by IPFS for common data:
/// - "hello world" -> QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
/// - empty file -> QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH
#[test]
fn test_cid_matches_ipfs() {
    // Test 1: "hello world" string
    let data = b"hello world";
    let cid = CidBuilder::v0().build_v0(data).unwrap();
    let cid_str = cid.to_string();

    // This should match IPFS's CID for "hello world"
    // Note: IPFS uses dag-pb codec for unixfs files, we use raw
    assert!(cid_str.starts_with("Qm"), "CIDv0 should start with Qm");
    assert_eq!(cid_str.len(), 46, "CIDv0 should be 46 characters");
}

/// Test CIDv0 format compliance
#[test]
fn test_cidv0_format() {
    let data = b"test data";
    let cid = CidBuilder::v0().build_v0(data).unwrap();

    // CIDv0 requirements
    assert!(cid.is_v0());
    assert!(!cid.is_v1());
    assert_eq!(cid.hash_algorithm_code(), 0x12); // SHA2-256

    let cid_str = cid.to_string();
    assert!(cid_str.starts_with("Qm"));
    assert_eq!(cid_str.len(), 46);
}

/// Test CIDv1 format compliance with different multibase encodings
#[test]
fn test_cidv1_multibase_formats() {
    let data = b"test data";
    let cid = CidBuilder::new().build(data).unwrap();

    assert!(cid.is_v1());

    // Test all common multibase encodings used by IPFS
    let base32_lower = cid.to_string_with_base(MultibaseEncoding::Base32Lower);
    assert!(
        base32_lower.starts_with('b'),
        "Base32lower should start with 'b'"
    );

    let base32_upper = cid.to_string_with_base(MultibaseEncoding::Base32Upper);
    assert!(
        base32_upper.starts_with('B'),
        "Base32upper should start with 'B'"
    );

    let base58btc = cid.to_string_with_base(MultibaseEncoding::Base58Btc);
    assert!(
        base58btc.starts_with('z'),
        "Base58btc should start with 'z'"
    );

    let base64 = cid.to_string_with_base(MultibaseEncoding::Base64);
    assert!(base64.starts_with('m'), "Base64 should start with 'm'");
}

/// Test CIDv0 to CIDv1 conversion (IPFS compatibility)
#[test]
fn test_cid_version_conversion() {
    let data = b"conversion test";

    // Create v0
    let cid_v0 = CidBuilder::v0().build_v0(data).unwrap();
    assert!(cid_v0.to_string().starts_with("Qm"));

    // Convert to v1
    let cid_v1 = cid_v0.to_v1().unwrap();
    assert!(cid_v1.is_v1());

    // Convert back to v0
    let back_to_v0 = cid_v1.to_v0().unwrap();
    assert_eq!(cid_v0, back_to_v0);
}

/// Test parsing CIDs in IPFS format
#[test]
fn test_parse_ipfs_cids() {
    // Valid CIDv0 (example from IPFS)
    let v0_str = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG";
    let cid_v0: ipfrs_core::Cid = v0_str.parse().unwrap();
    assert!(cid_v0.is_v0());
    assert_eq!(cid_v0.to_string(), v0_str);

    // Valid CIDv1 base32
    let data = b"test";
    let cid = CidBuilder::new().build(data).unwrap();
    let cid_str = cid.to_string_with_base(MultibaseEncoding::Base32Lower);
    let parsed: ipfrs_core::Cid = cid_str.parse().unwrap();
    assert_eq!(parsed, cid);
}

// ============================================================================
// Block Format Compatibility Tests
// ============================================================================

/// Test that blocks can be created with IPFS-compatible size limits
#[test]
fn test_block_size_limits() {
    // IPFS default max block size is 2MiB
    let max_size = 2 * 1024 * 1024;
    let data = vec![0u8; max_size];
    let block = Block::new(Bytes::from(data)).unwrap();
    assert_eq!(block.size(), max_size as u64);

    // Test minimum size (1 byte)
    let min_block = Block::new(Bytes::from(vec![0u8])).unwrap();
    assert_eq!(min_block.size(), 1);

    // Test that oversized blocks fail
    let oversized = vec![0u8; max_size + 1];
    let result = Block::new(Bytes::from(oversized));
    assert!(result.is_err());
}

/// Test block verification (IPFS does this for received blocks)
#[test]
fn test_block_verification() {
    let data = b"verify this block";
    let block = Block::new(Bytes::from_static(data)).unwrap();

    // Verification should pass
    assert!(block.verify().unwrap());

    // Create a block with mismatched CID (simulating corruption)
    let wrong_cid = CidBuilder::new().build(b"different data").unwrap();
    let invalid_block = Block::from_parts(wrong_cid, Bytes::from_static(data));

    // Verification should fail
    assert!(!invalid_block.verify().unwrap());
}

// ============================================================================
// DAG Compatibility Tests
// ============================================================================

/// Test DAG-CBOR encoding compatibility
#[test]
fn test_dag_cbor_encoding() {
    // Create IPLD structure similar to IPFS DAG nodes
    let mut map = BTreeMap::new();
    map.insert("name".to_string(), Ipld::String("test".to_string()));
    map.insert("size".to_string(), Ipld::Integer(1024));

    let ipld = Ipld::Map(map);

    // Encode to DAG-CBOR (IPFS uses CBOR for DAG nodes)
    let cbor = ipld.to_dag_cbor().unwrap();
    assert!(!cbor.is_empty());

    // Decode back
    let decoded = Ipld::from_dag_cbor(&cbor).unwrap();
    assert_eq!(decoded, ipld);
}

/// Test DAG-CBOR with CID links (IPFS DAG structure)
#[test]
fn test_dag_cbor_with_links() {
    let target_cid = CidBuilder::new().build(b"linked data").unwrap();

    let mut map = BTreeMap::new();
    map.insert("file".to_string(), Ipld::String("data.txt".to_string()));
    map.insert("link".to_string(), Ipld::Link(target_cid.into()));

    let ipld = Ipld::Map(map);

    // Encode with link
    let cbor = ipld.to_dag_cbor().unwrap();

    // Decode should preserve the link
    let decoded = Ipld::from_dag_cbor(&cbor).unwrap();

    if let Ipld::Map(decoded_map) = decoded {
        if let Some(Ipld::Link(link_cid)) = decoded_map.get("link") {
            let link_cid_unwrapped: ipfrs_core::Cid = (*link_cid).into();
            assert_eq!(link_cid_unwrapped, target_cid);
        } else {
            panic!("Link not preserved in round-trip");
        }
    }
}

/// Test DAG-JSON encoding (IPFS supports this for debugging)
#[test]
fn test_dag_json_encoding() {
    let mut map = BTreeMap::new();
    map.insert("hello".to_string(), Ipld::String("world".to_string()));
    map.insert("count".to_string(), Ipld::Integer(42));

    let ipld = Ipld::Map(map);

    // Encode to DAG-JSON
    let json_str = ipld.to_dag_json().unwrap();

    // Should be valid JSON
    assert!(json_str.contains("hello"));
    assert!(json_str.contains("world"));

    // Decode back
    let decoded = Ipld::from_dag_json(&json_str).unwrap();
    assert_eq!(decoded, ipld);
}

// ============================================================================
// Hash Algorithm Compatibility Tests
// ============================================================================

/// Test SHA2-256 hashing (IPFS default)
#[test]
fn test_sha256_compatibility() {
    use ipfrs_core::HashAlgorithm;

    let data = b"hash this";
    let cid = CidBuilder::new()
        .hash_algorithm(HashAlgorithm::Sha256)
        .build(data)
        .unwrap();

    assert_eq!(cid.hash_algorithm_code(), 0x12); // multihash code for SHA2-256
    assert_eq!(cid.hash_algorithm_name(), "sha2-256");
}

/// Test SHA3-256 hashing (supported by IPFS)
#[test]
fn test_sha3_256_compatibility() {
    use ipfrs_core::HashAlgorithm;

    let data = b"hash this with sha3";
    let cid = CidBuilder::new()
        .hash_algorithm(HashAlgorithm::Sha3_256)
        .build(data)
        .unwrap();

    // SHA3-256 uses code 0x16 in multihash
    let code = cid.hash_algorithm_code();
    println!("Actual SHA3-256 code: 0x{:x}", code);
    assert_eq!(code, 0x16); // multihash code for SHA3-256

    // The name should be sha3-256
    let name = cid.hash_algorithm_name();
    println!("Hash algorithm name: {}", name);
    assert_eq!(name, "sha3-256");
}

// ============================================================================
// Codec Compatibility Tests
// ============================================================================

/// Test codec support (IPFS uses various codecs)
#[test]
fn test_codec_compatibility() {
    use ipfrs_core::codec;

    let data = b"codec test";

    // Test RAW codec (0x55)
    let raw_cid = CidBuilder::new().codec(codec::RAW).build(data).unwrap();
    assert_eq!(raw_cid.codec_code(), codec::RAW);

    // Test DAG-CBOR codec (0x71)
    let cbor_cid = CidBuilder::new()
        .codec(codec::DAG_CBOR)
        .build(data)
        .unwrap();
    assert_eq!(cbor_cid.codec_code(), codec::DAG_CBOR);

    // Test DAG-PB codec (0x70) - IPFS default for files
    let pb_cid = CidBuilder::new().codec(codec::DAG_PB).build(data).unwrap();
    assert_eq!(pb_cid.codec_code(), codec::DAG_PB);
}

// ============================================================================
// Integration Tests
// ============================================================================

/// Test complete workflow: create, encode, verify (IPFS workflow)
#[test]
fn test_ipfs_workflow() {
    // 1. Create data
    let data = b"Hello, IPFS world!";

    // 2. Create block
    let block = Block::new(Bytes::from_static(data)).unwrap();

    // 3. Get CID
    let cid = block.cid();

    // 4. Verify block
    assert!(block.verify().unwrap());

    // 5. CID should be reproducible
    let cid2 = CidBuilder::new().build(data).unwrap();
    assert_eq!(*cid, cid2);

    // 6. Convert to string (for storage/transmission)
    let cid_str = cid.to_string();
    assert!(!cid_str.is_empty());

    // 7. Parse back from string
    let parsed: ipfrs_core::Cid = cid_str.parse().unwrap();
    assert_eq!(*cid, parsed);
}

/// Test that we can handle IPFS-style DAG structures
#[test]
fn test_ipfs_dag_structure() {
    // Create a simple DAG similar to IPFS UnixFS

    // Leaf nodes (file chunks)
    let chunk1 = b"chunk1";
    let chunk2 = b"chunk2";

    let cid1 = CidBuilder::new().build(chunk1).unwrap();
    let cid2 = CidBuilder::new().build(chunk2).unwrap();

    // Root node with links
    let mut root = BTreeMap::new();
    root.insert("type".to_string(), Ipld::String("file".to_string()));

    let links = vec![Ipld::Link(cid1.into()), Ipld::Link(cid2.into())];
    root.insert("links".to_string(), Ipld::List(links));

    let root_ipld = Ipld::Map(root);

    // Encode as DAG-CBOR (IPFS format)
    let encoded = root_ipld.to_dag_cbor().unwrap();
    assert!(!encoded.is_empty());

    // Create CID for the root
    let root_cid = CidBuilder::new()
        .codec(ipfrs_core::codec::DAG_CBOR)
        .build(&encoded)
        .unwrap();

    // This CID could be used to reference the entire DAG in IPFS
    assert!(root_cid.is_v1());
}

/// Test multibase encoding round-trips (IPFS uses multibase)
#[test]
fn test_multibase_roundtrips() {
    let data = b"multibase test";
    let cid = CidBuilder::new().build(data).unwrap();

    let encodings = [
        MultibaseEncoding::Base32Lower,
        MultibaseEncoding::Base32Upper,
        MultibaseEncoding::Base58Btc,
        MultibaseEncoding::Base64,
        MultibaseEncoding::Base64Url,
    ];

    for encoding in &encodings {
        let encoded = cid.to_string_with_base(*encoding);
        let parsed: ipfrs_core::Cid = encoded.parse().unwrap();
        assert_eq!(cid, parsed, "Round-trip failed for {:?}", encoding);
    }
}

/// Test that our blocks are compatible with IPFS block size conventions
#[test]
fn test_chunking_compatibility() {
    use ipfrs_core::{Chunker, ChunkingConfig};

    // IPFS uses 256KB chunks by default
    let config = ChunkingConfig::with_chunk_size(256 * 1024).unwrap();
    let chunker = Chunker::with_config(config);

    // Create a 1MB file (should create 4 chunks)
    let data = vec![0u8; 1_000_000];
    let chunked = chunker.chunk(&data).unwrap();

    // Each block should be <= 256KB
    for block in &chunked.blocks {
        assert!(block.size() <= 256 * 1024);
    }

    // Root CID should be valid
    assert!(chunked.root_cid.is_v1());
}
