"""Agent endpoint handlers.

Provides REST API for agent introspection and turn execution.
Agents are runtime entities that own memory, persona, and skills.
"""

from __future__ import annotations

import asyncio
import logging
import uuid
from typing import TYPE_CHECKING

from server.errors import ErrorCode
from server.transport.schemas import (
    AgentListItem,
    AgentListResponse,
    AgentResponse,
    AgentTurnRequest,
    AgentTurnResponse,
    SkillListItem,
    SkillListResponse,
    validate_request,
)
from server.transport.utils import error_response, get_runtime, json_response

if TYPE_CHECKING:
    from aiohttp import web

logger = logging.getLogger(__name__)


async def handle_list_agents(request: web.Request) -> web.Response:
    """GET /api/v1/agents - List all agents."""
    runtime = get_runtime(request)
    agents = await runtime.agent_repo.list()

    preview_limit = int(request.query.get("preview_limit", "200") or "200")

    items: list[AgentListItem] = []
    for agent in agents:
        defn = agent.definition
        instructions = defn.instructions or ""
        preview = (
            f"{instructions[:preview_limit]}..."
            if len(instructions) > preview_limit
            else instructions
        )

        items.append(AgentListItem(
            id=defn.id,
            name=defn.name,
            instructions_preview=preview,
            tools=defn.tools,
            skills=defn.skills,
            model=defn.model,
            max_steps=defn.max_steps,
            pathway=defn.pathway,
        ))

    resp = AgentListResponse(agents=items, count=len(items))
    return json_response(request, resp.model_dump(mode="json"))


async def handle_get_agent(request: web.Request) -> web.Response:
    """GET /api/v1/agents/{agent_id} - Get agent details."""
    runtime = get_runtime(request)
    agent_id = request.match_info["agent_id"]

    agent = await runtime.agent_repo.get(agent_id)

    if agent is None:
        return error_response(
            request,
            f"Agent not found: {agent_id}",
            ErrorCode.NOT_FOUND,
            status=404,
        )

    defn = agent.definition
    detail = AgentResponse(
        id=defn.id,
        name=defn.name,
        instructions=defn.instructions,
        tools=defn.tools,
        skills=defn.skills,
        model=defn.model,
        max_steps=defn.max_steps,
        temperature=defn.temperature,
        pathway=defn.pathway,
    )
    return json_response(request, detail.model_dump(mode="json"))


async def handle_agent_turn(request: web.Request) -> web.Response:
    """POST /api/v1/agents/{agent_id}/turn - Send message, get response."""
    runtime = get_runtime(request)
    agent_id = request.match_info["agent_id"]
    request_id = request.get("request_id", "unknown")

    try:
        body = await request.json()
    except Exception as e:
        logger.warning(
            "Invalid JSON body in agent_turn request for %s  [request_id=%s]: %s",
            agent_id, request_id, str(e),
        )
        return error_response(
            request,
            "Invalid JSON body.",
            ErrorCode.VALIDATION_ERROR,
            status=400,
        )

    validated, error_resp = validate_request(AgentTurnRequest, body)
    if error_resp:
        return json_response(request, error_resp.model_dump(mode="json"), status=400)

    assert validated is not None

    thread_id = validated.thread_id or str(uuid.uuid4())

    logger.info(
        "Agent turn: %s  thread=%s  [request_id=%s]",
        agent_id, thread_id[:8], request_id,
    )

    # Acquire concurrency semaphore (if configured) to cap parallel LLM turns
    semaphore: asyncio.Semaphore | None = request.app.get("turn_semaphore")

    try:
        if semaphore is not None:
            await semaphore.acquire()

        result = await runtime.turn(
            agent_id=agent_id,
            message=validated.message,
            thread_id=thread_id,
            attachments=validated.attachments,
            pathway_override=validated.pathway,
        )

        logger.info(
            "Agent turn completed: %s  success=%s  steps=%d  [request_id=%s]",
            agent_id, result.success, result.steps_taken, request_id,
        )

        resp = AgentTurnResponse(
            response=result.response,
            completed=result.success,
            steps_taken=result.steps_taken,
            agent_id=result.agent_id,
            thread_id=result.thread_id,
            pathway=result.pathway,
            error=result.error,
        )
        return json_response(request, resp.model_dump(mode="json"))

    except Exception as e:
        logger.error(
            "Agent turn failed: %s  thread=%s  [request_id=%s]: %s",
            agent_id, thread_id[:8], request_id, str(e), exc_info=True,
        )
        return error_response(
            request,
            f"Agent turn failed: {e!s}",
            ErrorCode.INTERNAL_ERROR,
            status=500,
        )
    finally:
        if semaphore is not None:
            semaphore.release()


async def handle_list_agent_skills(request: web.Request) -> web.Response:
    """GET /api/v1/agents/{agent_id}/skills - List all skills for an agent."""
    runtime = get_runtime(request)
    agent_id = request.match_info["agent_id"]

    agent = await runtime.agent_repo.get(agent_id)

    if agent is None:
        return error_response(
            request,
            f"Agent not found: {agent_id}",
            ErrorCode.NOT_FOUND,
            status=404,
        )

    # Use the agent's own skill scoping
    scoped_skills, _ = agent.scope_skills()

    skill_items: list[SkillListItem] = []
    for skill in scoped_skills:
        skill_items.append(SkillListItem(
            id=skill.id,
            name=skill.name,
            description=skill.description or "",
            tags=list(getattr(skill, "tags", set())),
            tools=[],
        ))

    resp = SkillListResponse(skills=skill_items, count=len(skill_items))
    return json_response(request, resp.model_dump(mode="json"))


__all__ = [
    "handle_agent_turn",
    "handle_get_agent",
    "handle_list_agent_skills",
    "handle_list_agents",
]
