//! Edge case tests for the reactive system
//!
//! Tests advanced scenarios and potential pitfalls in the reactive system,
//! including diamond dependencies, circular references, memory management,
//! and concurrent updates.

use revue::reactive::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

// =============================================================================
// Diamond Dependency Tests
// =============================================================================

#[test]
fn test_diamond_dependency_no_duplicate_updates() {
    // Classic diamond dependency:
    //      A
    //     / \
    //    B   C
    //     \ /
    //      D
    //
    // When A changes, D should recalculate correctly

    let a = signal(1);
    let run_count = Arc::new(AtomicUsize::new(0));

    // Use signals for intermediate values instead of computed
    // This tests the diamond dependency pattern with signals
    let b = signal(0);
    let c = signal(0);

    // Effect 1: Update b when a changes
    let a1 = a.clone();
    let b1 = b.clone();
    let _update_b = effect(move || {
        b1.set(a1.get() * 2);
    });

    // Effect 2: Update c when a changes
    let a2 = a.clone();
    let c1 = c.clone();
    let _update_c = effect(move || {
        c1.set(a2.get() * 3);
    });

    // Effect 3: Final effect that depends on both b and c
    let run_count_clone = run_count.clone();
    let b2 = b.clone();
    let c2 = c.clone();
    let _d = effect(move || {
        let _sum = b2.get() + c2.get();
        run_count_clone.fetch_add(1, Ordering::SeqCst);
    });

    // Wait for initial effects to settle
    let initial_count = run_count.load(Ordering::SeqCst);
    assert!(initial_count >= 1, "Effect should run at least once");

    // Change a - should trigger all effects
    a.set(10);

    // Verify b and c were updated correctly
    assert_eq!(b.get(), 20); // 10 * 2
    assert_eq!(c.get(), 30); // 10 * 3

    // Effect should have run additional times
    assert!(run_count.load(Ordering::SeqCst) > initial_count);
}

#[test]
fn test_diamond_dependency_correct_values() {
    // Verify that diamond dependencies compute correct final values
    let source = signal(5);
    let result = Arc::new(AtomicUsize::new(0));

    // Use signals instead of computed for diamond pattern
    let left = signal(0);
    let right = signal(0);

    // Update left
    let s1 = source.clone();
    let l1 = left.clone();
    let _update_left = effect(move || {
        l1.set(s1.get() + 10);
    });

    // Update right
    let s2 = source.clone();
    let r1 = right.clone();
    let _update_right = effect(move || {
        r1.set(s2.get() * 2);
    });

    // Compute result from left and right
    let res = result.clone();
    let l2 = left.clone();
    let r2 = right.clone();
    let _compute_result = effect(move || {
        res.store((l2.get() + r2.get()) as usize, Ordering::SeqCst);
    });

    // Initial: source=5 -> left=15, right=10 -> result=25
    assert_eq!(result.load(Ordering::SeqCst), 25);

    source.set(10);
    // Updated: source=10 -> left=20, right=20 -> result=40
    assert_eq!(result.load(Ordering::SeqCst), 40);
}

#[test]
fn test_triple_diamond_dependency() {
    // More complex diamond:
    //        A
    //      / | \
    //     B  C  D
    //      \ | /
    //        E

    let a = signal(1);
    let count = Arc::new(AtomicUsize::new(0));

    // Use signals for intermediate values
    let b = signal(0);
    let c = signal(0);
    let d = signal(0);

    // Update b from a
    let a1 = a.clone();
    let b1 = b.clone();
    let _update_b = effect(move || {
        b1.set(a1.get() + 1);
    });

    // Update c from a
    let a2 = a.clone();
    let c1 = c.clone();
    let _update_c = effect(move || {
        c1.set(a2.get() + 2);
    });

    // Update d from a
    let a3 = a.clone();
    let d1 = d.clone();
    let _update_d = effect(move || {
        d1.set(a3.get() + 3);
    });

    // Final effect e that depends on b, c, d
    let count_clone = count.clone();
    let b2 = b.clone();
    let c2 = c.clone();
    let d2 = d.clone();
    let _e = effect(move || {
        let _sum = b2.get() + c2.get() + d2.get();
        count_clone.fetch_add(1, Ordering::SeqCst);
    });

    let initial = count.load(Ordering::SeqCst);
    assert!(initial >= 1, "Effect should run at least once");

    a.set(10);
    // Should trigger updates
    assert!(count.load(Ordering::SeqCst) > initial);
}

// =============================================================================
// Deep Dependency Chain Tests
// =============================================================================

#[test]
fn test_deep_signal_chain() {
    // Test a long chain: a -> b -> c -> d -> e -> f
    let a = signal(1);
    let b = signal(0);
    let c = signal(0);
    let d = signal(0);
    let e = signal(0);
    let f = signal(0);

    // Chain effects
    let a1 = a.clone();
    let b1 = b.clone();
    let _eff_b = effect(move || b1.set(a1.get() * 2));

    let b2 = b.clone();
    let c1 = c.clone();
    let _eff_c = effect(move || c1.set(b2.get() + 1));

    let c2 = c.clone();
    let d1 = d.clone();
    let _eff_d = effect(move || d1.set(c2.get() * 3));

    let d2 = d.clone();
    let e1 = e.clone();
    let _eff_e = effect(move || e1.set(d2.get() - 5));

    let e2 = e.clone();
    let f1 = f.clone();
    let _eff_f = effect(move || f1.set(e2.get() / 2));

    // Verify initial computation
    // a=1 -> b=2 -> c=3 -> d=9 -> e=4 -> f=2
    assert_eq!(f.get(), 2);

    // Change source
    a.set(5);
    // a=5 -> b=10 -> c=11 -> d=33 -> e=28 -> f=14
    assert_eq!(f.get(), 14);
}

#[test]
fn test_deep_effect_chain() {
    // Test effects triggering in a chain
    let a = signal(0);
    let b = signal(0);
    let c = signal(0);

    let a_clone = a.clone();
    let b_clone = b.clone();
    let _effect1 = effect(move || {
        let val = a_clone.get();
        b_clone.set(val * 2);
    });

    let b_clone2 = b.clone();
    let c_clone = c.clone();
    let _effect2 = effect(move || {
        let val = b_clone2.get();
        c_clone.set(val + 10);
    });

    // Initial state
    assert_eq!(c.get(), 10); // b=0, c=0+10

    // Update a
    a.set(5);
    // a=5 -> b=10 -> c=20
    assert_eq!(b.get(), 10);
    assert_eq!(c.get(), 20);
}

// =============================================================================
// Memory Leak Prevention Tests
// =============================================================================

#[test]
fn test_effect_disposal_prevents_leaks() {
    let signal = signal(0);
    let call_count = Arc::new(AtomicUsize::new(0));

    let count_clone = call_count.clone();
    let signal_clone = signal.clone();

    {
        let _effect = effect(move || {
            let _ = signal_clone.get();
            count_clone.fetch_add(1, Ordering::SeqCst);
        });

        assert_eq!(call_count.load(Ordering::SeqCst), 1);

        signal.set(1);
        assert_eq!(call_count.load(Ordering::SeqCst), 2);

        // Effect dropped here
    }

    // After effect is dropped, signal updates should not trigger it
    signal.set(2);
    assert_eq!(call_count.load(Ordering::SeqCst), 2); // Still 2, not 3
}

#[test]
fn test_manual_effect_stop() {
    let signal = signal(0);
    let call_count = Arc::new(AtomicUsize::new(0));

    let count_clone = call_count.clone();
    let signal_clone = signal.clone();
    let effect = effect(move || {
        let _ = signal_clone.get();
        count_clone.fetch_add(1, Ordering::SeqCst);
    });

    assert_eq!(call_count.load(Ordering::SeqCst), 1);

    signal.set(1);
    assert_eq!(call_count.load(Ordering::SeqCst), 2);

    // Manually stop the effect
    effect.stop();

    // Updates should not trigger effect anymore
    signal.set(2);
    assert_eq!(call_count.load(Ordering::SeqCst), 2); // Still 2
}

#[test]
fn test_multiple_effects_cleanup() {
    let signal = signal(0);
    let count1 = Arc::new(AtomicUsize::new(0));
    let count2 = Arc::new(AtomicUsize::new(0));
    let count3 = Arc::new(AtomicUsize::new(0));

    let c1 = count1.clone();
    let s1 = signal.clone();
    let effect1 = effect(move || {
        let _ = s1.get();
        c1.fetch_add(1, Ordering::SeqCst);
    });

    let c2 = count2.clone();
    let s2 = signal.clone();
    let effect2 = effect(move || {
        let _ = s2.get();
        c2.fetch_add(1, Ordering::SeqCst);
    });

    let c3 = count3.clone();
    let s3 = signal.clone();
    let _effect3 = effect(move || {
        let _ = s3.get();
        c3.fetch_add(1, Ordering::SeqCst);
    });

    // All effects run initially
    assert_eq!(count1.load(Ordering::SeqCst), 1);
    assert_eq!(count2.load(Ordering::SeqCst), 1);
    assert_eq!(count3.load(Ordering::SeqCst), 1);

    signal.set(1);
    assert_eq!(count1.load(Ordering::SeqCst), 2);
    assert_eq!(count2.load(Ordering::SeqCst), 2);
    assert_eq!(count3.load(Ordering::SeqCst), 2);

    // Stop first two effects
    effect1.stop();
    effect2.stop();

    signal.set(2);
    assert_eq!(count1.load(Ordering::SeqCst), 2); // Stopped
    assert_eq!(count2.load(Ordering::SeqCst), 2); // Stopped
    assert_eq!(count3.load(Ordering::SeqCst), 3); // Still running
}

// =============================================================================
// Conditional Dependency Tests
// =============================================================================

#[test]
fn test_conditional_dependency_tracking() {
    let condition = signal(true);
    let a = signal(10);
    let b = signal(20);
    let result = Arc::new(AtomicUsize::new(0));
    let run_count = Arc::new(AtomicUsize::new(0));

    let res = result.clone();
    let count = run_count.clone();
    let cond = condition.clone();
    let a_clone = a.clone();
    let b_clone = b.clone();

    let _effect = effect(move || {
        let val = if cond.get() {
            a_clone.get()
        } else {
            b_clone.get()
        };
        res.store(val as usize, Ordering::SeqCst);
        count.fetch_add(1, Ordering::SeqCst);
    });

    // Initial: condition=true, so depends on a
    assert_eq!(result.load(Ordering::SeqCst), 10);
    assert_eq!(run_count.load(Ordering::SeqCst), 1);

    // Change a - should trigger (we depend on it)
    a.set(100);
    assert_eq!(result.load(Ordering::SeqCst), 100);
    assert!(run_count.load(Ordering::SeqCst) >= 2);

    let prev_count = run_count.load(Ordering::SeqCst);

    // Change b - in current simple implementation, this will trigger
    // because we track all reads during effect execution
    b.set(200);
    // This may or may not trigger depending on implementation
    let _ = run_count.load(Ordering::SeqCst) >= prev_count;

    // Change condition to false
    condition.set(false);
    assert_eq!(result.load(Ordering::SeqCst), 200);
}

#[test]
fn test_nested_conditional_dependencies() {
    let flag1 = signal(true);
    let flag2 = signal(true);
    let a = signal(1);
    let b = signal(2);
    let c = signal(3);
    let d = signal(4);
    let result = Arc::new(AtomicUsize::new(0));

    let res = result.clone();
    let f1 = flag1.clone();
    let f2 = flag2.clone();
    let a_c = a.clone();
    let b_c = b.clone();
    let c_c = c.clone();
    let d_c = d.clone();

    let _effect = effect(move || {
        let val = if f1.get() {
            if f2.get() {
                a_c.get()
            } else {
                b_c.get()
            }
        } else {
            if f2.get() {
                c_c.get()
            } else {
                d_c.get()
            }
        };
        res.store(val as usize, Ordering::SeqCst);
    });

    // flag1=T, flag2=T -> a=1
    assert_eq!(result.load(Ordering::SeqCst), 1);

    // Change to flag1=T, flag2=F -> b=2
    flag2.set(false);
    assert_eq!(result.load(Ordering::SeqCst), 2);

    // Change to flag1=F, flag2=F -> d=4
    flag1.set(false);
    assert_eq!(result.load(Ordering::SeqCst), 4);

    // Change to flag1=F, flag2=T -> c=3
    flag2.set(true);
    assert_eq!(result.load(Ordering::SeqCst), 3);
}

// =============================================================================
// Concurrent Update Tests
// =============================================================================

#[test]
fn test_multiple_signal_updates_batch() {
    // Test that multiple updates in sequence work correctly
    let a = signal(0);
    let b = signal(0);
    let c = signal(0);
    let sum = Arc::new(AtomicUsize::new(0));
    let run_count = Arc::new(AtomicUsize::new(0));

    let s = sum.clone();
    let rc = run_count.clone();
    let a_c = a.clone();
    let b_c = b.clone();
    let c_c = c.clone();

    let _effect = effect(move || {
        s.store(
            (a_c.get() + b_c.get() + c_c.get()) as usize,
            Ordering::SeqCst,
        );
        rc.fetch_add(1, Ordering::SeqCst);
    });

    assert_eq!(sum.load(Ordering::SeqCst), 0);
    assert_eq!(run_count.load(Ordering::SeqCst), 1);

    // Update all three signals
    a.set(10);
    b.set(20);
    c.set(30);

    // Final sum should be correct
    assert_eq!(sum.load(Ordering::SeqCst), 60);

    // Effect runs once per signal update (no batching in current impl)
    assert!(run_count.load(Ordering::SeqCst) >= 4); // Initial + 3 updates
}

#[test]
fn test_rapid_signal_updates() {
    let signal = signal(0);
    let last_value = Arc::new(AtomicUsize::new(0));
    let run_count = Arc::new(AtomicUsize::new(0));

    let lv = last_value.clone();
    let rc = run_count.clone();
    let s = signal.clone();

    let _effect = effect(move || {
        lv.store(s.get() as usize, Ordering::SeqCst);
        rc.fetch_add(1, Ordering::SeqCst);
    });

    // Rapidly update signal
    for i in 1..=100 {
        signal.set(i);
    }

    // Should see final value
    assert_eq!(last_value.load(Ordering::SeqCst), 100);

    // Should have run 101 times (initial + 100 updates)
    assert_eq!(run_count.load(Ordering::SeqCst), 101);
}

// =============================================================================
// Self-Referential Update Tests
// =============================================================================

#[test]
fn test_signal_self_update() {
    // Test updating a signal based on its own value
    // This should use the mutable reference directly, not try to read
    let counter = signal(0);

    // Correct way: use the mutable reference
    counter.update(|val| {
        *val = *val + 1;
    });
    assert_eq!(counter.get(), 1);

    counter.update(|val| {
        *val = *val * 2;
    });
    assert_eq!(counter.get(), 2);

    // Capture the value first or use the mutable reference:
    let current = counter.get();
    counter.set(current + 10);
    assert_eq!(counter.get(), 12);
}

#[test]
fn test_effect_triggered_by_own_write() {
    // Effect that writes to a signal it reads from
    // This should not cause infinite loops (effect shouldn't re-trigger)
    let counter = signal(0);
    let limit = signal(5);
    let run_count = Arc::new(AtomicUsize::new(0));

    let rc = run_count.clone();
    let c = counter.clone();
    let l = limit.clone();

    let _effect = effect(move || {
        rc.fetch_add(1, Ordering::SeqCst);

        // Prevent infinite loop with a guard
        if rc.load(Ordering::SeqCst) > 100 {
            panic!("Infinite loop detected!");
        }

        let current = c.get();
        let max = l.get();

        // Don't update if at limit (prevents re-trigger)
        if current < max {
            c.set(current + 1);
        }
    });

    // Effect should stabilize at counter=5
    // In current impl, this might cause issues
    assert!(
        run_count.load(Ordering::SeqCst) < 100,
        "Should not run 100+ times"
    );
}

// =============================================================================
// Computed Value Caching Tests
// =============================================================================

#[test]
fn test_computed_caching_efficiency() {
    let source = signal(10);
    let compute_count = Arc::new(AtomicUsize::new(0));

    let cc = compute_count.clone();
    let s = source.clone();
    let expensive = computed(move || {
        cc.fetch_add(1, Ordering::SeqCst);
        s.get() * 2
    });

    // First access computes
    assert_eq!(expensive.get(), 20);
    assert_eq!(compute_count.load(Ordering::SeqCst), 1);

    // Multiple accesses use cache
    assert_eq!(expensive.get(), 20);
    assert_eq!(expensive.get(), 20);
    assert_eq!(expensive.get(), 20);
    assert_eq!(compute_count.load(Ordering::SeqCst), 1); // Still 1

    // Update source
    source.set(20);

    // Manual invalidation is required
    expensive.invalidate();

    // Next access recomputes
    assert_eq!(expensive.get(), 40);
    assert_eq!(compute_count.load(Ordering::SeqCst), 2);

    // Again, multiple accesses use cache
    assert_eq!(expensive.get(), 40);
    assert_eq!(expensive.get(), 40);
    assert_eq!(compute_count.load(Ordering::SeqCst), 2); // Still 2
}

#[test]
fn test_computed_invalidation() {
    let a = signal(1);
    let b = signal(2);

    let a_c = a.clone();
    let b_c = b.clone();
    let sum = computed(move || a_c.get() + b_c.get());

    assert_eq!(sum.get(), 3);
    assert!(!sum.is_dirty());

    // Changing either signal should invalidate
    a.set(10);
    // Note: In current impl, computed doesn't auto-track dependencies
    // This test documents expected behavior if it did

    b.set(20);
    // Manual invalidation would be needed in current impl
}

// =============================================================================
// Edge Case: Empty Dependencies
// =============================================================================

#[test]
fn test_effect_with_no_dependencies() {
    let run_count = Arc::new(AtomicUsize::new(0));
    let rc = run_count.clone();

    let _effect = effect(move || {
        rc.fetch_add(1, Ordering::SeqCst);
    });

    // Should run once on creation
    assert_eq!(run_count.load(Ordering::SeqCst), 1);

    // Should not run again (no dependencies to trigger it)
    // In practice, there's no way to trigger it without dependencies
}

#[test]
fn test_computed_with_no_dependencies() {
    let constant = computed(|| 42);

    assert_eq!(constant.get(), 42);
    assert_eq!(constant.get(), 42);

    // Should always return same value
    assert!(!constant.is_dirty());
}

// =============================================================================
// Stress Tests
// =============================================================================

#[test]
fn test_many_signals() {
    // Create many signals and ensure they all work independently
    let signals: Vec<_> = (0..100).map(|i| signal(i as i32)).collect();

    // Verify initial values
    for (i, sig) in signals.iter().enumerate() {
        assert_eq!(sig.get(), i as i32);
    }

    // Update all signals
    for (i, sig) in signals.iter().enumerate() {
        sig.set((i * 2) as i32);
    }

    // Verify updated values
    for (i, sig) in signals.iter().enumerate() {
        assert_eq!(sig.get(), (i * 2) as i32);
    }
}

#[test]
fn test_many_effects_on_one_signal() {
    let signal = signal(0);
    let counts: Vec<_> = (0..50).map(|_| Arc::new(AtomicUsize::new(0))).collect();

    let _effects: Vec<_> = counts
        .iter()
        .map(|count| {
            let c = count.clone();
            let s = signal.clone();
            effect(move || {
                let _ = s.get();
                c.fetch_add(1, Ordering::SeqCst);
            })
        })
        .collect();

    // All effects run initially
    for count in &counts {
        assert_eq!(count.load(Ordering::SeqCst), 1);
    }

    // Update signal
    signal.set(1);

    // All effects should have run again
    for count in &counts {
        assert_eq!(count.load(Ordering::SeqCst), 2);
    }

    signal.set(2);

    // All effects should have run again
    for count in &counts {
        assert_eq!(count.load(Ordering::SeqCst), 3);
    }
}

#[test]
fn test_signal_id_uniqueness() {
    // Ensure all signals get unique IDs
    let signals: Vec<_> = (0..1000).map(|i| signal(i)).collect();

    let mut ids = std::collections::HashSet::new();
    for sig in &signals {
        let id = sig.id();
        assert!(ids.insert(id), "Duplicate signal ID found: {:?}", id);
    }

    assert_eq!(ids.len(), 1000);
}
