"""
Tests for ACT Kernel.
"""

import pytest
import time
import threading
import concurrent.futures
from act import ACTKernel, ToolError, ToolUnavailableError, TimeoutError


def success_handler(params):
    """Handler that always succeeds."""
    return {"result": "ok", "params": params}


def timeout_handler(params):
    """Handler that always times out."""
    time.sleep(10)
    return {}


def flaky_handler(params):
    """Handler that fails first 2 times, then succeeds."""
    if not hasattr(flaky_handler, "call_count"):
        flaky_handler.call_count = 0
    flaky_handler.call_count += 1
    if flaky_handler.call_count <= 2:
        raise ToolUnavailableError("temporary failure")
    return {"success": True}


def always_fail_handler(params):
    """Handler that always fails."""
    raise ToolError("always fail")


def slow_handler(params):
    """Handler with configurable delay."""
    delay = params.get("delay", 0.1)
    time.sleep(delay)
    return {"done": True}


@pytest.fixture
def kernel():
    """Fixture providing a configured ACTKernel."""
    k = ACTKernel(default_timeout_ms=1000, default_retries=1)
    k.register_tool("test", "success", success_handler)
    k.register_tool("test", "timeout", timeout_handler, metadata={"timeout_ms": 500, "retries": 0})
    k.register_tool("test", "flaky", flaky_handler, metadata={"retries": 2})
    k.register_tool("test", "fail", always_fail_handler)
    k.register_tool("test", "slow", slow_handler, metadata={"timeout_ms": 200})
    k.add_route("success", "test", "success")
    k.add_route("timeout", "test", "timeout")
    k.add_route("flaky", "test", "flaky")
    k.add_route("fail", "test", "fail")
    k.add_route("slow", "test", "slow")
    k.quota_manager.set_limit("test", "slow", rate=2, capacity=2)
    return k


def test_normal_success(kernel):
    """Test normal successful execution."""
    result = kernel.dispatch("success", {"key": "value"})
    assert result["execution"]["status"] == "ok"
    assert result["payload"]["result"] == "ok"
    assert result["error"] is None
    assert result["execution"]["retries"] == 0
    assert result["execution"]["quota_consumed"] == 1


def test_route_not_found(kernel):
    """Test routing failure."""
    result = kernel.dispatch("unknown_intent")
    assert result["execution"]["status"] == "error"
    assert result["error"]["code"] == "routing.not_found"


def test_timeout(kernel):
    """Test timeout handling."""
    result = kernel.dispatch("timeout")
    assert result["execution"]["status"] == "timeout"
    assert result["error"]["code"] == "execution.timeout"


def test_retry_exhausted(kernel):
    """Test retry exhaustion."""
    kernel.register_tool(
        "test", "retry_timeout", timeout_handler, metadata={"timeout_ms": 500, "retries": 2}
    )
    kernel.add_route("retry_timeout", "test", "retry_timeout")
    result = kernel.dispatch("retry_timeout")
    assert result["execution"]["status"] == "error"
    assert result["error"]["code"] == "execution.retry_exhausted"


def test_circuit_breaker(kernel):
    """Test circuit breaker functionality."""
    tool_key = ("test", "fail")
    breaker = kernel.circuit_breakers[tool_key]
    breaker.failure_threshold = 2
    breaker._failure_count = 0

    result1 = kernel.dispatch("fail")
    assert result1["execution"]["status"] == "error"

    result2 = kernel.dispatch("fail")
    assert result2["execution"]["status"] == "error"

    result3 = kernel.dispatch("fail")
    assert result3["execution"]["status"] == "rejected"
    assert result3["error"]["code"] == "execution.circuit_open"


def test_rate_limit(kernel):
    """Test rate limiting."""
    results = []
    for _ in range(5):
        results.append(kernel.dispatch("slow", {"delay": 0.01}))
        time.sleep(0.01)

    statuses = [r["execution"]["status"] for r in results]
    assert statuses.count("ok") <= 2
    assert statuses.count("rejected") >= 3


def test_concurrent(kernel):
    """Test concurrent execution."""
    kernel.quota_manager.set_limit("test", "slow", rate=10, capacity=10)
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        futures = [
            executor.submit(kernel.dispatch, "slow", {"delay": 0.01}) for _ in range(100)
        ]
        results = [f.result() for f in futures]

    ok_count = sum(1 for r in results if r["execution"]["status"] == "ok")
    assert ok_count <= 15


def test_resource_leak(kernel):
    """Test for resource leaks."""
    initial_threads = threading.active_count()
    for _ in range(100):
        kernel.dispatch("success")
        kernel.dispatch("timeout")
    time.sleep(1)
    final_threads = threading.active_count()
    assert final_threads - initial_threads < 20
