//! Integration tests for src/engine.rs
//!
//! Tests the main Engine orchestrator that coordinates all subsystems.
//! These tests follow the Given/When/Then pattern for clarity.

mod common;

use common::*;
use hypen_engine::dispatch::Action;
use hypen_engine::ir::{Component, Element};
use hypen_engine::lifecycle::{Module, ModuleInstance};
use hypen_engine::Engine;
use serde_json::json;
use std::sync::{Arc, Mutex};

// ========== A. Engine Initialization Tests (3 tests) ==========

#[test]
fn test_engine_new_creates_empty_state() {
    // GIVEN: Nothing
    // WHEN: Create new engine
    let engine = Engine::new();

    // THEN: Engine has empty tree, no module, no dirty nodes
    assert_eq!(engine.revision(), 0);
    // Note: Can't access private fields, but we can test behavior
}

#[test]
fn test_engine_default_is_identical_to_new() {
    // GIVEN: Two engines
    let engine1 = Engine::new();
    let engine2 = Engine::default();

    // THEN: Both start with revision 0
    assert_eq!(engine1.revision(), engine2.revision());
    assert_eq!(engine1.revision(), 0);
}

#[test]
fn test_engine_revision_starts_at_zero() {
    // GIVEN/WHEN: New engine
    let engine = Engine::new();

    // THEN: Revision is 0
    assert_eq!(engine.revision(), 0);
}

// ========== B. Component Registration Tests (5 tests) ==========

#[test]
fn test_register_component_adds_to_registry() {
    // GIVEN: Engine and component template
    let mut engine = Engine::new();
    let component = Component::new("Button", |_props| text_element("Click me"));

    // WHEN: Register component
    engine.register_component(component);

    // THEN: Component can be expanded
    let element = Element::new("Button");
    let expanded = engine.component_registry_mut().expand(&element);
    assert_element_type(&expanded, "Text");
}

#[test]
fn test_register_component_replaces_existing() {
    // GIVEN: Component already registered
    let mut engine = Engine::new();
    let component1 = Component::new("Button", |_props| text_element("First"));
    let component2 = Component::new("Button", |_props| text_element("Second"));

    // WHEN: Register with same name twice
    engine.register_component(component1);
    engine.register_component(component2);

    // THEN: Second registration replaces first
    let element = Element::new("Button");
    let expanded = engine.component_registry_mut().expand(&element);
    assert_element_type(&expanded, "Text");
}

#[test]
fn test_component_resolver_fallback_when_not_in_registry() {
    // GIVEN: Engine with custom resolver
    let mut engine = Engine::new();
    let resolved = Arc::new(Mutex::new(false));
    let resolved_clone = resolved.clone();

    engine.set_component_resolver(move |name, _context| {
        *resolved_clone.lock().unwrap() = true;
        if name == "DynamicButton" {
            Some(hypen_engine::ir::ResolvedComponent {
                source: "Text(\"Dynamic\")".to_string(),
                path: "DynamicButton.hypen".to_string(),
                passthrough: false,
                lazy: false,
            })
        } else {
            None
        }
    });

    // WHEN: Expand unknown component
    let element = Element::new("DynamicButton");
    let _ = engine.component_registry_mut().expand(&element);

    // THEN: Resolver was called
    assert!(*resolved.lock().unwrap());
}

#[test]
fn test_component_resolver_returns_none_falls_back_gracefully() {
    // GIVEN: Resolver that returns None
    let mut engine = Engine::new();
    engine.set_component_resolver(|_name, _context| None);

    // WHEN: Expand unknown component
    let element = Element::new("UnknownComponent");
    let expanded = engine.component_registry_mut().expand(&element);

    // THEN: Element returned as-is (no panic)
    assert_element_type(&expanded, "UnknownComponent");
}

#[test]
fn test_multiple_component_registrations() {
    // GIVEN: Engine
    let mut engine = Engine::new();

    // WHEN: Register 10 different components
    for i in 0..10 {
        let name = format!("Component{}", i);
        let content = format!("Content {}", i);
        let component = Component::new(name.clone(), move |_props| text_element(&content));
        engine.register_component(component);
    }

    // THEN: All can be expanded
    for i in 0..10 {
        let element = Element::new(format!("Component{}", i));
        let expanded = engine.component_registry_mut().expand(&element);
        assert_element_type(&expanded, "Text");
    }
}

// ========== C. Render Pipeline Tests (8 tests) ==========

#[test]
fn test_render_simple_text_element() {
    // GIVEN: Engine and Text element
    let mut engine = Engine::new();
    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render element
    let element = text_element("Hello");
    engine.render(&element);

    // THEN: Emits patches (Create + Insert)
    let captured = patches.lock().unwrap();
    assert_has_create(&captured);
    assert!(captured.len() >= 1);
}

#[test]
fn test_render_nested_column_with_children() {
    // GIVEN: Column { Text, Text }
    let mut engine = Engine::new();
    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render
    let element = column_with_children(vec![text_element("First"), text_element("Second")]);
    engine.render(&element);

    // THEN: Creates 3 nodes (Column + 2 Text)
    let captured = patches.lock().unwrap();
    assert_eq!(count_creates(&captured), 3);
}

#[test]
fn test_render_increments_revision() {
    // GIVEN: Engine with revision 0
    let mut engine = Engine::new();
    assert_eq!(engine.revision(), 0);

    // WHEN: Render element
    let element = text_element("Hello");
    engine.render(&element);

    // THEN: Revision increments to 1
    assert_eq!(engine.revision(), 1);
}

#[test]
fn test_render_callback_receives_patches() {
    // GIVEN: Engine with callback
    let mut engine = Engine::new();
    let invoked = Arc::new(Mutex::new(false));
    let invoked_clone = invoked.clone();
    engine.set_render_callback(move |patches| {
        *invoked_clone.lock().unwrap() = !patches.is_empty();
    });

    // WHEN: Render element
    let element = text_element("Hello");
    engine.render(&element);

    // THEN: Callback was invoked with patches
    assert!(*invoked.lock().unwrap());
}

#[test]
fn test_render_same_tree_twice_minimal_patches() {
    // GIVEN: Engine with rendered tree
    let mut engine = Engine::new();
    let element = text_element("Hello");
    engine.render(&element);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render identical tree again
    engine.render(&element);

    // THEN: Minimal patches (reconciliation detects no changes)
    let captured = patches.lock().unwrap();
    assert_no_changes(&captured);
}

#[test]
fn test_render_with_state_bindings() {
    // GIVEN: Element with ${state.name} binding and module with state
    let mut engine = Engine::new();
    let module_meta = Module::new("TestModule");
    let module = ModuleInstance::new(module_meta, json!({"name": "Alice"}));
    engine.set_module(module);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render with state binding
    let element = text_element_with_binding("name");
    engine.render(&element);

    // THEN: Binding resolved to "Alice" in Create patch
    let captured = patches.lock().unwrap();
    let has_alice = captured.iter().any(|p| {
        if let hypen_engine::reconcile::Patch::Create { props, .. } = p {
            props
                .get("text")
                .map(|v| v == &json!("Alice"))
                .unwrap_or(false)
        } else {
            false
        }
    });
    assert!(has_alice, "Expected Create patch with text='Alice'");
}

#[test]
fn test_render_empty_element_tree() {
    // GIVEN: Engine
    let mut engine = Engine::new();
    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render empty Column
    let element = column_with_children(vec![]);
    engine.render(&element);

    // THEN: No crash, creates Column node
    let captured = patches.lock().unwrap();
    assert!(captured.len() >= 1);
}

#[test]
fn test_render_multiple_times_increments_revision() {
    // GIVEN: Engine
    let mut engine = Engine::new();
    let element = text_element("Test");

    // WHEN: Render 5 times
    for _ in 0..5 {
        engine.render(&element);
    }

    // THEN: Revision is 5
    assert_eq!(engine.revision(), 5);
}

// ========== D. State Change Handling Tests (6 tests) ==========

#[test]
fn test_update_state_increments_revision() {
    // GIVEN: Rendered tree with state binding
    let mut engine = Engine::new();
    let module_meta = Module::new("TestModule");
    let module = ModuleInstance::new(module_meta, json!({"count": 0}));
    engine.set_module(module);

    let element = text_element_with_binding("count");
    engine.render(&element);
    assert_eq!(engine.revision(), 1);

    // WHEN: Update state
    engine.update_state(json!({"count": 1}));

    // THEN: Revision increments
    assert_eq!(engine.revision(), 2);
}

#[test]
fn test_update_state_with_nested_path() {
    // GIVEN: Binding to nested state
    let mut engine = Engine::new();
    let module_meta = Module::new("TestModule");
    let module = ModuleInstance::new(module_meta, json!({"user": {"name": "Alice"}}));
    engine.set_module(module);

    let element = text_element_with_binding("user.name");
    engine.render(&element);
    assert_eq!(engine.revision(), 1);

    // WHEN: Update user.name
    engine.update_state(json!({"user": {"name": "Bob"}}));

    // THEN: Revision increments (patches depend on dependency tracking)
    assert_eq!(engine.revision(), 2);
}

#[test]
fn test_update_state_with_no_dependents() {
    // GIVEN: Tree with no bindings to "foo"
    let mut engine = Engine::new();
    let element = text_element("Static");
    engine.render(&element);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Update unrelated state
    engine.update_state(json!({"foo": "bar"}));

    // THEN: No patches emitted
    let captured = patches.lock().unwrap();
    assert_eq!(captured.len(), 0);
}

#[test]
fn test_update_state_with_multiple_bindings() {
    // GIVEN: Tree with multiple state bindings
    let mut engine = Engine::new();
    let module_meta = Module::new("TestModule");
    let module = ModuleInstance::new(module_meta, json!({"a": "1", "b": "2"}));
    engine.set_module(module);

    let element = column_with_children(vec![
        text_element_with_binding("a"),
        text_element_with_binding("b"),
    ]);
    engine.render(&element);
    assert_eq!(engine.revision(), 1);

    // WHEN: Update both values
    engine.update_state(json!({"a": "10", "b": "20"}));

    // THEN: Revision increments (patch generation depends on dependency tracking)
    assert_eq!(engine.revision(), 2);
}

#[test]
fn test_update_state_with_empty_patch() {
    // GIVEN: Rendered tree
    let mut engine = Engine::new();
    let element = text_element("Test");
    engine.render(&element);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Update with empty state patch
    engine.update_state(json!({}));

    // THEN: No patches
    let captured = patches.lock().unwrap();
    assert_eq!(captured.len(), 0);
}

#[test]
fn test_multiple_state_updates_batch_correctly() {
    // GIVEN: Tree with bindings
    let mut engine = Engine::new();
    let module_meta = Module::new("TestModule");
    let module = ModuleInstance::new(module_meta, json!({"count": 0}));
    engine.set_module(module);

    let element = text_element_with_binding("count");
    engine.render(&element);

    // WHEN: Multiple updates
    engine.update_state(json!({"count": 1}));
    engine.update_state(json!({"count": 2}));
    engine.update_state(json!({"count": 3}));

    // THEN: Revision increments for each
    assert_eq!(engine.revision(), 4); // 1 initial + 3 updates
}

// ========== E. Action Dispatch Tests (5 tests) ==========

#[test]
fn test_dispatch_action_calls_registered_handler() {
    // GIVEN: Engine with action handler
    let mut engine = Engine::new();
    let called = Arc::new(Mutex::new(false));
    let called_clone = called.clone();
    engine.on_action("signIn", move |_action| {
        *called_clone.lock().unwrap() = true;
    });

    // WHEN: Dispatch action
    let action = Action::new("signIn");
    let result = engine.dispatch_action(action);

    // THEN: Handler was called
    assert!(result.is_ok());
    assert!(*called.lock().unwrap());
}

#[test]
fn test_dispatch_action_with_payload() {
    // GIVEN: Action handler expecting payload
    let mut engine = Engine::new();
    let received = Arc::new(Mutex::new(None));
    let received_clone = received.clone();
    engine.on_action("submit", move |action| {
        *received_clone.lock().unwrap() = action.payload.clone();
    });

    // WHEN: Dispatch with payload
    let action = Action::new("submit").with_payload(json!({"value": 42}));
    let _ = engine.dispatch_action(action);

    // THEN: Handler receives payload
    let payload = received.lock().unwrap();
    assert!(payload.is_some());
    assert_eq!(payload.as_ref().unwrap()["value"], 42);
}

#[test]
fn test_dispatch_action_without_handler_returns_error() {
    // GIVEN: Engine with no handlers
    let mut engine = Engine::new();

    // WHEN: Dispatch unknown action
    let action = Action::new("unknown");
    let result = engine.dispatch_action(action);

    // THEN: Returns error
    assert!(result.is_err());
}

#[test]
fn test_dispatch_multiple_actions_sequentially() {
    // GIVEN: Engine with multiple handlers
    let mut engine = Engine::new();
    let count = Arc::new(Mutex::new(0));

    for i in 1..=3 {
        let count_clone = count.clone();
        engine.on_action(format!("action{}", i), move |_| {
            *count_clone.lock().unwrap() += 1;
        });
    }

    // WHEN: Dispatch all actions
    for i in 1..=3 {
        let action = Action::new(format!("action{}", i));
        let _ = engine.dispatch_action(action);
    }

    // THEN: All handlers called
    assert_eq!(*count.lock().unwrap(), 3);
}

#[test]
fn test_action_handler_can_be_registered_after_render() {
    // GIVEN: Rendered tree
    let mut engine = Engine::new();
    let element = text_element("Button");
    engine.render(&element);

    let called = Arc::new(Mutex::new(false));
    let called_clone = called.clone();

    // WHEN: Register handler after render
    engine.on_action("click", move |_| {
        *called_clone.lock().unwrap() = true;
    });

    let action = Action::new("click");
    let _ = engine.dispatch_action(action);

    // THEN: Handler works
    assert!(*called.lock().unwrap());
}

// ========== F. Module Lifecycle Tests (4 tests) ==========

#[test]
fn test_set_module_registers_instance() {
    // GIVEN: Engine and module instance
    let mut engine = Engine::new();
    let module_meta = Module::new("ProfilePage");
    let module = ModuleInstance::new(module_meta, json!({}));

    // WHEN: Set module
    engine.set_module(module);

    // THEN: Can render with module state (no panic)
    let element = text_element("Test");
    engine.render(&element);
}

#[test]
fn test_render_with_module_state() {
    // GIVEN: Module with user state
    let mut engine = Engine::new();
    let module = user_module(); // From fixtures
    engine.set_module(module);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render with binding to user.name
    let element = text_element_with_binding("user.name");
    engine.render(&element);

    // THEN: Resolves to "Alice" (from user_module fixture) in Create patch
    let captured = patches.lock().unwrap();
    let has_alice = captured.iter().any(|p| {
        if let hypen_engine::reconcile::Patch::Create { props, .. } = p {
            props
                .get("text")
                .map(|v| v == &json!("Alice"))
                .unwrap_or(false)
        } else {
            false
        }
    });
    assert!(has_alice, "Expected Create patch with user.name='Alice'");
}

#[test]
fn test_module_state_update_increments_revision() {
    // GIVEN: Rendered module component
    let mut engine = Engine::new();
    let module_meta = Module::new("CounterModule");
    let module = ModuleInstance::new(module_meta, json!({"count": 0}));
    engine.set_module(module);

    let element = text_element_with_binding("count");
    engine.render(&element);
    assert_eq!(engine.revision(), 1);

    // WHEN: Update module state
    engine.update_state(json!({"count": 5}));

    // THEN: Revision increments
    // Note: Patches may not be emitted if dependency tracking isn't active
    assert_eq!(engine.revision(), 2);
}

#[test]
fn test_multiple_module_instances_replace_each_other() {
    // GIVEN: Engine with first module
    let mut engine = Engine::new();
    let module1 = ModuleInstance::new(Module::new("Module1"), json!({"value": "first"}));
    engine.set_module(module1);

    // WHEN: Set second module
    let module2 = ModuleInstance::new(Module::new("Module2"), json!({"value": "second"}));
    engine.set_module(module2);

    // THEN: Can render with new module (no panic)
    let element = text_element_with_binding("value");
    engine.render(&element);
}

// ========== G. Edge Cases Tests (2 tests) ==========

#[test]
fn test_render_very_deep_tree() {
    // GIVEN: Deeply nested tree (50 levels)
    let mut engine = Engine::new();
    let element = deep_tree(50);

    // WHEN: Render
    engine.render(&element);

    // THEN: No stack overflow, completes successfully
    assert_eq!(engine.revision(), 1);
}

#[test]
fn test_render_tree_with_many_children() {
    // GIVEN: Column with 100 Text children
    let mut engine = Engine::new();
    let element = wide_tree(100);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render
    engine.render(&element);

    // THEN: Completes successfully, creates 101 nodes (1 Column + 100 Text)
    let captured = patches.lock().unwrap();
    assert_eq!(count_creates(&captured), 101);
}
