"""String lifetime test - verify scheduler handles string destruction correctly."""

import asyncio
from pathlib import Path
import re

import pytest

from .types import APIClientConnectedFactory, RunCompiledFunction


@pytest.mark.asyncio
async def test_scheduler_string_lifetime(
    yaml_config: str,
    run_compiled: RunCompiledFunction,
    api_client_connected: APIClientConnectedFactory,
) -> None:
    """Test that scheduler correctly handles string lifetimes when strings go out of scope."""

    # Get the absolute path to the external components directory
    external_components_path = str(
        Path(__file__).parent / "fixtures" / "external_components"
    )

    # Replace the placeholder in the YAML config with the actual path
    yaml_config = yaml_config.replace(
        "EXTERNAL_COMPONENT_PATH", external_components_path
    )

    # Create events for synchronization
    test1_complete = asyncio.Event()
    test2_complete = asyncio.Event()
    test3_complete = asyncio.Event()
    test4_complete = asyncio.Event()
    test5_complete = asyncio.Event()
    all_tests_complete = asyncio.Event()

    # Track test progress
    test_stats = {
        "tests_passed": 0,
        "tests_failed": 0,
        "errors": [],
        "current_test": None,
        "test_callbacks_executed": {},
    }

    def on_log_line(line: str) -> None:
        # Track test-specific events
        if "Test 1 complete" in line:
            test1_complete.set()
        elif "Test 2 complete" in line:
            test2_complete.set()
        elif "Test 3 complete" in line:
            test3_complete.set()
        elif "Test 4 complete" in line:
            test4_complete.set()
        elif "Test 5 complete" in line:
            test5_complete.set()

        # Track individual callback executions
        callback_match = re.search(r"Callback '(.+?)' executed", line)
        if callback_match:
            callback_name = callback_match.group(1)
            test_stats["test_callbacks_executed"][callback_name] = True

        # Track test results from the C++ test output
        if "Tests passed:" in line and "string_lifetime" in line:
            # Extract the number from "Tests passed: 32"
            match = re.search(r"Tests passed:\s*(\d+)", line)
            if match:
                test_stats["tests_passed"] = int(match.group(1))
        elif "Tests failed:" in line and "string_lifetime" in line:
            match = re.search(r"Tests failed:\s*(\d+)", line)
            if match:
                test_stats["tests_failed"] = int(match.group(1))
        elif "ERROR" in line and "string_lifetime" in line:
            test_stats["errors"].append(line)

        # Check for memory corruption indicators
        if any(
            indicator in line.lower()
            for indicator in [
                "use after free",
                "heap corruption",
                "segfault",
                "abort",
                "assertion",
                "sanitizer",
                "bad memory",
                "invalid pointer",
            ]
        ):
            pytest.fail(f"Memory corruption detected: {line}")

        # Check for completion
        if "String lifetime tests complete" in line:
            all_tests_complete.set()

    async with (
        run_compiled(yaml_config, line_callback=on_log_line),
        api_client_connected() as client,
    ):
        # Verify we can connect
        device_info = await client.device_info()
        assert device_info is not None
        assert device_info.name == "scheduler-string-lifetime-test"

        # List entities and services
        _, services = await asyncio.wait_for(
            client.list_entities_services(), timeout=5.0
        )

        # Find our test services
        test_services = {}
        for service in services:
            if service.name == "run_test1":
                test_services["test1"] = service
            elif service.name == "run_test2":
                test_services["test2"] = service
            elif service.name == "run_test3":
                test_services["test3"] = service
            elif service.name == "run_test4":
                test_services["test4"] = service
            elif service.name == "run_test5":
                test_services["test5"] = service
            elif service.name == "run_final_check":
                test_services["final"] = service

        # Ensure all services are found
        required_services = ["test1", "test2", "test3", "test4", "test5", "final"]
        for service_name in required_services:
            assert service_name in test_services, f"{service_name} service not found"

        # Run tests sequentially, waiting for each to complete
        try:
            # Test 1
            client.execute_service(test_services["test1"], {})
            await asyncio.wait_for(test1_complete.wait(), timeout=5.0)

            # Test 2
            client.execute_service(test_services["test2"], {})
            await asyncio.wait_for(test2_complete.wait(), timeout=5.0)

            # Test 3
            client.execute_service(test_services["test3"], {})
            await asyncio.wait_for(test3_complete.wait(), timeout=5.0)

            # Test 4
            client.execute_service(test_services["test4"], {})
            await asyncio.wait_for(test4_complete.wait(), timeout=5.0)

            # Test 5
            client.execute_service(test_services["test5"], {})
            await asyncio.wait_for(test5_complete.wait(), timeout=5.0)

            # Final check
            client.execute_service(test_services["final"], {})
            await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0)

        except asyncio.TimeoutError:
            pytest.fail(f"String lifetime test timed out. Stats: {test_stats}")

        # Check for any errors
        assert test_stats["tests_failed"] == 0, f"Tests failed: {test_stats['errors']}"

        # Verify we had the expected number of passing tests
        assert test_stats["tests_passed"] == 30, (
            f"Expected exactly 30 tests to pass, but got {test_stats['tests_passed']}"
        )
