"""Monitoring API endpoints for Mission Control."""

from __future__ import annotations

from datetime import datetime
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession

from app.api.deps import OrganizationContext, require_org_member
from app.core.logging import get_logger
from app.db import pagination
from app.db.session import get_session
from app.models.monitoring import (
    CostSnapshot,
    CronJobStatus,
    SessionEvent,
    SubAgentRun,
    SystemHealthMetric,
)
from app.schemas.monitoring import (
    CostBreakdownRead,
    CostSnapshotRead,
    CostSummaryRead,
    CronJobStatusRead,
    CronSummaryRead,
    HealthSummaryRead,
    SessionEventRead,
    SessionSummaryRead,
    SubAgentRunRead,
    SubAgentSummaryRead,
    SystemHealthMetricRead,
    TrendDay,
    TrendRead,
)
from app.schemas.pagination import DefaultLimitOffsetPage
from app.models.gateways import Gateway

router = APIRouter(prefix="/monitoring", tags=["monitoring"])
SESSION_DEP = Depends(get_session)
ORG_MEMBER_DEP = Depends(require_org_member)

_logger = get_logger(__name__)

# Query parameter constants
GATEWAY_ID_QUERY = Query(default=None, description="Filter by gateway ID")
JOB_NAME_QUERY = Query(default=None, description="Filter by job name")
ENABLED_QUERY = Query(default=None, description="Filter by enabled status")
SESSION_KEY_QUERY = Query(default=None, description="Filter by session key")
MODEL_QUERY = Query(default=None, description="Filter by model name")
EVENT_TYPE_QUERY = Query(default=None, description="Filter by event type")
STATUS_QUERY = Query(default=None, description="Filter by status")
AGENT_QUERY = Query(default=None, description="Filter by agent name")
START_DATE_QUERY = Query(default=None, description="Filter from this date (inclusive)")
END_DATE_QUERY = Query(default=None, description="Filter until this date (inclusive)")


@router.get(
    "/cost-snapshots",
    response_model=DefaultLimitOffsetPage[CostSnapshotRead],
    summary="List Cost Snapshots",
    description="List cost snapshots for gateways in the caller's organization.",
)
async def list_cost_snapshots(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    start: datetime | None = START_DATE_QUERY,
    end: datetime | None = END_DATE_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DefaultLimitOffsetPage[CostSnapshotRead]:
    """List cost snapshots with optional filters.

    - **gateway_id**: Filter by specific gateway
    - **start**: Filter snapshots from this date (inclusive)
    - **end**: Filter snapshots until this date (inclusive)
    """
    statement = select(CostSnapshot).where(
        CostSnapshot.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CostSnapshot.gateway_id == gateway_id)

    if start is not None:
        statement = statement.where(CostSnapshot.collected_at >= start)

    if end is not None:
        statement = statement.where(CostSnapshot.collected_at <= end)

    statement = statement.order_by(col(CostSnapshot.created_at).desc())
    return await pagination.paginate(session, statement)


@router.get(
    "/cost-snapshots/{id}",
    response_model=CostSnapshotRead,
    summary="Get Cost Snapshot",
    description="Get a single cost snapshot by ID.",
)
async def get_cost_snapshot(
    id: UUID,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> CostSnapshot:
    """Get a cost snapshot by ID."""
    snapshot = await CostSnapshot.objects.by_id(id).first(session)
    if snapshot is None:
        raise HTTPException(status_code=404, detail="Cost snapshot not found")
    if snapshot.organization_id != ctx.organization.id:
        raise HTTPException(status_code=403, detail="Access denied")
    return snapshot


@router.get(
    "/cron-jobs",
    response_model=DefaultLimitOffsetPage[CronJobStatusRead],
    summary="List Cron Job Statuses",
    description="List cron job statuses for gateways in the caller's organization.",
)
async def list_cron_jobs(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    enabled: bool | None = ENABLED_QUERY,
    job_name: str | None = JOB_NAME_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DefaultLimitOffsetPage[CronJobStatusRead]:
    """List cron job statuses with optional filters.

    - **gateway_id**: Filter by specific gateway
    - **enabled**: Filter by enabled status
    - **job_name**: Filter by job name (exact match)
    """
    statement = select(CronJobStatus).where(
        CronJobStatus.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CronJobStatus.gateway_id == gateway_id)

    if enabled is not None:
        statement = statement.where(CronJobStatus.enabled == enabled)

    if job_name is not None:
        statement = statement.where(CronJobStatus.job_name == job_name)

    statement = statement.order_by(col(CronJobStatus.created_at).desc())
    return await pagination.paginate(session, statement)


@router.get(
    "/cron-jobs/{id}",
    response_model=CronJobStatusRead,
    summary="Get Cron Job Status",
    description="Get a single cron job status by ID.",
)
async def get_cron_job(
    id: UUID,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> CronJobStatus:
    """Get a cron job status by ID."""
    job = await CronJobStatus.objects.by_id(id).first(session)
    if job is None:
        raise HTTPException(status_code=404, detail="Cron job status not found")
    if job.organization_id != ctx.organization.id:
        raise HTTPException(status_code=403, detail="Access denied")
    return job


@router.get(
    "/sessions",
    response_model=DefaultLimitOffsetPage[SessionEventRead],
    summary="List Session Events",
    description="List session events for gateways in the caller's organization.",
)
async def list_sessions(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    session_key: str | None = SESSION_KEY_QUERY,
    model: str | None = MODEL_QUERY,
    event_type: str | None = EVENT_TYPE_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DefaultLimitOffsetPage[SessionEventRead]:
    """List session events with optional filters.

    - **gateway_id**: Filter by specific gateway
    - **session_key**: Filter by session key
    - **model**: Filter by model name
    - **event_type**: Filter by event type
    """
    statement = select(SessionEvent).where(
        SessionEvent.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SessionEvent.gateway_id == gateway_id)

    if session_key is not None:
        statement = statement.where(SessionEvent.session_key == session_key)

    if model is not None:
        statement = statement.where(SessionEvent.model == model)

    if event_type is not None:
        statement = statement.where(SessionEvent.event_type == event_type)

    statement = statement.order_by(col(SessionEvent.created_at).desc())
    return await pagination.paginate(session, statement)


@router.get(
    "/sessions/{id}",
    response_model=SessionEventRead,
    summary="Get Session Event",
    description="Get a single session event by ID.",
)
async def get_session_event(
    id: UUID,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> SessionEvent:
    """Get a session event by ID."""
    event = await SessionEvent.objects.by_id(id).first(session)
    if event is None:
        raise HTTPException(status_code=404, detail="Session event not found")
    if event.organization_id != ctx.organization.id:
        raise HTTPException(status_code=403, detail="Access denied")
    return event


@router.get(
    "/health",
    response_model=DefaultLimitOffsetPage[SystemHealthMetricRead],
    summary="List System Health Metrics",
    description="List system health metrics for gateways in the caller's organization.",
)
async def list_health_metrics(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    start: datetime | None = START_DATE_QUERY,
    end: datetime | None = END_DATE_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DefaultLimitOffsetPage[SystemHealthMetricRead]:
    """List system health metrics with optional filters.

    - **gateway_id**: Filter by specific gateway
    - **start**: Filter metrics from this date (inclusive)
    - **end**: Filter metrics until this date (inclusive)
    """
    statement = select(SystemHealthMetric).where(
        SystemHealthMetric.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SystemHealthMetric.gateway_id == gateway_id)

    if start is not None:
        statement = statement.where(SystemHealthMetric.collected_at >= start)

    if end is not None:
        statement = statement.where(SystemHealthMetric.collected_at <= end)

    statement = statement.order_by(col(SystemHealthMetric.created_at).desc())
    return await pagination.paginate(session, statement)


@router.get(
    "/health/{id}",
    response_model=SystemHealthMetricRead,
    summary="Get System Health Metric",
    description="Get a single system health metric by ID.",
)
async def get_health_metric(
    id: UUID,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> SystemHealthMetric:
    """Get a system health metric by ID."""
    metric = await SystemHealthMetric.objects.by_id(id).first(session)
    if metric is None:
        raise HTTPException(status_code=404, detail="Health metric not found")
    if metric.organization_id != ctx.organization.id:
        raise HTTPException(status_code=403, detail="Access denied")
    return metric


@router.get(
    "/sub-agents",
    response_model=DefaultLimitOffsetPage[SubAgentRunRead],
    summary="List Sub-Agent Runs",
    description="List sub-agent runs for gateways in the caller's organization.",
)
async def list_sub_agents(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    status: str | None = STATUS_QUERY,
    agent: str | None = AGENT_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DefaultLimitOffsetPage[SubAgentRunRead]:
    """List sub-agent runs with optional filters.

    - **gateway_id**: Filter by specific gateway
    - **status**: Filter by run status (pending, running, succeeded, failed)
    - **agent**: Filter by agent name
    """
    statement = select(SubAgentRun).where(
        SubAgentRun.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SubAgentRun.gateway_id == gateway_id)

    if status is not None:
        statement = statement.where(SubAgentRun.status == status)

    if agent is not None:
        statement = statement.where(SubAgentRun.agent == agent)

    statement = statement.order_by(col(SubAgentRun.created_at).desc())
    return await pagination.paginate(session, statement)


@router.get(
    "/sub-agents/{id}",
    response_model=SubAgentRunRead,
    summary="Get Sub-Agent Run",
    description="Get a single sub-agent run by ID.",
)
async def get_sub_agent_run(
    id: UUID,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> SubAgentRun:
    """Get a sub-agent run by ID."""
    run = await SubAgentRun.objects.by_id(id).first(session)
    if run is None:
        raise HTTPException(status_code=404, detail="Sub-agent run not found")
    if run.organization_id != ctx.organization.id:
        raise HTTPException(status_code=403, detail="Access denied")
    return run


@router.get(
    "/cost-summary",
    response_model=list[CostSummaryRead],
    summary="Cost Summary",
    description="Consolidated cost overview for all gateways in the organization.",
)
async def get_cost_summary(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[CostSummaryRead]:
    """Get cost summary for all gateways in the organization.

    Returns consolidated cost data with model breakdown for the most recent
    snapshot per gateway.

    - **gateway_id**: Filter by specific gateway
    """
    statement = select(CostSnapshot).where(
        CostSnapshot.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CostSnapshot.gateway_id == gateway_id)

    statement = statement.order_by(col(CostSnapshot.created_at).desc())
    result = await session.execute(statement)
    snapshots = result.scalars().all()

    if not snapshots:
        return []

    # Group by gateway and get the latest snapshot per gateway
    from collections import defaultdict
    by_gateway: dict[UUID, CostSnapshot] = {}
    for snapshot in snapshots:
        if snapshot.gateway_id not in by_gateway or snapshot.collected_at > by_gateway[snapshot.gateway_id].collected_at:
            by_gateway[snapshot.gateway_id] = snapshot

    # Build CostSummaryRead objects
    from app.models.gateways import Gateway
    summaries = []
    for gw_id, snapshot in by_gateway.items():
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == gw_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        summaries.append(CostSummaryRead(
            gateway_id=gw_id,
            gateway_name=gateway_name,
            period_start=snapshot.period_start,
            period_end=snapshot.period_end,
            total_cost=snapshot.total_cost,
            cost_by_model=snapshot.model_costs or {},
            token_counts=snapshot.token_counts,
            last_collected_at=snapshot.collected_at,
        ))

    return summaries


@router.get(
    "/cost-breakdown",
    response_model=list[CostBreakdownRead],
    summary="Cost Breakdown",
    description="Models ranked by cost with percentage breakdown for all gateways.",
)
async def get_cost_breakdown(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[CostBreakdownRead]:
    """Get cost breakdown for all gateways in the organization.

    Returns models ranked by cost with percentage breakdown for the most recent
    snapshot per gateway.

    - **gateway_id**: Filter by specific gateway
    """
    statement = select(CostSnapshot).where(
        CostSnapshot.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CostSnapshot.gateway_id == gateway_id)

    statement = statement.order_by(col(CostSnapshot.created_at).desc())
    result = await session.execute(statement)
    snapshots = result.scalars().all()

    if not snapshots:
        return []

    # Group by gateway and get the latest snapshot per gateway
    from collections import defaultdict
    by_gateway: dict[UUID, CostSnapshot] = {}
    for snapshot in snapshots:
        if snapshot.gateway_id not in by_gateway or snapshot.collected_at > by_gateway[snapshot.gateway_id].collected_at:
            by_gateway[snapshot.gateway_id] = snapshot

    # Build CostBreakdownRead objects
    from app.models.gateways import Gateway
    breakdowns = []
    for gw_id, snapshot in by_gateway.items():
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == gw_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Build breakdown list with percentages
        breakdown = []
        total = snapshot.total_cost or 0.0
        if total > 0 and snapshot.model_costs:
            for model, cost in snapshot.model_costs.items():
                pct = round((cost / total) * 100, 2) if total > 0 else 0.0
                breakdown.append({
                    "model": model,
                    "cost": cost,
                    "percent": pct,
                })

        # Sort by cost descending
        breakdown.sort(key=lambda x: x["cost"], reverse=True)

        breakdowns.append(CostBreakdownRead(
            gateway_id=gw_id,
            gateway_name=gateway_name,
            period_start=snapshot.period_start,
            period_end=snapshot.period_end,
            total_cost=snapshot.total_cost,
            breakdown=breakdown,
            last_collected_at=snapshot.collected_at,
        ))

    return breakdowns


@router.get(
    "/health-summary",
    response_model=list[HealthSummaryRead],
    summary="Health Summary",
    description="Latest health status and metrics for all gateways in the organization.",
)
async def get_health_summary(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[HealthSummaryRead]:
    """Get health summary for all gateways in the organization.

    Returns the latest health snapshot per gateway with system metrics.

    - **gateway_id**: Filter by specific gateway
    """
    # Get latest health metric per gateway
    subquery = (
        select(
            SystemHealthMetric.gateway_id,
            col(SystemHealthMetric.collected_at).label("latest_collected"),
            col(SystemHealthMetric.created_at).label("latest_created"),
        )
        .where(SystemHealthMetric.organization_id == ctx.organization.id)
        .order_by(col(SystemHealthMetric.collected_at).desc())
        .subquery()
    )

    # Get distinct gateway_ids with their latest metric
    statement = select(SystemHealthMetric).where(
        SystemHealthMetric.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SystemHealthMetric.gateway_id == gateway_id)

    # Get all metrics, then group by gateway to get latest
    result = await session.execute(statement)
    metrics = result.scalars().all()

    if not metrics:
        return []

    # Group by gateway and get the latest metric per gateway
    from collections import defaultdict
    by_gateway: dict[UUID, SystemHealthMetric] = {}
    for metric in metrics:
        if metric.gateway_id not in by_gateway or metric.collected_at > by_gateway[metric.gateway_id].collected_at:
            by_gateway[metric.gateway_id] = metric

    # Build HealthSummaryRead objects
    summaries = []
    for gw_id, metric in by_gateway.items():
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == gw_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Determine status
        status = "online" if metric.gateway_live else "offline"

        # Calculate uptime in seconds
        uptime_seconds = None
        if metric.gateway_uptime_ms:
            uptime_seconds = round(metric.gateway_uptime_ms / 1000)

        # Calculate memory in MB
        memory_mb = None
        if metric.ram_used_bytes and metric.ram_total_bytes:
            memory_mb = round(metric.ram_used_bytes / (1024 * 1024), 2)

        summaries.append(HealthSummaryRead(
            gateway_id=gw_id,
            gateway_name=gateway_name,
            status=status,
            pid=metric.gateway_pid,
            uptime_seconds=uptime_seconds,
            memory_mb=memory_mb,
            cpu_percent=metric.cpu_percent,
            ram_percent=metric.ram_percent,
            swap_percent=metric.swap_percent,
            disk_percent=metric.disk_percent,
            compaction_mode=None,  # Not tracked in current schema
            last_collected_at=metric.collected_at,
        ))

    return summaries


@router.get(
    "/cron-summary",
    response_model=list[CronSummaryRead],
    summary="Cron Summary",
    description="Status and scheduling info for all cron jobs in the organization.",
)
async def get_cron_summary(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    enabled: bool | None = ENABLED_QUERY,
    job_name: str | None = JOB_NAME_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[CronSummaryRead]:
    """Get cron summary for all cron jobs in the organization.

    Returns cron job status with scheduling info.

    - **gateway_id**: Filter by specific gateway
    - **enabled**: Filter by enabled status
    - **job_name**: Filter by job name (exact match)
    """
    statement = select(CronJobStatus).where(
        CronJobStatus.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CronJobStatus.gateway_id == gateway_id)

    if enabled is not None:
        statement = statement.where(CronJobStatus.enabled == enabled)

    if job_name is not None:
        statement = statement.where(CronJobStatus.job_name == job_name)

    statement = statement.order_by(col(CronJobStatus.created_at).desc())
    result = await session.execute(statement)
    jobs = result.scalars().all()

    if not jobs:
        return []

    # Build CronSummaryRead objects
    summaries = []
    for job in jobs:
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == job.gateway_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Extract model from metadata if present
        model = None
        if job.metadata_ and isinstance(job.metadata_, dict):
            model = job.metadata_.get("model")

        summaries.append(CronSummaryRead(
            gateway_id=job.gateway_id,
            gateway_name=gateway_name,
            job_name=job.job_name,
            schedule=job.schedule,
            enabled=job.enabled,
            status=job.status,
            last_run_at=job.last_run_at,
            next_run_at=job.next_run_at,
            last_error=job.last_error,
            failure_count=job.failure_count,
            model=model,
            metadata_=job.metadata_,
        ))

    return summaries


@router.get(
    "/sessions-summary",
    response_model=list[SessionSummaryRead],
    summary="Sessions Summary",
    description="Recent session events with context and token breakdown.",
)
async def get_sessions_summary(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    session_key: str | None = SESSION_KEY_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[SessionSummaryRead]:
    """Get sessions summary for the organization.

    Returns recent session events with model display names and token breakdown.

    - **gateway_id**: Filter by specific gateway
    - **session_key**: Filter by session key
    """
    statement = select(SessionEvent).where(
        SessionEvent.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SessionEvent.gateway_id == gateway_id)

    if session_key is not None:
        statement = statement.where(SessionEvent.session_key == session_key)

    statement = statement.order_by(col(SessionEvent.created_at).desc())
    result = await session.execute(statement)
    events = result.scalars().all()

    if not events:
        return []

    # Build SessionSummaryRead objects
    from app.services.monitoring.data_processing import ModelName
    summaries = []
    for event in events:
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == event.gateway_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Get model display name
        model_display_name = None
        if event.model:
            model_display_name = ModelName(event.model)

        # Extract token counts
        prompt_tokens = None
        completion_tokens = None
        total_tokens = None
        if event.token_counts and isinstance(event.token_counts, dict):
            prompt_tokens = event.token_counts.get("prompt_tokens")
            completion_tokens = event.token_counts.get("completion_tokens")
            total_tokens = event.token_counts.get("total_tokens")
            if total_tokens is None and prompt_tokens is not None and completion_tokens is not None:
                total_tokens = prompt_tokens + completion_tokens

        summaries.append(SessionSummaryRead(
            gateway_id=event.gateway_id,
            gateway_name=gateway_name,
            session_key=event.session_key,
            event_type=event.event_type,
            model=event.model,
            model_display_name=model_display_name,
            agent_id=event.agent_id,
            channel=event.channel,
            context_percent=event.context_percent,
            prompt_tokens=prompt_tokens,
            completion_tokens=completion_tokens,
            total_tokens=total_tokens,
            last_collected_at=event.collected_at,
        ))

    return summaries


@router.get(
    "/sub-agents-summary",
    response_model=list[SubAgentSummaryRead],
    summary="Sub-Agent Summary",
    description="Sub-agent runs with cost, duration, and token breakdown.",
)
async def get_sub_agents_summary(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    status: str | None = STATUS_QUERY,
    agent: str | None = AGENT_QUERY,
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[SubAgentSummaryRead]:
    """Get sub-agents summary for the organization.

    Returns sub-agent runs with cost and token breakdown.

    - **gateway_id**: Filter by specific gateway
    - **status**: Filter by run status (pending, running, succeeded, failed)
    - **agent**: Filter by agent name
    """
    statement = select(SubAgentRun).where(
        SubAgentRun.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(SubAgentRun.gateway_id == gateway_id)

    if status is not None:
        statement = statement.where(SubAgentRun.status == status)

    if agent is not None:
        statement = statement.where(SubAgentRun.agent == agent)

    statement = statement.order_by(col(SubAgentRun.created_at).desc())
    result = await session.execute(statement)
    runs = result.scalars().all()

    if not runs:
        return []

    # Build SubAgentSummaryRead objects
    from app.services.monitoring.data_processing import ModelName
    summaries = []
    for run in runs:
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == run.gateway_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Get model display name
        model_display_name = None
        if run.model:
            model_display_name = ModelName(run.model)

        # Calculate duration in seconds
        duration_seconds = None
        if run.duration_ms:
            duration_seconds = round(run.duration_ms / 1000, 2)

        # Extract token counts
        prompt_tokens = None
        completion_tokens = None
        total_tokens = None
        if run.token_counts and isinstance(run.token_counts, dict):
            prompt_tokens = run.token_counts.get("prompt_tokens")
            completion_tokens = run.token_counts.get("completion_tokens")
            total_tokens = run.token_counts.get("total_tokens")
            if total_tokens is None and prompt_tokens is not None and completion_tokens is not None:
                total_tokens = prompt_tokens + completion_tokens

        summaries.append(SubAgentSummaryRead(
            gateway_id=run.gateway_id,
            gateway_name=gateway_name,
            agent_id=run.agent,
            session_key=run.parent_session_key,
            model=run.model,
            model_display_name=model_display_name,
            status=run.status,
            cost=run.cost,
            duration_seconds=duration_seconds,
            prompt_tokens=prompt_tokens,
            completion_tokens=completion_tokens,
            total_tokens=total_tokens,
            last_collected_at=run.collected_at,
        ))

    return summaries


@router.get(
    "/trends",
    response_model=list[TrendRead],
    summary="Trends Summary",
    description="Cost and token trends over time (7d or 30d).",
)
async def get_trends(
    gateway_id: UUID | None = GATEWAY_ID_QUERY,
    range_param: str = Query(default="7d", description="Time range: 7d or 30d"),
    session: AsyncSession = SESSION_DEP,
    ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> list[TrendRead]:
    """Get cost and token trends for the organization.

    Returns daily aggregated cost and token data for the specified time range.

    - **gateway_id**: Filter by specific gateway
    - **range**: Time range - "7d" (default) or "30d"
    """
    import re
    from datetime import date, timedelta
    from app.services.monitoring.data_processing import ModelName

    # Parse range with safe upper limit
    range_days = 7
    if range_param == "30d":
        range_days = 30
    elif re.match(r"^(\d+)d$", range_param):
        days = int(range_param[:-1])
        if days < 1 or days > 365:  # Safe limit: 1-365 days
            raise HTTPException(status_code=400, detail="Invalid range. Must be between 1d and 365d")
        range_days = days
    else:
        raise HTTPException(status_code=400, detail="Invalid range. Must be '7d', '30d', or 'Nd' where N is 1-365")

    # Get all cost snapshots
    statement = select(CostSnapshot).where(
        CostSnapshot.organization_id == ctx.organization.id
    )

    if gateway_id is not None:
        statement = statement.where(CostSnapshot.gateway_id == gateway_id)

    statement = statement.order_by(col(CostSnapshot.created_at).desc())
    result = await session.execute(statement)
    snapshots = result.scalars().all()

    if not snapshots:
        return []

    # Group by gateway and get latest snapshot per gateway
    from collections import defaultdict
    by_gateway: dict[UUID, CostSnapshot] = {}
    for snapshot in snapshots:
        if snapshot.gateway_id not in by_gateway or snapshot.collected_at > by_gateway[snapshot.gateway_id].collected_at:
            by_gateway[snapshot.gateway_id] = snapshot

    # Build daily buckets
    today = date.today()
    chart_dates = []
    for i in range(range_days - 1, -1, -1):
        chart_dates.append((today - timedelta(days=i)).strftime("%Y-%m-%d"))

    # Aggregate daily costs from snapshots
    daily_costs: dict[str, dict[str, float]] = defaultdict(dict)
    daily_tokens: dict[str, dict[str, int]] = defaultdict(dict)
    daily_calls: dict[str, dict[str, int]] = defaultdict(dict)

    for gw_id, snapshot in by_gateway.items():
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == gw_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Only include if we have model costs
        if snapshot.model_costs:
            for model, cost in snapshot.model_costs.items():
                # Use today's date for simplicity (would need date tracking in schema for real daily breakdown)
                date_str = snapshot.collected_at.strftime("%Y-%m-%d")
                if date_str not in daily_costs:
                    daily_costs[date_str] = {}
                if date_str not in daily_tokens:
                    daily_tokens[date_str] = {}
                daily_costs[date_str][model] = cost
                if snapshot.token_counts and model in snapshot.token_counts:
                    daily_tokens[date_str][model] = snapshot.token_counts[model]

    # Build trend data per gateway
    trends = []
    for gw_id, snapshot in by_gateway.items():
        # Get gateway name
        gateway_stmt = select(Gateway).where(Gateway.id == gw_id)
        gateway_result = await session.execute(gateway_stmt)
        gateway = gateway_result.scalar_one_or_none()
        gateway_name = gateway.name if gateway else "Unknown"

        # Build days list
        days_data = []
        for d in chart_dates:
            day_costs = daily_costs.get(d, {})
            day_tokens = daily_tokens.get(d, {})

            total_cost = sum(day_costs.values())
            total_tokens = sum(day_tokens.values())

            # Build model_costs dict
            model_costs = {}
            for m, c in day_costs.items():
                # Use ModelName for display
                display_name = ModelName(m)
                model_costs[display_name] = round(c, 2)

            days_data.append(TrendDay(
                date=d,
                total_cost=round(total_cost, 2),
                total_tokens=total_tokens,
                model_costs=model_costs,
            ))

        trends.append(TrendRead(
            gateway_id=gw_id,
            gateway_name=gateway_name,
            range=range_param,
            days=days_data,
            last_collected_at=snapshot.collected_at,
        ))

    return trends
