"""Agents router: CRUD, tool discovery, and running an agent."""

from __future__ import annotations

import uuid

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session, sessionmaker

from legate.agent.builtin_tools import BUILTIN_REGISTRY
from legate.agent.service import launch_run
from legate.api.deps import Principal, require
from legate.api.schemas import (
    AgentCreate,
    AgentOut,
    AgentUpdate,
    RunCreate,
    RunCreateResponse,
    RunOut,
    ToolOut,
)
from legate.audit.service import record as audit_record
from legate.billing import service as billing
from legate.connectors.registry import catalog_tool_specs, known_tool_names
from legate.connectors.resolution import workspace_mcp_specs
from legate.db.models.workspace import Workspace
from legate.db.session import get_db, get_session_factory
from legate.observability import metrics
from legate.policy.rbac import Permission
from legate.repositories import agents as agents_repo
from legate.repositories import runs as runs_repo
from legate.security.ratelimit import enforce_run_rate

router = APIRouter(tags=["agents"])


def _validate_tools(tools: list[str], db: Session, workspace_id: uuid.UUID) -> None:
    known = known_tool_names() | {s.name for s in workspace_mcp_specs(db, workspace_id)}
    unknown = [t for t in tools if t not in known]
    if unknown:
        raise HTTPException(422, detail=f"unknown tools: {', '.join(unknown)}")


@router.get("/tools", response_model=list[ToolOut])
def list_tools(
    principal: Principal = Depends(require(Permission.VIEW)),
    db: Session = Depends(get_db),
) -> list[ToolOut]:
    # Built-in tools, every connector-type tool, plus this workspace's MCP tools.
    specs = (
        list(BUILTIN_REGISTRY.specs())
        + catalog_tool_specs()
        + workspace_mcp_specs(db, principal.workspace_id)
    )
    return [ToolOut(**spec.as_dict()) for spec in specs]


@router.get("/agents", response_model=list[AgentOut])
def list_agents(
    principal: Principal = Depends(require(Permission.VIEW)),
    db: Session = Depends(get_db),
) -> list[AgentOut]:
    agents = agents_repo.list_for_workspace(db, workspace_id=principal.workspace_id)
    return [AgentOut.model_validate(a) for a in agents]


@router.post("/agents", response_model=AgentOut, status_code=status.HTTP_201_CREATED)
def create_agent(
    body: AgentCreate,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> AgentOut:
    _validate_tools(body.tools, db, principal.workspace_id)
    workspace = db.get(Workspace, principal.workspace_id)
    if workspace is not None:
        billing.enforce_resource(db, workspace=workspace, resource="agents")
    agent = agents_repo.create(
        db,
        workspace_id=principal.workspace_id,
        name=body.name,
        description=body.description,
        system_prompt=body.system_prompt,
        provider=body.provider,
        model=body.model,
        tools=body.tools,
        memory_config=body.memory_config,
        guardrails_json=body.guardrails_json,
        created_by=principal.user.id if principal.user else None,
    )
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="agent.create",
        target=str(agent.id),
        metadata={"name": agent.name},
    )
    db.commit()
    db.refresh(agent)
    return AgentOut.model_validate(agent)


@router.get("/agents/{agent_id}", response_model=AgentOut)
def get_agent(
    agent_id: uuid.UUID,
    principal: Principal = Depends(require(Permission.VIEW)),
    db: Session = Depends(get_db),
) -> AgentOut:
    agent = agents_repo.get(db, workspace_id=principal.workspace_id, agent_id=agent_id)
    if agent is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="agent not found")
    return AgentOut.model_validate(agent)


@router.patch("/agents/{agent_id}", response_model=AgentOut)
def update_agent(
    agent_id: uuid.UUID,
    body: AgentUpdate,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> AgentOut:
    agent = agents_repo.get(db, workspace_id=principal.workspace_id, agent_id=agent_id)
    if agent is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="agent not found")

    updates = body.model_dump(exclude_unset=True)
    if "tools" in updates and updates["tools"] is not None:
        _validate_tools(updates["tools"], db, principal.workspace_id)
    for field, value in updates.items():
        setattr(agent, field, value)

    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="agent.update",
        target=str(agent.id),
        metadata={"fields": sorted(updates.keys())},
    )
    db.commit()
    db.refresh(agent)
    return AgentOut.model_validate(agent)


@router.delete("/agents/{agent_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_agent(
    agent_id: uuid.UUID,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> None:
    agent = agents_repo.get(db, workspace_id=principal.workspace_id, agent_id=agent_id)
    if agent is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="agent not found")
    agents_repo.delete(db, agent)
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="agent.delete",
        target=str(agent_id),
    )
    db.commit()


@router.post(
    "/agents/{agent_id}/run", response_model=RunCreateResponse, status_code=status.HTTP_201_CREATED
)
async def run_agent(
    agent_id: uuid.UUID,
    body: RunCreate,
    principal: Principal = Depends(require(Permission.RUN)),
    db: Session = Depends(get_db),
    session_factory: sessionmaker = Depends(get_session_factory),
) -> RunCreateResponse:
    agent = agents_repo.get(db, workspace_id=principal.workspace_id, agent_id=agent_id)
    if agent is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="agent not found")

    enforce_run_rate(principal.workspace_id)
    workspace = db.get(Workspace, principal.workspace_id)
    if workspace is not None:
        billing.enforce_run_quota(db, workspace=workspace)

    run = runs_repo.create(
        db,
        workspace_id=principal.workspace_id,
        agent_id=agent.id,
        input={"input": body.input},
        created_by=principal.user.id if principal.user else None,
    )
    billing.record_usage(db, workspace_id=principal.workspace_id, metric="runs")
    metrics.record_run_started("agent")
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="agent.run",
        target=str(run.id),
        metadata={"agent_id": str(agent.id)},
    )
    db.commit()
    run_id = run.id

    await launch_run(run_id, session_factory)

    db.refresh(run)
    return RunCreateResponse(
        run=RunOut.model_validate(run),
        stream_url=f"/runs/{run_id}/stream",
    )
