use workflow_context::{ContextStore, Vgcs};
use std::io::Read;
use tempfile::TempDir;
use std::sync::Arc;
use std::thread;

const ZERO_OID: &str = "0000000000000000000000000000000000000000";

#[test]
fn test_basic_workflow() -> anyhow::Result<()> {
    let temp_dir = TempDir::new()?;
    let store = Vgcs::new(temp_dir.path());
    let req_id = "req-001";

    // 1. Init
    store.init_repo(req_id)?;

    // 2. Write Transaction (Initial Commit)
    let mut tx = store.begin_transaction(req_id, ZERO_OID)?;
    tx.write("test.txt", b"Hello World")?;
    let commit_hash_1 = tx.commit("Initial commit", "Test User")?;

    // 3. Read
    let mut reader = store.read_file(req_id, &commit_hash_1, "test.txt")?;
    let mut content = String::new();
    reader.read_to_string(&mut content)?;
    assert_eq!(content, "Hello World");

    // 4. Modify file
    let mut tx = store.begin_transaction(req_id, &commit_hash_1)?;
    tx.write("test.txt", b"Hello World Modified")?;
    tx.write("new.txt", b"New File")?;
    let commit_hash_2 = tx.commit("Second commit", "Test User")?;

    // 5. Verify Diff
    let changes = store.diff(req_id, &commit_hash_1, &commit_hash_2)?;
    // Should have 1 Modified (test.txt) and 1 Added (new.txt)
    assert_eq!(changes.len(), 2);
    
    // 6. List Dir
    let entries = store.list_dir(req_id, &commit_hash_2, "")?;
    assert_eq!(entries.len(), 2); // test.txt, new.txt

    Ok(())
}

#[test]
fn test_large_file_support() -> anyhow::Result<()> {
    let temp_dir = TempDir::new()?;
    let store = Vgcs::new(temp_dir.path());
    let req_id = "req-large";
    store.init_repo(req_id)?;

    // Create 2MB data
    let large_data = vec![b'a'; 2 * 1024 * 1024];
    
    let mut tx = store.begin_transaction(req_id, ZERO_OID)?;
    tx.write("large.bin", &large_data)?;
    let commit_hash = tx.commit("Add large file", "Tester")?;

    // Read back
    let mut reader = store.read_file(req_id, &commit_hash, "large.bin")?;
    let mut read_data = Vec::new();
    reader.read_to_end(&mut read_data)?;
    
    assert_eq!(read_data.len(), large_data.len());
    // Checking first and last bytes to be reasonably sure without comparing 2MB in assertion message on failure
    assert_eq!(read_data[0], b'a');
    assert_eq!(read_data[read_data.len()-1], b'a');
    assert_eq!(read_data, large_data);

    // Check internal blob store
    // We don't calculate SHA256 here to verify path exactly, but we check if blobs dir has content
    let blobs_dir = temp_dir.path().join("blobs").join(req_id);
    assert!(blobs_dir.exists());
    // Should have subdirectories for SHA prefix
    let entries = std::fs::read_dir(blobs_dir)?.collect::<Vec<_>>();
    assert!(!entries.is_empty());

    Ok(())
}

#[test]
fn test_parallel_branching_and_merge() -> anyhow::Result<()> {
    let temp_dir = TempDir::new()?;
    // Clone temp_path for threads
    let temp_path = temp_dir.path().to_path_buf();
    let store = Arc::new(Vgcs::new(&temp_path));
    let req_id = "req-parallel";
    
    store.init_repo(req_id)?;

    // Initial commit
    let base_commit = {
        let mut tx = store.begin_transaction(req_id, ZERO_OID)?;
        tx.write("base.txt", b"Base Content")?;
        tx.commit("Base Commit", "System")?
    };

    // Fork 1: Modify base.txt
    let store1 = store.clone();
    let base1 = base_commit.clone();
    let handle1 = thread::spawn(move || -> anyhow::Result<String> {
        let mut tx = store1.begin_transaction(req_id, &base1)?;
        tx.write("base.txt", b"Base Content Modified by 1")?;
        tx.write("file1.txt", b"File 1 Content")?;
        Ok(tx.commit("Fork 1 Commit", "User 1")?)
    });

    // Fork 2: Add file2.txt (No conflict)
    let store2 = store.clone();
    let base2 = base_commit.clone();
    let handle2 = thread::spawn(move || -> anyhow::Result<String> {
        let mut tx = store2.begin_transaction(req_id, &base2)?;
        tx.write("file2.txt", b"File 2 Content")?;
        Ok(tx.commit("Fork 2 Commit", "User 2")?)
    });

    let commit1 = handle1.join().unwrap()?;
    let commit2 = handle2.join().unwrap()?;

    // Merge Fork 2 into Fork 1 (Memory Merge)
    // This merge should succeed as they touch different files/areas (mostly)
    // But wait, Fork 1 modified base.txt, Fork 2 kept it as is.
    // Git merge should take Fork 1's change and include Fork 2's new file.
    
    // We need to commit the merge result to verify it
    let merge_tree_oid = store.merge_trees(req_id, &base_commit, &commit1, &commit2)?;
    
    // Manually create a commit from the merge tree to verify content (optional but good)
    // In real system, Orchestrator would do this.
    // For test, we can just verify the tree contains what we expect or use a helper.
    // Or we can just trust merge_trees returns an OID on success.
    
    assert!(!merge_tree_oid.is_empty());
    
    Ok(())
}

#[test]
fn test_merge_conflict() -> anyhow::Result<()> {
    let temp_dir = TempDir::new()?;
    let store = Vgcs::new(temp_dir.path());
    let req_id = "req-conflict";
    store.init_repo(req_id)?;

    // Base
    let mut tx = store.begin_transaction(req_id, ZERO_OID)?;
    tx.write("conflict.txt", b"Base Version")?;
    let base_commit = tx.commit("Base", "System")?;

    // Branch A: Edit conflict.txt
    let mut tx_a = store.begin_transaction(req_id, &base_commit)?;
    tx_a.write("conflict.txt", b"Version A")?;
    let commit_a = tx_a.commit("Commit A", "User A")?;

    // Branch B: Edit conflict.txt differently
    let mut tx_b = store.begin_transaction(req_id, &base_commit)?;
    tx_b.write("conflict.txt", b"Version B")?;
    let commit_b = tx_b.commit("Commit B", "User B")?;

    // Try Merge
    let result = store.merge_trees(req_id, &base_commit, &commit_a, &commit_b);
    
    // Should fail with conflict
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("Merge conflict"));

    Ok(())
}
