"""
Agents + Runs router.

GET  /agents                            — list all registered agent classes
POST /tenants/{tenant_id}/runs          — trigger an agent run (async, non-blocking)
GET  /tenants/{tenant_id}/runs          — list all runs for a tenant
GET  /tenants/{tenant_id}/runs/{run_id} — get run status + result

FIX: FastAPI BackgroundTasks runs sync functions directly on the event loop,
blocking all subsequent requests. _execute_run is sync/blocking, so we
schedule it via asyncio.to_thread() inside an async wrapper, ensuring it
runs in a thread pool and the event loop stays free.
"""

from __future__ import annotations

import asyncio
import os
import tempfile
from datetime import datetime, timezone

from fastapi import APIRouter, BackgroundTasks, HTTPException, status
from ulid import ULID

from zia.api.auth import AuthDep
from zia.api.models import (
    AgentInfo,
    AgentListResponse,
    RunCreateRequest,
    RunListResponse,
    RunRecord,
    RunStatus,
)
from zia.api.run_store import RunStore
from zia.tenants.context import TenantRegistry

router = APIRouter(tags=["Agents & Runs"])


# ---------------------------------------------------------------------------
# Agent discovery
# ---------------------------------------------------------------------------

@router.get("/agents", response_model=AgentListResponse, summary="List registered agents")
async def list_agents(_key: AuthDep) -> AgentListResponse:
    """List all agent classes registered in the AgentRegistry."""
    from zia.core.runtime.registry import AgentRegistry
    regs = AgentRegistry.get().all_registrations()
    agents = [
        AgentInfo(
            domain=r.domain,
            class_name=r.class_name,
            version=r.version,
            description=r.description,
            module=r.module,
        )
        for r in regs
    ]
    return AgentListResponse(agents=agents, total=len(agents))


# ---------------------------------------------------------------------------
# Run execution (sync, blocking — called via asyncio.to_thread)
# ---------------------------------------------------------------------------

def _do_execute_run(run_id: str, tenant_id: str, domain: str, environment: str) -> None:
    """
    Synchronous agent execution. MUST be called via asyncio.to_thread() so it
    runs in the thread pool and does not block the uvicorn event loop.
    """
    from zia.cli.templates import DOMAIN_TEMPLATES
    from zia.core.dsl.parser import load_agent_yaml
    from zia.core.runtime.agent import AgentContext
    from zia.core.runtime.executor import AgentExecutor
    from zia.core.runtime.registry import AgentRegistry

    store = RunStore.get()
    record = store.get_run(run_id)
    if record is None:
        return

    # Mark as running
    record = RunRecord(**{
        **record.model_dump(),
        "status": RunStatus.RUNNING,
        "started_at": datetime.now(timezone.utc),
    })
    store.update(record)

    try:
        registry = AgentRegistry.get()
        if not registry.is_registered(domain):
            raise ValueError(f"No agent registered for domain '{domain}'")

        if domain not in DOMAIN_TEMPLATES:
            raise ValueError(f"No template for domain '{domain}'")

        # Write a temp YAML from the domain template so we have a valid DSL
        tmpl = DOMAIN_TEMPLATES[domain]
        yaml_str = tmpl.yaml_template.format(
            agent_id=f"api-run-{domain.replace('_', '-')}",
            agent_name=f"API Run — {domain}",
        )
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write(yaml_str)
            tmp_path = f.name

        try:
            dsl = load_agent_yaml(tmp_path)
        finally:
            os.unlink(tmp_path)

        context = AgentContext(
            tenant_id=tenant_id,
            trace_id=run_id,
            dsl=dsl,
            environment=environment,
        )

        import inspect
        agent_cls = registry.resolve(domain)
        sig = inspect.signature(agent_cls.__init__)
        agent: object
        if "adapter" in sig.parameters:
            try:
                from zia.sif.graph.adapter import KuzuAdapter
                adapter = KuzuAdapter()
                adapter.initialize_schema(tenant_id)
                agent = agent_cls(adapter)
            except Exception:
                # kuzu unavailable — run agent without graph adapter
                agent = agent_cls()
        else:
            agent = agent_cls()

        result = AgentExecutor().run(agent, context)

        record = RunRecord(**{
            **record.model_dump(),
            "status": RunStatus.COMPLETED if result.success else RunStatus.FAILED,
            "output": result.output if result.success else None,
            "errors": result.errors if not result.success else [],
            "completed_at": datetime.now(timezone.utc),
            "duration_ms": result.duration_ms,
        })

    except Exception as exc:
        record = RunRecord(**{
            **record.model_dump(),
            "status": RunStatus.FAILED,
            "errors": [str(exc)],
            "completed_at": datetime.now(timezone.utc),
        })

    store.update(record)


async def _async_execute_run(run_id: str, tenant_id: str, domain: str, environment: str) -> None:
    """
    Async wrapper: runs _do_execute_run in a thread pool via asyncio.to_thread()
    so the event loop is not blocked during agent execution.
    """
    await asyncio.to_thread(_do_execute_run, run_id, tenant_id, domain, environment)


# ---------------------------------------------------------------------------
# HTTP routes
# ---------------------------------------------------------------------------

@router.post(
    "/tenants/{tenant_id}/runs",
    response_model=RunRecord,
    status_code=status.HTTP_202_ACCEPTED,
    summary="Trigger an agent run",
)
async def create_run(
    tenant_id: str,
    body: RunCreateRequest,
    background_tasks: BackgroundTasks,
    _key: AuthDep,
) -> RunRecord:
    """
    Trigger an agent run. Returns immediately with run_id + status=queued.
    Poll GET /tenants/{tenant_id}/runs/{run_id} for status and result.
    """
    if not TenantRegistry().exists(tenant_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tenant '{tenant_id}' not found. Register it first via POST /tenants.",
        )

    run_id = str(ULID())
    record = RunRecord(
        run_id=run_id,
        tenant_id=tenant_id,
        domain=body.domain,
        environment=body.environment,
        status=RunStatus.QUEUED,
    )
    RunStore.get().create(record)

    # Schedule async wrapper — runs _do_execute_run in a thread pool
    background_tasks.add_task(_async_execute_run, run_id, tenant_id, body.domain, body.environment)
    return record


@router.get(
    "/tenants/{tenant_id}/runs",
    response_model=RunListResponse,
    summary="List all runs for a tenant",
)
async def list_runs(tenant_id: str, _key: AuthDep) -> RunListResponse:
    runs = RunStore.get().list_for_tenant(tenant_id)
    return RunListResponse(tenant_id=tenant_id, runs=runs, total=len(runs))


@router.get(
    "/tenants/{tenant_id}/runs/{run_id}",
    response_model=RunRecord,
    summary="Get run status and result",
)
async def get_run(tenant_id: str, run_id: str, _key: AuthDep) -> RunRecord:
    record = RunStore.get().get_run(run_id)
    if not record or record.tenant_id != tenant_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Run '{run_id}' not found for tenant '{tenant_id}'",
        )
    return record
