from __future__ import annotations

import asyncio
from pathlib import Path
from types import SimpleNamespace

from openclaw_lite.core.agent import AgentLoop
from openclaw_lite.core.session_store import SessionStore
from openclaw_lite.core.tool_registry import ToolRegistry
from openclaw_lite.providers.base import ProviderError
from openclaw_lite.schemas import TELEGRAM_ACTIVE_USER_ID_METADATA_KEY, ToolDefinition


class DummyProviderRegistry:
    def __init__(self, provider):
        self.provider = provider

    def get(self, name: str):
        return self.provider


class DummySkillManager:
    def get_system_prompt_fragments(self):
        return []


class DummyProvider:
    def __init__(self, responses):
        self._responses = list(responses)
        self.calls = 0

    async def generate(self, **kwargs):
        response = self._responses[self.calls]
        self.calls += 1
        if isinstance(response, Exception):
            raise response
        return response


class DummyRuntime:
    def __init__(self, tmp_path: Path, provider: DummyProvider, tools: list[ToolDefinition]):
        self.config = {
            "system_prompt": "test agent mode",
            "project_root": str(tmp_path),
            "workspace_dir": str(tmp_path / "workspace"),
            "tool_loop_limit": 0,
            "provider_retry_attempts": 3,
            "provider_retry_base_delay_sec": 0,
        }
        self.session_store = SessionStore(
            tmp_path / "data",
            default_provider="demo",
            default_model="demo-model",
            history_limit=0,
        )
        self.provider_registry = DummyProviderRegistry(provider)
        self.skill_manager = DummySkillManager()
        self.tool_registry = ToolRegistry(tmp_path / "tool_audit.log")
        self.tool_registry.register_many(tools)

    def sync_skill_tools(self):
        return None

    def list_provider_names(self):
        return ["demo"]

    def get_session(self, *, session_id: str, chat_id: str, user_id: int | None):
        return self.session_store.get_or_create(session_id, chat_id=chat_id, user_id=user_id)

    def max_agent_steps(self):
        return None

    def auth_is_allowed(self, **kwargs):
        return True


async def _run_turn(runtime: DummyRuntime, *, transport_state=None):
    loop = AgentLoop(runtime)
    return await loop.run_turn(
        session_id="telegram:1:main",
        chat_id="1",
        user_id=1,
        text="fix it",
        transport="telegram",
        transport_state=transport_state or {},
    )


def test_agent_mode_recovers_after_tool_error(tmp_path: Path) -> None:
    async def broken_tool(context, arguments):
        raise RuntimeError("tool exploded")

    provider = DummyProvider(
        [
            {
                "text": "trying a tool",
                "tool_calls": [{"id": "1", "name": "broken_tool", "arguments": {}}],
                "usage": {},
            },
            {
                "text": "resolved after tool error",
                "tool_calls": [],
                "usage": {},
            },
        ]
    )
    runtime = DummyRuntime(
        tmp_path,
        provider,
        [
            ToolDefinition(
                name="broken_tool",
                description="always fails",
                parameters={"type": "object", "properties": {}},
                handler=broken_tool,
                owner_only=True,
            )
        ],
    )

    result = asyncio.run(_run_turn(runtime))

    assert result["text"] == "resolved after tool error"
    assert result["iterations"] == 2
    assert provider.calls == 2


def test_agent_mode_emits_progress_events(tmp_path: Path) -> None:
    provider = DummyProvider(
        [
            {
                "text": "done",
                "tool_calls": [],
                "usage": {},
            }
        ]
    )
    runtime = DummyRuntime(tmp_path, provider, [])
    events: list[str] = []

    def callback(payload):
        events.append(payload["event"])

    result = asyncio.run(_run_turn(runtime, transport_state={"progress_callback": callback}))

    assert result["text"] == "done"
    assert events == ["agent_started", "agent_iteration_started", "assistant_response", "agent_finished"]


def test_open_telegram_agent_turn_marks_session_active(tmp_path: Path) -> None:
    provider = DummyProvider(
        [
            {
                "text": "done",
                "tool_calls": [],
                "usage": {},
            }
        ]
    )
    runtime = DummyRuntime(tmp_path, provider, [])

    result = asyncio.run(_run_turn(runtime))
    session = runtime.get_session(session_id="telegram:1:main", chat_id="1", user_id=1)

    assert result["session_closed"] is False
    assert session.metadata[TELEGRAM_ACTIVE_USER_ID_METADATA_KEY] == 1


def test_session_end_tool_closes_session(tmp_path: Path) -> None:
    async def end_tool(context, arguments):
        return {"closed": True}

    provider = DummyProvider(
        [
            {
                "text": "closing session now",
                "tool_calls": [{"id": "1", "name": "session_end", "arguments": {}}],
                "usage": {},
            }
        ]
    )
    runtime = DummyRuntime(
        tmp_path,
        provider,
        [
            ToolDefinition(
                name="session_end",
                description="close session",
                parameters={"type": "object", "properties": {}},
                handler=end_tool,
                owner_only=True,
            )
        ],
    )

    result = asyncio.run(_run_turn(runtime))
    session = runtime.get_session(session_id="telegram:1:main", chat_id="1", user_id=1)

    assert result["session_closed"] is True
    assert session.closed is True
    assert TELEGRAM_ACTIVE_USER_ID_METADATA_KEY not in session.metadata


def test_agent_mode_retries_transient_provider_error_and_recovers(tmp_path: Path) -> None:
    provider = DummyProvider(
        [
            ProviderError(
                "Network error for https://api.openai.com/v1/chat/completions: [Errno -3] Temporary failure in name resolution"
            ),
            {
                "text": "done after retry",
                "tool_calls": [],
                "usage": {},
            },
        ]
    )
    runtime = DummyRuntime(tmp_path, provider, [])
    events: list[str] = []

    def callback(payload):
        events.append(payload["event"])

    result = asyncio.run(_run_turn(runtime, transport_state={"progress_callback": callback}))

    assert result["text"] == "done after retry"
    assert provider.calls == 2
    assert "provider_retry_scheduled" in events


def test_agent_mode_does_not_retry_non_transient_provider_error(tmp_path: Path) -> None:
    provider = DummyProvider(
        [
            ProviderError(
                "openai requires an API key. Configure it via Telegram /settings or /apikey <provider> <key>."
            )
        ]
    )
    runtime = DummyRuntime(tmp_path, provider, [])

    result = asyncio.run(_run_turn(runtime))

    assert provider.calls == 1
    assert "Провайдер `demo` не готов" in result["text"]
