"""Integration tests for Agent lifecycle: start → execute task → return result → stop"""

import pytest
from datetime import datetime, timezone
from unittest.mock import AsyncMock

from agentkit.core.base import BaseAgent
from agentkit.core.config_driven import AgentConfig, ConfigDrivenAgent
from agentkit.core.protocol import (
    AgentCapability,
    AgentStatus,
    TaskMessage,
    TaskResult,
    TaskStatus,
)
from agentkit.memory.base import Memory, MemoryItem
from agentkit.tools.function_tool import FunctionTool


# ── Helpers ────────────────────────────────────────────────


class InMemoryMemory(Memory):
    """In-memory Memory implementation for testing without Redis/PG."""

    def __init__(self):
        self._store: dict[str, MemoryItem] = {}

    async def store(self, key: str, value, metadata=None) -> None:
        self._store[key] = MemoryItem(
            key=key, value=value, metadata=metadata or {}, created_at=datetime.now(timezone.utc)
        )

    async def retrieve(self, key: str) -> MemoryItem | None:
        return self._store.get(key)

    async def search(self, query: str, top_k: int = 5, filters=None) -> list[MemoryItem]:
        results = []
        for item in self._store.values():
            if query.lower() in str(item.value).lower() or query.lower() in item.key.lower():
                results.append(item)
        return results[:top_k]

    async def delete(self, key: str) -> bool:
        if key in self._store:
            del self._store[key]
            return True
        return False


class TrackingAgent(BaseAgent):
    """Agent that records lifecycle hook calls for testing."""

    def __init__(self, should_fail: bool = False):
        super().__init__(name="tracking_agent", agent_type="tracking")
        self.should_fail = should_fail
        self.hook_calls: list[str] = []

    def get_capabilities(self) -> AgentCapability:
        return AgentCapability(
            agent_name=self.name,
            agent_type=self.agent_type,
            version=self.version,
            supported_tasks=["tracking"],
            max_concurrency=1,
            description="Tracking test agent",
        )

    async def on_task_start(self, task: TaskMessage) -> None:
        self.hook_calls.append("on_task_start")

    async def on_task_complete(self, task: TaskMessage, output: dict) -> None:
        self.hook_calls.append("on_task_complete")

    async def on_task_failed(self, task: TaskMessage, error: Exception) -> None:
        self.hook_calls.append("on_task_failed")

    async def handle_task(self, task: TaskMessage) -> dict:
        if self.should_fail:
            raise RuntimeError("Intentional failure for testing")
        return {"message": f"Handled task {task.task_id}"}


def _make_task(**overrides) -> TaskMessage:
    defaults = dict(
        task_id="task-001",
        agent_name="test_agent",
        task_type="test_task",
        priority=1,
        input_data={"query": "hello"},
        callback_url=None,
        created_at=datetime.now(timezone.utc),
    )
    defaults.update(overrides)
    return TaskMessage(**defaults)


# ── Tests ──────────────────────────────────────────────────


@pytest.mark.integration
async def test_config_driven_agent_lifecycle():
    """ConfigDrivenAgent from config → start → execute task → return TaskResult → stop."""
    config = AgentConfig(
        name="lifecycle_agent",
        agent_type="lifecycle_test",
        task_mode="llm_generate",
        description="Test lifecycle agent",
        prompt={
            "identity": "You are a test agent",
            "instructions": "Process the input",
            "output_format": "JSON",
        },
    )

    mock_llm = AsyncMock()
    mock_llm.chat = AsyncMock(return_value='{"result": "processed"}')

    agent = ConfigDrivenAgent(config=config, llm_client=mock_llm)

    # Start without Redis (local mode)
    await agent.start()
    assert agent.status == AgentStatus.ONLINE

    # Execute a task
    task = _make_task(agent_name="lifecycle_agent", task_type="lifecycle_test")
    result = await agent.execute(task)

    assert isinstance(result, TaskResult)
    assert result.task_id == "task-001"
    assert result.status == TaskStatus.COMPLETED
    assert result.output_data is not None
    assert result.error_message is None

    # Stop
    await agent.stop()
    assert agent.status == AgentStatus.OFFLINE


@pytest.mark.integration
async def test_lifecycle_hooks_called_in_order():
    """BaseAgent lifecycle hooks called in order: on_task_start → handle_task → on_task_complete."""
    agent = TrackingAgent(should_fail=False)
    await agent.start()

    task = _make_task(agent_name="tracking_agent", task_type="tracking")
    result = await agent.execute(task)

    assert result.status == TaskStatus.COMPLETED
    assert agent.hook_calls == ["on_task_start", "on_task_complete"]

    await agent.stop()


@pytest.mark.integration
async def test_task_failure_triggers_on_task_failed():
    """Task failure triggers on_task_failed, TaskResult status is FAILED."""
    agent = TrackingAgent(should_fail=True)
    await agent.start()

    task = _make_task(agent_name="tracking_agent", task_type="tracking")
    result = await agent.execute(task)

    assert result.status == TaskStatus.FAILED
    assert result.error_message == "Intentional failure for testing"
    assert "on_task_failed" in agent.hook_calls
    # on_task_start should be called before on_task_failed
    assert agent.hook_calls.index("on_task_start") < agent.hook_calls.index("on_task_failed")

    await agent.stop()


@pytest.mark.integration
async def test_agent_with_working_memory():
    """Agent with WorkingMemory stores and retrieves context during task execution."""

    class MemoryAgent(BaseAgent):
        def __init__(self, memory: Memory):
            super().__init__(name="memory_agent", agent_type="memory_test")
            self.use_memory(memory)

        def get_capabilities(self) -> AgentCapability:
            return AgentCapability(
                agent_name=self.name,
                agent_type=self.agent_type,
                version=self.version,
                supported_tasks=["memory_test"],
                max_concurrency=1,
                description="Memory test agent",
            )

        async def on_task_start(self, task: TaskMessage) -> None:
            # Store context at task start
            if self.memory:
                await self.memory.store(
                    f"ctx:{task.task_id}",
                    {"task_type": task.task_type, "input": task.input_data},
                )

        async def handle_task(self, task: TaskMessage) -> dict:
            # Retrieve stored context
            if self.memory:
                item = await self.memory.retrieve(f"ctx:{task.task_id}")
                if item:
                    return {"retrieved_context": item.value, "processed": True}
            return {"processed": True, "retrieved_context": None}

    memory = InMemoryMemory()
    agent = MemoryAgent(memory=memory)
    await agent.start()

    task = _make_task(agent_name="memory_agent", task_type="memory_test")
    result = await agent.execute(task)

    assert result.status == TaskStatus.COMPLETED
    assert result.output_data["processed"] is True
    assert result.output_data["retrieved_context"] is not None
    assert result.output_data["retrieved_context"]["task_type"] == "memory_test"

    # Verify memory still has the data
    stored = await memory.retrieve("ctx:task-001")
    assert stored is not None

    await agent.stop()


@pytest.mark.integration
async def test_agent_with_episodic_memory():
    """Agent with EpisodicMemory records experience after task completion."""

    class EpisodicAgent(BaseAgent):
        def __init__(self, memory: Memory):
            super().__init__(name="episodic_agent", agent_type="episodic_test")
            self.use_memory(memory)

        def get_capabilities(self) -> AgentCapability:
            return AgentCapability(
                agent_name=self.name,
                agent_type=self.agent_type,
                version=self.version,
                supported_tasks=["episodic_test"],
                max_concurrency=1,
                description="Episodic test agent",
            )

        async def on_task_complete(self, task: TaskMessage, output: dict) -> None:
            # Record experience after task completion
            if self.memory:
                await self.memory.store(
                    f"experience:{task.task_id}",
                    {
                        "input": task.input_data,
                        "output": output,
                        "task_type": task.task_type,
                    },
                    metadata={"outcome": "success"},
                )

        async def handle_task(self, task: TaskMessage) -> dict:
            return {"answer": "42", "confidence": 0.95}

    memory = InMemoryMemory()
    agent = EpisodicAgent(memory=memory)
    await agent.start()

    task = _make_task(agent_name="episodic_agent", task_type="episodic_test")
    result = await agent.execute(task)

    assert result.status == TaskStatus.COMPLETED

    # Verify experience was recorded
    experience = await memory.retrieve("experience:task-001")
    assert experience is not None
    assert experience.value["output"]["answer"] == "42"
    assert experience.metadata["outcome"] == "success"

    await agent.stop()
