"""Agents API — souls & activity."""
from __future__ import annotations

import asyncio
import json
from typing import Any, Optional

from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field

from app.db import fetch_all, fetch_one
from app.services import agent_approvals, agent_integration, agent_souls
from app.services.agent_names import normalize_agent_key
from app.services.agent_terminal import execute_terminal_command

router = APIRouter(prefix="/api/agents", tags=["agents-api"])


class SoulUpdate(BaseModel):
    display_name: Optional[str] = None
    role_title: Optional[str] = None
    soul_md: Optional[str] = None
    responsibilities: Optional[str] = None
    is_active: Optional[bool] = None


class ActionRequestBody(BaseModel):
    agent_key: str
    title: str = Field(..., min_length=1, max_length=255)
    action_type: str = "query"
    query_payload: dict[str, Any] = Field(default_factory=dict)


class RejectBody(BaseModel):
    reason: str = ""


class ExecuteBody(BaseModel):
    result: dict[str, Any] = Field(default_factory=dict)


class HandoffBody(BaseModel):
    from_agent: str
    to_agent: str
    handoff_type: str = "partner"
    payload: dict[str, Any] = Field(default_factory=dict)
    correlation_id: Optional[str] = None


class TerminalCommandBody(BaseModel):
    command: str = Field(..., min_length=1, max_length=2000)


def _aggregate_agent_stats() -> dict[str, dict[str, Any]]:
    from datetime import datetime, timedelta, timezone

    rows = fetch_all(
        """
        SELECT agent_name, title, status, created_at
        FROM agent_events
        WHERE created_at >= NOW() - INTERVAL '7 days'
        ORDER BY created_at DESC
        """
    )
    now = datetime.now(timezone.utc)
    by_key: dict[str, dict[str, Any]] = {}
    for r in rows:
        key = normalize_agent_key(str(r.get("agent_name") or ""))
        if not key:
            continue
        bucket = by_key.setdefault(
            key,
            {"events_6h": 0, "errors_24h": 0, "last_event_at": None, "last_event_title": None},
        )
        created = r.get("created_at")
        if bucket["last_event_at"] is None and created is not None:
            bucket["last_event_at"] = created
            bucket["last_event_title"] = r.get("title")
        if created is not None:
            if hasattr(created, "tzinfo") and created.tzinfo is None:
                created = created.replace(tzinfo=timezone.utc)
            if created >= now - timedelta(hours=6):
                bucket["events_6h"] += 1
            if created >= now - timedelta(hours=24) and str(r.get("status") or "") in ("error", "rejected"):
                bucket["errors_24h"] += 1
    return by_key


def _node_health(events_6h: int, errors_24h: int, event_count: int) -> str:
    if events_6h > 0 and errors_24h == 0:
        return "healthy"
    if events_6h > 0:
        return "warn"
    if event_count > 0:
        return "idle"
    return "offline"


@router.get("/collaboration")
def api_collaboration() -> dict[str, Any]:
    items = agent_integration.list_collaboration()
    return {"items": items, "count": len(items)}


@router.post("/handoff")
def api_create_handoff(body: HandoffBody) -> dict[str, Any]:
    try:
        handoff = agent_integration.create_handoff(
            body.from_agent,
            body.to_agent,
            handoff_type=body.handoff_type,
            payload=body.payload,
            correlation_id=body.correlation_id,
        )
    except ValueError as exc:
        raise HTTPException(400, str(exc)) from exc
    return {"ok": True, "handoff": handoff}


def _terminal_payload(ev: dict[str, Any]) -> dict[str, Any]:
    if ev.get("created_at") and hasattr(ev["created_at"], "isoformat"):
        ev["created_at"] = ev["created_at"].isoformat()
    meta = ev.get("metadata")
    if isinstance(meta, str):
        try:
            meta = json.loads(meta)
        except Exception:
            meta = {}
    ev["metadata"] = meta or {}
    line_type = "handoff" if "handoff" in str(ev.get("event_type") or "") else "action"
    if str(ev.get("event_type") or "").startswith("terminal_"):
        line_type = "command" if ev.get("event_type") == "terminal_in" else "output"
    if ev["metadata"].get("target_agent"):
        line_type = "handoff_out"
    if ev["metadata"].get("source_agent"):
        line_type = "handoff_in"
    if str(ev.get("status") or "") in ("error", "rejected"):
        line_type = "error"
    return {
        "type": line_type,
        "id": ev.get("id"),
        "agent": normalize_agent_key(ev.get("agent_name")),
        "message": ev.get("title") or ev.get("event_type"),
        "detail": (ev.get("body") or "")[:500],
        "status": ev.get("status"),
        "correlation_id": ev["metadata"].get("correlation_id"),
        "at": ev.get("created_at"),
    }


@router.get("/souls/{agent_key}/terminal/stream")
async def api_agent_terminal_stream(agent_key: str, correlation_id: Optional[str] = None):
    key = normalize_agent_key(agent_key)
    soul = agent_souls.get_soul(key)
    if not soul:
        raise HTTPException(404, "Agent not found")

    async def event_gen():
        last_id = 0
        try:
            hist = fetch_all(
                """
                SELECT id, agent_name, event_type, title, body, status, channel, metadata, created_at
                FROM agent_events
                WHERE LOWER(agent_name) = %s
                ORDER BY id DESC
                LIMIT 30
                """,
                (key,),
            )
            for row in reversed(hist):
                last_id = max(last_id, int(row["id"]))
                payload = _terminal_payload(dict(row))
                yield f"data: {json.dumps(payload, default=str)}\n\n"
        except Exception:
            pass

        while True:
            try:
                if correlation_id:
                    rows = fetch_all(
                        """
                        SELECT id, agent_name, event_type, title, body, status, channel, metadata, created_at
                        FROM agent_events
                        WHERE metadata->>'correlation_id' = %s
                          AND id > %s
                        ORDER BY id ASC
                        LIMIT 50
                        """,
                        (correlation_id, last_id),
                    )
                else:
                    rows = fetch_all(
                        """
                        SELECT id, agent_name, event_type, title, body, status, channel, metadata, created_at
                        FROM agent_events
                        WHERE LOWER(agent_name) = %s AND id > %s
                        ORDER BY id ASC
                        LIMIT 50
                        """,
                        (key, last_id),
                    )
                for row in rows:
                    last_id = max(last_id, int(row["id"]))
                    payload = _terminal_payload(dict(row))
                    yield f"data: {json.dumps(payload, default=str)}\n\n"
            except Exception as exc:
                err = {"type": "error", "message": str(exc)}
                yield f"data: {json.dumps(err)}\n\n"
            await asyncio.sleep(0.4)

    return StreamingResponse(
        event_gen(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.get("/live/stream")
async def api_agents_live_stream():
    """Single realtime stream for all agent terminals."""

    async def event_gen():
        last_id = 0
        try:
            recent = fetch_all(
                """
                SELECT id, agent_name, event_type, title, body, status, channel, metadata, created_at
                FROM agent_events
                WHERE created_at >= NOW() - INTERVAL '30 minutes'
                ORDER BY id ASC
                LIMIT 80
                """
            )
            for row in recent:
                last_id = max(last_id, int(row["id"]))
                payload = _terminal_payload(dict(row))
                payload["replay"] = True
                yield f"data: {json.dumps(payload, default=str)}\n\n"
        except Exception:
            pass
        yield f"data: {json.dumps({'type': 'connected', 'last_id': last_id})}\n\n"

        while True:
            try:
                rows = fetch_all(
                    """
                    SELECT id, agent_name, event_type, title, body, status, channel, metadata, created_at
                    FROM agent_events
                    WHERE id > %s
                    ORDER BY id ASC
                    LIMIT 100
                    """,
                    (last_id,),
                )
                for row in rows:
                    last_id = max(last_id, int(row["id"]))
                    payload = _terminal_payload(dict(row))
                    yield f"data: {json.dumps(payload, default=str)}\n\n"
            except Exception as exc:
                err = {"type": "error", "message": str(exc)}
                yield f"data: {json.dumps(err)}\n\n"
            await asyncio.sleep(0.25)

    return StreamingResponse(
        event_gen(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.get("/souls")
def api_list_souls() -> dict[str, Any]:
    return {"items": agent_souls.list_souls(), "count": len(agent_souls.list_souls())}


@router.get("/souls/{agent_key}")
def api_get_soul(agent_key: str) -> dict[str, Any]:
    soul = agent_souls.get_soul(agent_key)
    if not soul:
        raise HTTPException(404, "Agent not found")
    return {"soul": soul}


@router.get("/souls/{agent_key}/events")
def api_list_agent_events(agent_key: str, limit: int = 50) -> dict[str, Any]:
    soul = agent_souls.get_soul(agent_key)
    if not soul:
        raise HTTPException(404, "Agent not found")
    items = agent_souls.list_agent_events(agent_key, limit=min(limit, 100))
    return {"agent_key": agent_key.lower(), "items": items, "count": len(items)}


@router.post("/souls/{agent_key}/terminal/command")
async def api_terminal_command(agent_key: str, body: TerminalCommandBody) -> dict[str, Any]:
    try:
        return await execute_terminal_command(agent_key, body.command.strip())
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.put("/souls/{agent_key}")
def api_update_soul(agent_key: str, body: SoulUpdate) -> dict[str, Any]:
    try:
        soul = agent_souls.update_soul(agent_key, **body.model_dump(exclude_none=True))
    except ValueError as exc:
        raise HTTPException(404, str(exc)) from exc
    return {"soul": soul}


@router.get("/mesh")
def api_agents_mesh() -> dict[str, Any]:
    souls = agent_souls.list_souls()
    by_key = _aggregate_agent_stats()

    nodes: list[dict[str, Any]] = []
    for soul in souls:
        key = str(soul.get("agent_key") or "").lower()
        row = by_key.get(key, {})
        events_6h = int(row.get("events_6h") or 0)
        errors_24h = int(row.get("errors_24h") or 0)
        health = _node_health(events_6h, errors_24h, int(soul.get("event_count") or 0))
        is_active = health == "healthy" and events_6h > 0
        node = dict(soul)
        node["health"] = health
        node["events_6h"] = events_6h
        node["errors_24h"] = errors_24h
        node["is_active"] = is_active
        node["last_event_title"] = row.get("last_event_title")
        lat = row.get("last_event_at")
        if lat is not None and hasattr(lat, "isoformat"):
            node["last_event_at"] = lat.isoformat()
        nodes.append(node)

    report_edges: list[dict[str, Any]] = []
    for node in nodes:
        key = str(node.get("agent_key") or "").lower()
        if key == "herman" or not node.get("is_active"):
            continue
        report_edges.append(
            {
                "source": key,
                "target": "herman",
                "type": "report",
                "active": True,
                "weight": int(node.get("events_6h") or 1),
            }
        )

    delegate_rows = fetch_all(
        """
        SELECT metadata, created_at
        FROM agent_events
        WHERE agent_name = 'herman'
          AND created_at >= NOW() - INTERVAL '6 hours'
          AND (
            event_type IN ('openswarm_delegation', 'delegate', 'packaging_delivered', 'telegram_delegation')
            OR metadata ? 'delegated'
          )
        ORDER BY created_at DESC
        LIMIT 100
        """
    )
    delegate_edges: list[dict[str, Any]] = []
    seen_delegate: set[tuple[str, str]] = set()
    for r in delegate_rows:
        meta = r.get("metadata") or {}
        if isinstance(meta, str):
            try:
                meta = json.loads(meta)
            except Exception:
                meta = {}
        delegated = meta.get("delegated") or meta.get("delegated_agents") or []
        if isinstance(delegated, str):
            delegated = [delegated]
        channel = meta.get("channel") or "herman"
        for agent in delegated:
            tgt = normalize_agent_key(str(agent))
            if not tgt or tgt == "herman":
                continue
            pair = ("herman", tgt)
            if pair in seen_delegate:
                continue
            seen_delegate.add(pair)
            delegate_edges.append(
                {
                    "source": "herman",
                    "target": tgt,
                    "type": "delegate",
                    "active": True,
                    "channel": channel,
                }
            )

    peer_static = [
        {
            "source": str(r["from_agent"]),
            "target": str(r["to_agent"]),
            "type": "static",
            "handoff_type": r.get("handoff_type"),
        }
        for r in agent_integration.list_collaboration()
    ]
    peer_live = agent_integration.peer_edges_live(hours=6)
    peer_edges = peer_static + peer_live

    herman_active = any(n.get("agent_key") == "herman" and n.get("is_active") for n in nodes)
    herman_active = herman_active or bool(delegate_edges) or bool(report_edges)
    executive_edges = []
    if herman_active:
        executive_edges = [
            {"source": "herman", "target": "ceo", "type": "executive", "active": True},
            {"source": "herman", "target": "cto", "type": "executive", "active": True},
        ]

    return {
        "nodes": nodes,
        "report_edges": report_edges,
        "delegate_edges": delegate_edges,
        "peer_edges": peer_edges,
        "executive_edges": executive_edges,
        "edges": report_edges,
        "executives": [
            {"id": "ceo", "label": "CEO", "role": "Aissa"},
            {"id": "cto", "label": "CTO", "role": "Platform"},
        ],
    }


@router.get("/approvals")
def api_list_approvals(status: Optional[str] = None, limit: int = 50) -> dict[str, Any]:
    try:
        items = agent_approvals.list_requests(status=status, limit=limit)
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return {"items": items, "count": len(items)}


@router.post("/requests")
def api_create_action_request(body: ActionRequestBody) -> dict[str, Any]:
    try:
        req = agent_approvals.create_request(
            body.agent_key,
            body.title,
            action_type=body.action_type,
            query_payload=body.query_payload,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    auto = None
    rid = (req or {}).get("id")
    if rid:
        try:
            auto = agent_approvals.try_auto_approve_and_execute(int(rid))
        except Exception:
            auto = None
    msg = "Wacht op goedkeuring voordat query uitgevoerd mag worden"
    if auto:
        msg = "Auto-goedgekeurd en uitgevoerd (SysOps policy)"
    return {"ok": True, "request": req, "auto_executed": bool(auto), "message": msg}


@router.post("/approvals/{request_id}/approve")
def api_approve_request(request_id: int) -> dict[str, Any]:
    import os

    import httpx

    try:
        req = agent_approvals.approve_request(request_id, approved_by="ceo")
        action = req.get("action_type") or ""
        auto_result = None

        if action in ("config_backup", "maintenance_scan"):
            tools_url = os.getenv("TOOLS_API_URL", "http://tools-api:8700").rstrip("/")
            path = "/ops/backup/run" if action == "config_backup" else "/ops/maintenance/scan"
            payload = {"approval_request_id": request_id} if action == "config_backup" else None
            with httpx.Client(timeout=180.0) as client:
                resp = client.post(f"{tools_url}{path}", json=payload or {})
            try:
                auto_result = resp.json()
            except Exception:
                auto_result = {"ok": False, "detail": resp.text}
            req = agent_approvals.mark_executed(request_id, auto_result or {})

    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return {"ok": True, "request": req, "executed": auto_result is not None, "result": auto_result}


@router.post("/approvals/{request_id}/reject")
def api_reject_request(request_id: int, body: RejectBody) -> dict[str, Any]:
    try:
        req = agent_approvals.reject_request(request_id, reason=body.reason)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return {"ok": True, "request": req}


@router.post("/approvals/{request_id}/execute")
def api_execute_approved_request(request_id: int, body: ExecuteBody) -> dict[str, Any]:
    import os

    import httpx

    try:
        req = agent_approvals.require_approved(request_id)
        result_payload = body.result or {}
        action = req.get("action_type") or ""

        if action == "config_backup":
            tools_url = os.getenv("TOOLS_API_URL", "http://tools-api:8700").rstrip("/")
            with httpx.Client(timeout=180.0) as client:
                resp = client.post(
                    f"{tools_url}/ops/backup/run",
                    json={"approval_request_id": request_id},
                )
            try:
                result_payload = resp.json()
            except Exception:
                result_payload = {"ok": False, "detail": resp.text}
            if resp.status_code >= 400:
                raise HTTPException(status_code=502, detail=result_payload.get("detail") or resp.text)

        elif action == "maintenance_scan":
            tools_url = os.getenv("TOOLS_API_URL", "http://tools-api:8700").rstrip("/")
            with httpx.Client(timeout=120.0) as client:
                resp = client.post(f"{tools_url}/ops/maintenance/scan")
            result_payload = resp.json() if resp.status_code < 400 else {"ok": False, "detail": resp.text}

        req = agent_approvals.mark_executed(request_id, result_payload)
    except HTTPException:
        raise
    except PermissionError as exc:
        raise HTTPException(status_code=403, detail=str(exc)) from exc
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc
    return {"ok": True, "request": req}
