"""Bus de mensagens agente↔agente↔humano — Spec 029."""

from __future__ import annotations

import json
import sqlite3
from datetime import datetime, timezone
from typing import Any

from app.agents.catalog import AGENT_CATALOG, resolve_agent


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def init_messages_schema(conn: sqlite3.Connection) -> None:
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS agent_threads (
            id INTEGER PRIMARY KEY,
            subject TEXT NOT NULL,
            severity TEXT NOT NULL DEFAULT 'info',
            status TEXT NOT NULL DEFAULT 'open',
            primary_agent TEXT NOT NULL,
            related_finding_id INTEGER,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
        );
        CREATE TABLE IF NOT EXISTS agent_messages (
            id INTEGER PRIMARY KEY,
            thread_id INTEGER NOT NULL,
            from_type TEXT NOT NULL,
            from_id TEXT NOT NULL,
            to_type TEXT NOT NULL,
            to_id TEXT NOT NULL,
            body TEXT NOT NULL,
            context_json TEXT,
            requires_human INTEGER NOT NULL DEFAULT 0,
            human_role_hint TEXT,
            acknowledged_at TEXT,
            acknowledged_by TEXT,
            created_at TEXT NOT NULL,
            FOREIGN KEY (thread_id) REFERENCES agent_threads(id)
        );
        CREATE INDEX IF NOT EXISTS idx_agent_messages_thread ON agent_messages(thread_id);
        CREATE INDEX IF NOT EXISTS idx_agent_messages_inbox ON agent_messages(requires_human, acknowledged_at);
    """)


def create_thread(
    conn: sqlite3.Connection,
    *,
    subject: str,
    primary_agent: str,
    severity: str = "info",
    related_finding_id: int | None = None,
) -> int:
    now = _now()
    return int(
        conn.execute(
            """INSERT INTO agent_threads (subject, severity, status, primary_agent, related_finding_id, created_at, updated_at)
               VALUES (?,?,?,?,?,?,?)""",
            (subject, severity, "open", primary_agent, related_finding_id, now, now),
        ).lastrowid
    )


def post_message(
    conn: sqlite3.Connection,
    *,
    thread_id: int,
    from_type: str,
    from_id: str,
    to_type: str,
    to_id: str,
    body: str,
    context: dict | None = None,
    requires_human: bool = False,
    human_role_hint: str | None = None,
) -> int:
    now = _now()
    mid = int(
        conn.execute(
            """INSERT INTO agent_messages
               (thread_id, from_type, from_id, to_type, to_id, body, context_json, requires_human, human_role_hint, created_at)
               VALUES (?,?,?,?,?,?,?,?,?,?)""",
            (
                thread_id,
                from_type,
                from_id,
                to_type,
                to_id,
                body,
                json.dumps(context or {}),
                1 if requires_human else 0,
                human_role_hint,
                now,
            ),
        ).lastrowid
    )
    conn.execute("UPDATE agent_threads SET updated_at=? WHERE id=?", (now, thread_id))
    return mid


def notify_finding_to_operators(
    conn: sqlite3.Connection,
    *,
    finding_id: int,
    scenario_id: str,
    title: str,
    severity: str,
    human_action: str,
    agent_id: str,
    thread_id: int | None = None,
) -> int:
    """Abre thread + mensagem para operadores humanos."""
    profile = resolve_agent(scenario_id, agent_id)
    role_hint = {
        "critical": "agentic_operator",
        "high": "ops_lead",
        "warn": "technician",
    }.get(severity, "technician")

    if thread_id:
        tid = thread_id
    else:
        existing = conn.execute(
            "SELECT id FROM agent_threads WHERE related_finding_id=? AND status='open'",
            (finding_id,),
        ).fetchone()
        if existing:
            tid = existing["id"]
        else:
            inc = conn.execute(
                "SELECT thread_id FROM agent_incidents WHERE scenario_id=? AND status='open'",
                (scenario_id,),
            ).fetchone()
            if inc and inc["thread_id"]:
                tid = inc["thread_id"]
            else:
                tid = create_thread(
                    conn,
                    subject=title,
                    primary_agent=profile.id,
                    severity=severity,
                    related_finding_id=finding_id,
                )

    agent_name = profile.name
    body = (
        f"**{agent_name}** ({profile.id}) detectou: {title}\n\n"
        f"Acção sugerida: {human_action or 'Investigar manualmente.'}\n\n"
        f"Cenário: `{scenario_id}` · Severidade: **{severity}**"
    )

    # Mensagem agente → humanos (inbox operadores)
    post_message(
        conn,
        thread_id=tid,
        from_type="agent",
        from_id=profile.id,
        to_type="human",
        to_id=role_hint,
        body=body,
        context={"finding_id": finding_id, "scenario_id": scenario_id, "severity": severity},
        requires_human=severity in ("high", "critical", "warn"),
        human_role_hint=role_hint,
    )

    # Maestro (A0) regista coordenação inter-agente
    if profile.id not in ("A0", "orchestrator"):
        post_message(
            conn,
            thread_id=tid,
            from_type="agent",
            from_id="A0",
            to_type="agent",
            to_id=profile.id,
            body=f"Registado finding #{finding_id}. Aguardando acção humana ({role_hint}).",
            context={"coordination": True},
            requires_human=False,
        )

    return tid


def list_inbox(conn: sqlite3.Connection, *, role: str, limit: int = 50) -> list[dict]:
    """Mensagens pendentes para operadores humanos."""
    role_priority = {
        "super_admin": ("agentic_operator", "ops_lead", "technician", "noc", "devops", "security_analyst"),
        "agentic_operator": ("agentic_operator", "ops_lead"),
        "ops_lead": ("ops_lead", "agentic_operator", "technician"),
        "technician": ("technician",),
        "security_analyst": ("security_analyst", "agentic_operator"),
        "devops": ("devops", "ops_lead"),
        "noc": ("noc",),
        "developer": ("developer", "ops_lead"),
    }
    allowed = role_priority.get(role, (role,))

    q = """
        SELECT m.*, t.subject AS thread_subject, t.severity AS thread_severity, t.primary_agent
        FROM agent_messages m
        JOIN agent_threads t ON t.id = m.thread_id
        WHERE m.requires_human = 1 AND m.acknowledged_at IS NULL
          AND m.to_type = 'human'
        ORDER BY m.id DESC LIMIT ?
    """
    rows = [dict(r) for r in conn.execute(q, (limit * 3,))]
    out = []
    for r in rows:
        hint = r.get("human_role_hint") or r.get("to_id") or ""
        if role == "super_admin" or hint in allowed or role in allowed:
            r["agent_name"] = AGENT_CATALOG.get(r["from_id"], AGENT_CATALOG.get("sentinel")).name
            out.append(r)
        if len(out) >= limit:
            break
    return out


def list_threads(conn: sqlite3.Connection, *, limit: int = 40) -> list[dict]:
    rows = conn.execute(
        "SELECT * FROM agent_threads ORDER BY updated_at DESC LIMIT ?", (limit,)
    ).fetchall()
    out = []
    for r in rows:
        item = dict(r)
        p = AGENT_CATALOG.get(item["primary_agent"])
        item["agent_name"] = p.name if p else item["primary_agent"]
        pending = conn.execute(
            "SELECT COUNT(*) c FROM agent_messages WHERE thread_id=? AND requires_human=1 AND acknowledged_at IS NULL",
            (item["id"],),
        ).fetchone()["c"]
        item["pending_human"] = pending
        out.append(item)
    return out


def thread_messages(conn: sqlite3.Connection, thread_id: int) -> list[dict]:
    rows = conn.execute(
        "SELECT * FROM agent_messages WHERE thread_id=? ORDER BY id ASC", (thread_id,)
    ).fetchall()
    out = []
    for r in rows:
        item = dict(r)
        if item["from_type"] == "agent":
            p = AGENT_CATALOG.get(item["from_id"])
            item["from_label"] = f"{p.name} ({item['from_id']})" if p else item["from_id"]
        else:
            item["from_label"] = item["from_id"]
        out.append(item)
    return out


def human_reply(
    conn: sqlite3.Connection,
    *,
    thread_id: int,
    username: str,
    body: str,
    target_agent: str | None = None,
) -> int:
    agent_to = target_agent or conn.execute(
        "SELECT primary_agent FROM agent_threads WHERE id=?", (thread_id,)
    ).fetchone()["primary_agent"]

    mid = post_message(
        conn,
        thread_id=thread_id,
        from_type="human",
        from_id=username,
        to_type="agent",
        to_id=agent_to,
        body=body,
        requires_human=False,
    )

    return mid


def ack_message(conn: sqlite3.Connection, message_id: int, username: str) -> bool:
    row = conn.execute("SELECT id FROM agent_messages WHERE id=?", (message_id,)).fetchone()
    if not row:
        return False
    conn.execute(
        "UPDATE agent_messages SET acknowledged_at=?, acknowledged_by=? WHERE id=?",
        (_now(), username, message_id),
    )
    return True
