from __future__ import annotations

from collections import Counter
import json

from fastapi import Depends, FastAPI
from sqlalchemy import select

from services.shared.core import Role, utc_now_iso
from services.shared.db import get_session
from services.shared.event_bus import append_outbox_event, event_bus_enabled
from services.shared.models import AgentStateIn, AgentStateOut, HealthResponse
from services.shared.security import require_roles
from services.shared.sql_init import init_sql_schema
from services.shared.sql_models import (
    Interaction,
    SupervisorAgentStateRow,
    SupervisorQueueSnapshotRow,
    VoiceEventRow,
)

app = FastAPI(title="supervisor-service", version="1.1.0")

init_sql_schema()


def _agent_out(row: SupervisorAgentStateRow) -> AgentStateOut:
    return AgentStateOut(
        agent_id=row.agent_id,
        state=row.state,
        queue_id=row.queue_id,
        updated_at=row.updated_at,
    )


@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
    return HealthResponse(status="ok", service="supervisor-service", version="v1.1")


@app.post("/supervisor/agent-states", response_model=AgentStateOut)
def set_agent_state(
    payload: AgentStateIn,
    _: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR)),
) -> AgentStateOut:
    session = get_session()
    try:
        row = session.execute(
            select(SupervisorAgentStateRow).where(SupervisorAgentStateRow.agent_id == payload.agent_id)
        ).scalar_one_or_none()
        if not row:
            row = SupervisorAgentStateRow(
                agent_id=payload.agent_id,
                state=payload.state,
                queue_id=payload.queue_id,
                updated_at=utc_now_iso(),
            )
            session.add(row)
        else:
            row.state = payload.state
            row.queue_id = payload.queue_id
            row.updated_at = utc_now_iso()

        if event_bus_enabled():
            append_outbox_event(
                session,
                event_type="agent.state.changed",
                producer_service="supervisor-service",
                entity_type="agent_state",
                entity_id=payload.agent_id,
                payload={
                    "event": "agent.state.changed",
                    "agent_id": payload.agent_id,
                    "state": payload.state,
                    "queue_id": payload.queue_id,
                    "updated_at": row.updated_at,
                },
            )
        session.commit()
        session.refresh(row)
        return _agent_out(row)
    finally:
        session.close()


@app.get("/supervisor/agents", response_model=list[AgentStateOut])
def list_agents(
    _: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR, Role.ANALYST)),
) -> list[AgentStateOut]:
    session = get_session()
    try:
        rows = session.execute(
            select(SupervisorAgentStateRow).order_by(SupervisorAgentStateRow.id.asc())
        ).scalars().all()
        return [_agent_out(r) for r in rows]
    finally:
        session.close()


@app.post("/supervisor/queue-metrics")
def update_queue_metrics(
    queue_id: str,
    in_queue: int,
    avg_wait_seconds: int,
    _: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR)),
) -> dict:
    session = get_session()
    try:
        row = session.execute(
            select(SupervisorQueueSnapshotRow).where(SupervisorQueueSnapshotRow.queue_id == queue_id)
        ).scalar_one_or_none()
        if not row:
            row = SupervisorQueueSnapshotRow(
                queue_id=queue_id,
                in_queue=in_queue,
                avg_wait_seconds=avg_wait_seconds,
                updated_at=utc_now_iso(),
            )
            session.add(row)
        else:
            row.in_queue = in_queue
            row.avg_wait_seconds = avg_wait_seconds
            row.updated_at = utc_now_iso()

        session.commit()
        return {
            "queue_id": row.queue_id,
            "in_queue": row.in_queue,
            "avg_wait_seconds": row.avg_wait_seconds,
            "updated_at": row.updated_at,
        }
    finally:
        session.close()




@app.get("/supervisor/realtime")
def realtime(
    _: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR, Role.ANALYST)),
) -> dict:
    session = get_session()
    try:
        agents = session.execute(
            select(SupervisorAgentStateRow).order_by(SupervisorAgentStateRow.id.asc())
        ).scalars().all()
        snapshots = session.execute(
            select(SupervisorQueueSnapshotRow).order_by(SupervisorQueueSnapshotRow.id.asc())
        ).scalars().all()

        items = [
            {
                "agent_id": a.agent_id,
                "state": a.state,
                "queue_id": a.queue_id,
                "updated_at": a.updated_at,
            }
            for a in agents
        ]
        status_counts = Counter(a["state"] for a in items)

        return {
            "agents": {
                "total": len(items),
                "by_state": dict(status_counts),
                "items": items,
            },
            "queues": [
                {
                    "queue_id": q.queue_id,
                    "in_queue": q.in_queue,
                    "avg_wait_seconds": q.avg_wait_seconds,
                    "updated_at": q.updated_at,
                }
                for q in snapshots
            ],
            "timestamp": utc_now_iso(),
        }
    finally:
        session.close()


@app.get("/supervisor/live-calls")
def live_calls(
    limit: int = 20,
    _: dict = Depends(require_roles(Role.ADMIN, Role.SUPERVISOR, Role.ANALYST)),
) -> dict:
    session = get_session()
    try:
        event_rows = session.execute(
            select(VoiceEventRow).order_by(VoiceEventRow.id.desc()).limit(500)
        ).scalars().all()
        interaction_rows = session.execute(
            select(Interaction).order_by(Interaction.id.desc())
        ).scalars().all()

        interaction_by_id = {row.interaction_id: row for row in interaction_rows}
        calls_by_id: dict[str, dict] = {}
        tracked_types = {"call.started", "call.ended", "recording.ready"}

        for row in event_rows[:500]:
            payload = {}
            try:
                payload = json.loads(row.payload_json or "{}")
            except Exception:  # noqa: BLE001
                payload = {}
            if payload.get("source") != "asterisk":
                continue
            if row.event_type not in tracked_types:
                continue

            call = calls_by_id.get(row.call_id) or {
                "call_id": row.call_id,
                "interaction_id": row.interaction_id,
                "started_at": None,
                "ended_at": None,
                "has_recording": False,
                "last_at": row.created_at,
            }
            if row.interaction_id:
                call["interaction_id"] = row.interaction_id
            call["last_at"] = row.created_at or call["last_at"]
            if row.event_type == "call.started":
                call["started_at"] = row.created_at
            elif row.event_type == "call.ended":
                call["ended_at"] = row.created_at
            elif row.event_type == "recording.ready":
                call["has_recording"] = True
            calls_by_id[row.call_id] = call

        items = []
        indexed_interaction_ids: set[str] = set()
        for call in calls_by_id.values():
            interaction = interaction_by_id.get(call.get("interaction_id"))
            queue_id = interaction.queue_id if interaction else None
            status = "ended" if call.get("ended_at") else "active"
            items.append(
                {
                    **call,
                    "queue_id": queue_id,
                    "status": status,
                }
            )
            if call.get("interaction_id"):
                indexed_interaction_ids.add(str(call["interaction_id"]))

        # Fallback interactions are shown only when no Asterisk-linked calls were detected.
        # This keeps supervisor live-calls focused on real call_id-based telephony events.
        if not items:
            for row in interaction_rows:
                if row.channel != "voice":
                    continue
                if not (row.subject or "").startswith("Inbound call"):
                    continue
                if row.interaction_id in indexed_interaction_ids:
                    continue
                items.append(
                    {
                        "call_id": f"interaction:{row.interaction_id}",
                        "interaction_id": row.interaction_id,
                        "started_at": row.created_at,
                        "ended_at": row.updated_at if row.status == "closed" else None,
                        "has_recording": False,
                        "last_at": row.updated_at or row.created_at,
                        "queue_id": row.queue_id,
                        "status": row.status or "new",
                    }
                )

        items.sort(key=lambda item: item.get("last_at") or "", reverse=True)
        return {
            "items": items[: max(limit, 1)],
            "timestamp": utc_now_iso(),
        }
    finally:
        session.close()
