"""
Test script for the NavigationStorage refactoring (updated for new architecture)
"""

import asyncio
from unittest.mock import MagicMock

from aiogram import Bot

from ui_router import (
    InMemoryNavigationStorage,
    NavigationState,
)


async def test_in_memory_storage():
    """Test InMemoryNavigationStorage implementation"""
    print("=" * 60)
    print("TEST 1: InMemoryNavigationStorage")
    print("=" * 60)

    storage = InMemoryNavigationStorage()

    # Test save and get
    state = NavigationState(
        current_scene="main",
        history=["start"],
        transitions=["send"],
    )

    await storage.save_state("bot1", 123, 123, state)
    loaded = await storage.get_state("bot1", 123, 123)

    assert loaded is not None, "State should be loaded"
    assert loaded.current_scene == "main", f"Expected 'main', got {loaded.current_scene}"
    assert loaded.history == ["start"], f"Expected ['start'], got {loaded.history}"
    print("[OK] Save and get state works")

    # Test isolation by user_id
    state2 = NavigationState(current_scene="settings")
    await storage.save_state("bot1", 456, 456, state2)

    loaded1 = await storage.get_state("bot1", 123, 123)
    loaded2 = await storage.get_state("bot1", 456, 456)

    assert loaded1.current_scene == "main", "User 123 should have main"
    assert loaded2.current_scene == "settings", "User 456 should have settings"
    print("[OK] User isolation works")

    # Test delete
    await storage.delete_state("bot1", 123, 123)
    loaded = await storage.get_state("bot1", 123, 123)
    assert loaded is None, "State should be deleted"
    print("[OK] Delete state works")

    print()


async def test_navigation_manager():
    """Test NavigationManager with new API"""
    print("=" * 60)
    print("TEST 2: NavigationManager with NavigationStorage")
    print("=" * 60)

    from ui_router.state.context import NavigationManager

    storage = InMemoryNavigationStorage()
    manager = NavigationManager(navigation_storage=storage, max_history_depth=10)

    bot_id = 123456

    # Test initialize
    state = await manager.initialize(bot_id=bot_id, user_id=123, chat_id=123, initial_scene="start")
    assert state.current_scene == "start", "Should initialize to start scene"
    print("[OK] Initialize works")

    # Test navigate_to
    state = await manager.navigate_to(bot_id, 123, 123, "main", add_to_history=True, transition="send")
    assert state.current_scene == "main", "Should navigate to main"
    assert "start" in state.history, "Should add start to history"
    print("[OK] Navigate to works")

    # Test go_back
    state, scene, transition = await manager.go_back(bot_id, 123, 123)
    assert scene == "start", f"Should go back to start, got {scene}"
    assert state.current_scene == "start", "Current scene should be start"
    print("[OK] Go back works")

    print()


async def test_multi_bot_isolation():
    """Test that multiple bots don't conflict"""
    print("=" * 60)
    print("TEST 3: Multi-bot isolation")
    print("=" * 60)

    from ui_router.state.context import NavigationManager

    # Same storage for both managers
    storage = InMemoryNavigationStorage()

    # Single manager for all bots
    manager = NavigationManager(navigation_storage=storage, max_history_depth=10)

    bot1_id = 111
    bot2_id = 222

    # User 123 uses both bots
    await manager.initialize(bot1_id, 123, 123, "bot1_start")
    await manager.initialize(bot2_id, 123, 123, "bot2_start")

    # Navigate in bot1
    await manager.navigate_to(bot1_id, 123, 123, "bot1_main")

    # Navigate in bot2
    await manager.navigate_to(bot2_id, 123, 123, "bot2_main")

    # Check isolation
    state1 = await manager.get_state(bot1_id, 123, 123)
    state2 = await manager.get_state(bot2_id, 123, 123)

    assert state1.current_scene == "bot1_main", f"Bot1 should be at bot1_main, got {state1.current_scene}"
    assert state2.current_scene == "bot2_main", f"Bot2 should be at bot2_main, got {state2.current_scene}"
    print("[OK] Multi-bot isolation works - each bot has independent state")

    print()


async def test_execution_context():
    """Test ExecutionContext with new API"""
    print("=" * 60)
    print("TEST 4: ExecutionContext")
    print("=" * 60)

    from ui_router.state.context import ExecutionContext, NavigationManager

    storage = InMemoryNavigationStorage()
    manager = NavigationManager(navigation_storage=storage, max_history_depth=10)

    bot_id = 123456

    # Mock bot
    bot = MagicMock(spec=Bot)
    bot.id = bot_id

    # Initialize state
    nav_state = await manager.initialize(bot_id, 123, 123, "test_scene")

    # Create context
    context = ExecutionContext(
        navigation_manager=manager,
        navigation_state=nav_state,
        scene_id="test_scene",
        bot=bot,
        user_id=123,
        chat_id=123,
        event_data={"test": "data"},
    )

    assert context.user_id == 123, "User ID should be set"
    assert context.chat_id == 123, "Chat ID should be set"
    assert context.scene_id == "test_scene", "Scene ID should be set"
    print("[OK] ExecutionContext created with new API")

    # Test save_to_storage
    context.navigation_state.current_scene = "updated_scene"
    await context.save_to_storage()

    # Verify saved
    loaded = await manager.get_state(bot_id, 123, 123)
    assert loaded.current_scene == "updated_scene", "Should save to storage"
    print("[OK] save_to_storage works")

    print()


async def test_prefix_isolation():
    """Test that prefix provides router isolation"""
    print("=" * 60)
    print("TEST 5: Prefix isolation for multiple routers")
    print("=" * 60)

    # Create two separate storages with different prefixes
    storage_router_a = InMemoryNavigationStorage(prefix="router_a")
    storage_router_b = InMemoryNavigationStorage(prefix="router_b")

    bot_id = 123
    user_id = 456
    chat_id = 456

    # Create different states for each router
    state_a = NavigationState(
        current_scene="scene_a",
        history=["start_a"],
        transitions=["send"],
    )

    state_b = NavigationState(
        current_scene="scene_b",
        history=["start_b"],
        transitions=["send"],
    )

    # Save states in isolated storages
    await storage_router_a.save_state(bot_id, user_id, chat_id, state_a)
    await storage_router_b.save_state(bot_id, user_id, chat_id, state_b)

    # Verify isolation
    loaded_a = await storage_router_a.get_state(bot_id, user_id, chat_id)
    loaded_b = await storage_router_b.get_state(bot_id, user_id, chat_id)

    assert loaded_a is not None, "Router A state should exist"
    assert loaded_b is not None, "Router B state should exist"
    assert loaded_a.current_scene == "scene_a", f"Router A should have scene_a, got {loaded_a.current_scene}"
    assert loaded_b.current_scene == "scene_b", f"Router B should have scene_b, got {loaded_b.current_scene}"
    print("[OK] Prefix isolation works - each router has independent state")

    print()


async def test_prefix_same_storage_instance():
    """Test prefix isolation using single storage instance with runtime prefix"""
    print("=" * 60)
    print("TEST 6: Prefix isolation with single storage instance")
    print("=" * 60)

    # Single storage instance can serve multiple routers with different prefix in runtime
    storage = InMemoryNavigationStorage()

    bot_id = 111
    user_id = 222
    chat_id = 222

    state_a = NavigationState(current_scene="router_a_scene")
    state_b = NavigationState(current_scene="router_b_scene")

    # Save with explicit prefix parameter
    await storage.save_state(bot_id, user_id, chat_id, state_a, prefix="router_a")
    await storage.save_state(bot_id, user_id, chat_id, state_b, prefix="router_b")

    # Load with explicit prefix parameter
    loaded_a = await storage.get_state(bot_id, user_id, chat_id, prefix="router_a")
    loaded_b = await storage.get_state(bot_id, user_id, chat_id, prefix="router_b")

    assert loaded_a is not None, "Router A state should exist"
    assert loaded_b is not None, "Router B state should exist"
    assert loaded_a.current_scene == "router_a_scene", f"Expected router_a_scene, got {loaded_a.current_scene}"
    assert loaded_b.current_scene == "router_b_scene", f"Expected router_b_scene, got {loaded_b.current_scene}"
    print("[OK] Runtime prefix isolation works with single storage instance")

    print()


async def test_backward_compatibility_no_prefix():
    """Test backward compatibility: storage without prefix works as before"""
    print("=" * 60)
    print("TEST 7: Backward compatibility (no prefix)")
    print("=" * 60)

    storage = InMemoryNavigationStorage()  # No prefix

    bot_id = 999
    user_id = 888
    chat_id = 888

    state = NavigationState(current_scene="default_scene")

    # Use storage without prefix (should work as before)
    await storage.save_state(bot_id, user_id, chat_id, state)
    loaded = await storage.get_state(bot_id, user_id, chat_id)

    assert loaded is not None, "State should be saved and loaded without prefix"
    assert loaded.current_scene == "default_scene", f"Expected default_scene, got {loaded.current_scene}"
    print("[OK] Backward compatibility maintained - no prefix works as before")

    print()


async def test_prefix_delete_isolation():
    """Test that delete respects prefix isolation"""
    print("=" * 60)
    print("TEST 8: Prefix isolation for delete operation")
    print("=" * 60)

    storage = InMemoryNavigationStorage()

    bot_id = 111
    user_id = 222
    chat_id = 222

    state_a = NavigationState(current_scene="scene_a")
    state_b = NavigationState(current_scene="scene_b")

    # Save with different prefixes
    await storage.save_state(bot_id, user_id, chat_id, state_a, prefix="router_a")
    await storage.save_state(bot_id, user_id, chat_id, state_b, prefix="router_b")

    # Delete only router_a
    await storage.delete_state(bot_id, user_id, chat_id, prefix="router_a")

    # Verify router_a is deleted but router_b remains
    loaded_a = await storage.get_state(bot_id, user_id, chat_id, prefix="router_a")
    loaded_b = await storage.get_state(bot_id, user_id, chat_id, prefix="router_b")

    assert loaded_a is None, "Router A state should be deleted"
    assert loaded_b is not None, "Router B state should still exist"
    assert loaded_b.current_scene == "scene_b", f"Router B should have scene_b, got {loaded_b.current_scene}"
    print("[OK] Delete operation respects prefix isolation")

    print()


async def main():
    """Run all tests"""
    print("\n")
    print("TESTING NAVIGATION STORAGE REFACTORING")
    print("=" * 60)
    print()

    try:
        await test_in_memory_storage()
        await test_navigation_manager()
        await test_multi_bot_isolation()
        await test_execution_context()
        await test_prefix_isolation()
        await test_prefix_same_storage_instance()
        await test_backward_compatibility_no_prefix()
        await test_prefix_delete_isolation()

        print("=" * 60)
        print("ALL TESTS PASSED!")
        print("=" * 60)
        print()

    except AssertionError as e:
        print()
        print("=" * 60)
        print(f"[FAIL] TEST FAILED: {e}")
        print("=" * 60)
        raise
    except Exception as e:
        print()
        print("=" * 60)
        print(f"[FAIL] ERROR: {e}")
        print("=" * 60)
        import traceback

        traceback.print_exc()
        raise


if __name__ == "__main__":
    asyncio.run(main())
