"""End-to-end tests for Soni Framework"""

from pathlib import Path

import pytest

from soni.core.config import ConfigLoader, SoniConfig
from soni.core.errors import ValidationError
from soni.runtime import RuntimeLoop


@pytest.fixture
def config_path():
    """Path to flight booking example configuration"""
    return Path("examples/flight_booking/soni.yaml")


@pytest.fixture
def runtime(config_path):
    """Create RuntimeLoop instance for testing"""
    return RuntimeLoop(config_path)


@pytest.mark.asyncio
@pytest.mark.skip(
    reason="Requires AsyncSqliteSaver for full async support. "
    "SqliteSaver doesn't support async methods. "
    "This will be fixed in Hito 10."
)
async def test_e2e_flight_booking_complete_flow(runtime):
    """
    Test complete flight booking flow end-to-end.

    This test validates:
    1. User triggers booking intent
    2. System collects origin, destination, date
    3. System searches for flights
    4. System confirms booking
    5. System returns booking reference
    """
    # Arrange
    user_id = "test-user-e2e-1"

    # Act & Assert - Step 1: Trigger booking
    response1 = await runtime.process_message("I want to book a flight", user_id)
    assert isinstance(response1, str)
    assert len(response1) > 0
    # Should ask for origin
    assert "origin" in response1.lower() or "from" in response1.lower()

    # Act & Assert - Step 2: Provide origin
    response2 = await runtime.process_message("New York", user_id)
    assert isinstance(response2, str)
    # Should ask for destination
    assert "destination" in response2.lower() or "to" in response2.lower()

    # Act & Assert - Step 3: Provide destination
    response3 = await runtime.process_message("Los Angeles", user_id)
    assert isinstance(response3, str)
    # Should ask for date
    assert "date" in response3.lower() or "when" in response3.lower()

    # Act & Assert - Step 4: Provide date
    response4 = await runtime.process_message("Next Friday", user_id)
    assert isinstance(response4, str)
    # Should show flights or confirm booking
    assert "flight" in response4.lower() or "booking" in response4.lower()

    # Act & Assert - Step 5: Final response should have booking reference
    # (If booking is confirmed in same turn)
    if "booking" in response4.lower() and "reference" in response4.lower():
        assert "BK-" in response4 or "booking" in response4.lower()


@pytest.mark.asyncio
@pytest.mark.skip(
    reason="Requires AsyncSqliteSaver for full async support. "
    "SqliteSaver doesn't support async methods. "
    "This will be fixed in Hito 10."
)
async def test_e2e_state_persistence(runtime):
    """
    Test that state persists between turns.

    This test validates:
    1. State is maintained across multiple messages
    2. Slots are collected and remembered
    3. Flow progression is tracked
    """
    # Arrange
    user_id = "test-user-e2e-2"

    # Act - Start conversation
    response1 = await runtime.process_message("I want to book a flight", user_id)

    # Act - Provide origin
    response2 = await runtime.process_message("Paris", user_id)

    # Act - Provide destination
    response3 = await runtime.process_message("London", user_id)

    # Assert - System should remember origin when asking for destination
    # (This is implicit in the flow, but we can verify by checking responses)
    assert isinstance(response1, str)
    assert isinstance(response2, str)
    assert isinstance(response3, str)

    # Act - Try to continue conversation (system should remember context)
    response4 = await runtime.process_message("Tomorrow", user_id)

    # Assert - Final response should reference all collected information
    assert isinstance(response4, str)
    # Should mention both cities or booking details
    assert len(response4) > 0


@pytest.mark.asyncio
@pytest.mark.skip(
    reason="Requires AsyncSqliteSaver for full async support. "
    "SqliteSaver doesn't support async methods. "
    "This will be fixed in Hito 10."
)
async def test_e2e_multiple_conversations(runtime):
    """
    Test that multiple conversations are handled independently.

    This test validates:
    1. Each user has independent state
    2. Conversations don't interfere with each other
    """
    # Arrange
    user_id_1 = "test-user-e2e-3"
    user_id_2 = "test-user-e2e-4"

    # Act - Start conversation for user 1
    response1_user1 = await runtime.process_message("I want to book a flight", user_id_1)

    # Act - Start conversation for user 2
    response1_user2 = await runtime.process_message("I want to book a flight", user_id_2)

    # Assert - Both should get responses
    assert isinstance(response1_user1, str)
    assert isinstance(response1_user2, str)

    # Act - Continue user 1 conversation
    response2_user1 = await runtime.process_message("Tokyo", user_id_1)

    # Act - Continue user 2 conversation
    response2_user2 = await runtime.process_message("Berlin", user_id_2)

    # Assert - Both conversations should progress independently
    assert isinstance(response2_user1, str)
    assert isinstance(response2_user2, str)
    # Responses should be different (different cities)
    assert response2_user1 != response2_user2


@pytest.mark.asyncio
@pytest.mark.skip(
    reason="Requires AsyncSqliteSaver for full async support. "
    "SqliteSaver doesn't support async methods. "
    "This will be fixed in Hito 10."
)
async def test_e2e_error_handling(runtime):
    """
    Test error handling in E2E flow.

    This test validates:
    1. Empty messages are rejected
    2. Invalid inputs are handled gracefully
    3. System recovers from errors
    """
    # Arrange
    user_id = "test-user-e2e-5"

    # Act & Assert - Empty message should raise error
    with pytest.raises(ValidationError):
        await runtime.process_message("", user_id)

    # Act & Assert - Valid message should work
    response = await runtime.process_message("I want to book a flight", user_id)
    assert isinstance(response, str)

    # Act & Assert - System should continue after error
    response2 = await runtime.process_message("New York", user_id)
    assert isinstance(response2, str)


@pytest.mark.asyncio
async def test_e2e_configuration_loading():
    """
    Test that example configuration loads correctly.

    This test validates:
    1. Configuration file is valid
    2. All required components are present
    3. Configuration can be used to create runtime
    """
    # Arrange
    config_path = Path("examples/flight_booking/soni.yaml")

    # Act - Load configuration
    config_dict = ConfigLoader.load(config_path)
    config = SoniConfig(**config_dict)

    # Assert - Configuration is valid
    assert config.version == "0.1"
    assert "book_flight" in config.flows
    assert len(config.slots) > 0
    assert len(config.actions) > 0

    # Act - Create runtime with config
    runtime = RuntimeLoop(config_path)

    # Assert - Runtime is initialized
    assert runtime.config is not None
    assert runtime.graph is not None
    assert runtime.du is not None
